k3s/vendor/github.com/lxc/lxd/shared/util_linux.go
2019-11-10 04:05:59 +00:00

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")
}