mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
379 lines
8.5 KiB
Go
379 lines
8.5 KiB
Go
// +build linux
|
|
|
|
package shared
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/lxc/lxd/shared/units"
|
|
)
|
|
|
|
// --- pure Go functions ---
|
|
|
|
func GetFileStat(p string) (uid int, gid int, major uint32, minor uint32, inode uint64, nlink int, err error) {
|
|
var stat unix.Stat_t
|
|
err = unix.Lstat(p, &stat)
|
|
if err != nil {
|
|
return
|
|
}
|
|
uid = int(stat.Uid)
|
|
gid = int(stat.Gid)
|
|
inode = uint64(stat.Ino)
|
|
nlink = int(stat.Nlink)
|
|
if stat.Mode&unix.S_IFBLK != 0 || stat.Mode&unix.S_IFCHR != 0 {
|
|
major = unix.Major(stat.Rdev)
|
|
minor = unix.Minor(stat.Rdev)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// GetPathMode returns a os.FileMode for the provided path
|
|
func GetPathMode(path string) (os.FileMode, error) {
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
return os.FileMode(0000), err
|
|
}
|
|
|
|
mode, _, _ := GetOwnerMode(fi)
|
|
return mode, nil
|
|
}
|
|
|
|
func parseMountinfo(name string) int {
|
|
// In case someone uses symlinks we need to look for the actual
|
|
// mountpoint.
|
|
actualPath, err := filepath.EvalSymlinks(name)
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
|
|
f, err := os.Open("/proc/self/mountinfo")
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
defer f.Close()
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
tokens := strings.Fields(line)
|
|
if len(tokens) < 5 {
|
|
return -1
|
|
}
|
|
cleanPath := filepath.Clean(tokens[4])
|
|
if cleanPath == actualPath {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func IsMountPoint(name string) bool {
|
|
ret := parseMountinfo(name)
|
|
if ret == 1 {
|
|
return true
|
|
}
|
|
|
|
stat, err := os.Stat(name)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
rootStat, err := os.Lstat(name + "/..")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// If the directory has the same device as parent, then it's not a mountpoint.
|
|
return stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev
|
|
}
|
|
|
|
func SetSize(fd int, width int, height int) (err error) {
|
|
var dimensions [4]uint16
|
|
dimensions[0] = uint16(height)
|
|
dimensions[1] = uint16(width)
|
|
|
|
if _, _, err := unix.Syscall6(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This uses ssize_t llistxattr(const char *path, char *list, size_t size); to
|
|
// handle symbolic links (should it in the future be possible to set extended
|
|
// attributed on symlinks): If path is a symbolic link the extended attributes
|
|
// associated with the link itself are retrieved.
|
|
func llistxattr(path string, list []byte) (sz int, err error) {
|
|
var _p0 *byte
|
|
_p0, err = unix.BytePtrFromString(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var _p1 unsafe.Pointer
|
|
if len(list) > 0 {
|
|
_p1 = unsafe.Pointer(&list[0])
|
|
} else {
|
|
_p1 = unsafe.Pointer(nil)
|
|
}
|
|
r0, _, e1 := unix.Syscall(unix.SYS_LLISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(list)))
|
|
sz = int(r0)
|
|
if e1 != 0 {
|
|
err = e1
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetAllXattr retrieves all extended attributes associated with a file,
|
|
// directory or symbolic link.
|
|
func GetAllXattr(path string) (xattrs map[string]string, err error) {
|
|
e1 := fmt.Errorf("Extended attributes changed during retrieval")
|
|
|
|
// Call llistxattr() twice: First, to determine the size of the buffer
|
|
// we need to allocate to store the extended attributes, second, to
|
|
// actually store the extended attributes in the buffer. Also, check if
|
|
// the size/number of extended attributes hasn't changed between the two
|
|
// calls.
|
|
pre, err := llistxattr(path, nil)
|
|
if err != nil || pre < 0 {
|
|
return nil, err
|
|
}
|
|
if pre == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
dest := make([]byte, pre)
|
|
|
|
post, err := llistxattr(path, dest)
|
|
if err != nil || post < 0 {
|
|
return nil, err
|
|
}
|
|
if post != pre {
|
|
return nil, e1
|
|
}
|
|
|
|
split := strings.Split(string(dest), "\x00")
|
|
if split == nil {
|
|
return nil, fmt.Errorf("No valid extended attribute key found")
|
|
}
|
|
// *listxattr functions return a list of names as an unordered array
|
|
// of null-terminated character strings (attribute names are separated
|
|
// by null bytes ('\0')), like this: user.name1\0system.name1\0user.name2\0
|
|
// Since we split at the '\0'-byte the last element of the slice will be
|
|
// the empty string. We remove it:
|
|
if split[len(split)-1] == "" {
|
|
split = split[:len(split)-1]
|
|
}
|
|
|
|
xattrs = make(map[string]string, len(split))
|
|
|
|
for _, x := range split {
|
|
xattr := string(x)
|
|
// Call Getxattr() twice: First, to determine the size of the
|
|
// buffer we need to allocate to store the extended attributes,
|
|
// second, to actually store the extended attributes in the
|
|
// buffer. Also, check if the size of the extended attribute
|
|
// hasn't changed between the two calls.
|
|
pre, err = unix.Getxattr(path, xattr, nil)
|
|
if err != nil || pre < 0 {
|
|
return nil, err
|
|
}
|
|
|
|
dest = make([]byte, pre)
|
|
post := 0
|
|
if pre > 0 {
|
|
post, err = unix.Getxattr(path, xattr, dest)
|
|
if err != nil || post < 0 {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if post != pre {
|
|
return nil, e1
|
|
}
|
|
|
|
xattrs[xattr] = string(dest)
|
|
}
|
|
|
|
return xattrs, nil
|
|
}
|
|
|
|
var ObjectFound = fmt.Errorf("Found requested object")
|
|
|
|
func LookupUUIDByBlockDevPath(diskDevice string) (string, error) {
|
|
uuid := ""
|
|
readUUID := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if (info.Mode() & os.ModeSymlink) == os.ModeSymlink {
|
|
link, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// filepath.Join() will call Clean() on the result and
|
|
// thus resolve those ugly "../../" parts that make it
|
|
// hard to compare the strings.
|
|
absPath := filepath.Join("/dev/disk/by-uuid", link)
|
|
if absPath == diskDevice {
|
|
uuid = path
|
|
// Will allows us to avoid needlessly travers
|
|
// the whole directory.
|
|
return ObjectFound
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err := filepath.Walk("/dev/disk/by-uuid", readUUID)
|
|
if err != nil && err != ObjectFound {
|
|
return "", fmt.Errorf("Failed to detect UUID: %s", err)
|
|
}
|
|
|
|
if uuid == "" {
|
|
return "", fmt.Errorf("Failed to detect UUID")
|
|
}
|
|
|
|
lastSlash := strings.LastIndex(uuid, "/")
|
|
return uuid[lastSlash+1:], nil
|
|
}
|
|
|
|
// Detect whether err is an errno.
|
|
func GetErrno(err error) (errno error, iserrno bool) {
|
|
sysErr, ok := err.(*os.SyscallError)
|
|
if ok {
|
|
return sysErr.Err, true
|
|
}
|
|
|
|
pathErr, ok := err.(*os.PathError)
|
|
if ok {
|
|
return pathErr.Err, true
|
|
}
|
|
|
|
tmpErrno, ok := err.(unix.Errno)
|
|
if ok {
|
|
return tmpErrno, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// Utsname returns the same info as unix.Utsname, as strings
|
|
type Utsname struct {
|
|
Sysname string
|
|
Nodename string
|
|
Release string
|
|
Version string
|
|
Machine string
|
|
Domainname string
|
|
}
|
|
|
|
// Uname returns Utsname as strings
|
|
func Uname() (*Utsname, error) {
|
|
/*
|
|
* Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
|
|
* there is really no better way to do this, which is
|
|
* unfortunate. Also, we ditch the more accepted CharsToString
|
|
* version in that thread, since it doesn't seem as portable,
|
|
* viz. github issue #206.
|
|
*/
|
|
|
|
uname := unix.Utsname{}
|
|
err := unix.Uname(&uname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Utsname{
|
|
Sysname: intArrayToString(uname.Sysname),
|
|
Nodename: intArrayToString(uname.Nodename),
|
|
Release: intArrayToString(uname.Release),
|
|
Version: intArrayToString(uname.Version),
|
|
Machine: intArrayToString(uname.Machine),
|
|
Domainname: intArrayToString(uname.Domainname),
|
|
}, nil
|
|
}
|
|
|
|
func intArrayToString(arr interface{}) string {
|
|
slice := reflect.ValueOf(arr)
|
|
s := ""
|
|
for i := 0; i < slice.Len(); i++ {
|
|
val := slice.Index(i)
|
|
valInt := int64(-1)
|
|
|
|
switch val.Kind() {
|
|
case reflect.Int:
|
|
case reflect.Int8:
|
|
valInt = int64(val.Int())
|
|
case reflect.Uint:
|
|
case reflect.Uint8:
|
|
valInt = int64(val.Uint())
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if valInt == 0 {
|
|
break
|
|
}
|
|
|
|
s += string(byte(valInt))
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func Statvfs(path string) (*unix.Statfs_t, error) {
|
|
var st unix.Statfs_t
|
|
|
|
err := unix.Statfs(path, &st)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &st, nil
|
|
}
|
|
|
|
func DeviceTotalMemory() (int64, error) {
|
|
// Open /proc/meminfo
|
|
f, err := os.Open("/proc/meminfo")
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Read it line by line
|
|
scan := bufio.NewScanner(f)
|
|
for scan.Scan() {
|
|
line := scan.Text()
|
|
|
|
// We only care about MemTotal
|
|
if !strings.HasPrefix(line, "MemTotal:") {
|
|
continue
|
|
}
|
|
|
|
// Extract the before last (value) and last (unit) fields
|
|
fields := strings.Split(line, " ")
|
|
value := fields[len(fields)-2] + fields[len(fields)-1]
|
|
|
|
// Feed the result to units.ParseByteSizeString to get an int value
|
|
valueBytes, err := units.ParseByteSizeString(value)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return valueBytes, nil
|
|
}
|
|
|
|
return -1, fmt.Errorf("Couldn't find MemTotal")
|
|
}
|