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