syscontainer-tools/libdevice/devices_unix.go
taleintervenor dfaef9d393 syscontainer-tools: update license to Mulan PSL v2
reason: update license to Mulan PSL v2

Signed-off-by: taleintervenor <taleintervenor@aliyun.com>
2020-04-27 14:54:17 +08:00

255 lines
6.1 KiB
Go

// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved.
// syscontainer-tools is licensed under the Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v2 for more details.
// Description: device operation
// Author: zhangwei
// Create: 2018-01-18
// +build linux freebsd
package libdevice
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"isula.org/syscontainer-tools/types"
"github.com/sirupsen/logrus"
)
var (
// ErrNotADevice not a device error
ErrNotADevice = errors.New("not a device")
)
// Testing dependencies
var (
osLstat = os.Lstat
)
// ParseMapping will return a device with mapping segment only.
func ParseMapping(device string) (*types.Device, error) {
var src, dst, permissions string
arr := strings.Split(device, ":")
permissions = "rwm"
// According to the length of device specifications
if len(arr) < 1 || len(arr) > 3 {
return nil, fmt.Errorf("invalid device specification: %s", device)
}
src = arr[0]
if len(arr) == 3 {
if arr[2] != "" {
permissions = arr[2]
}
dst = arr[1]
}
if len(arr) == 2 {
if CheckDeviceMode(arr[1]) {
permissions = arr[1]
} else {
dst = arr[1]
}
}
if !CheckDeviceMode(permissions) {
return nil, fmt.Errorf("invalid permission: %s", permissions)
}
if src != "" {
if !filepath.IsAbs(src) {
return nil, fmt.Errorf("hostpath should be an absolute path: %s", src)
}
src = filepath.Clean(src)
}
if dst != "" {
if !filepath.IsAbs(dst) {
return nil, fmt.Errorf("containerpath should be an absolute path: %s", dst)
}
dst = filepath.Clean(dst)
}
if src == "" && dst == "" {
return nil, fmt.Errorf("either of host path and container path should be assigned")
}
ret := &types.Device{
Path: dst,
PathOnHost: src,
Permissions: permissions,
}
return ret, nil
}
// CheckDeviceMode checks if the mode is ilegal.
func CheckDeviceMode(mode string) bool {
var DeviceMode = map[rune]bool{
'r': true,
'w': true,
'm': true,
}
if mode == "" {
return false
}
for _, md := range mode {
if !DeviceMode[md] {
// Device Mode is ilegal
return false
}
DeviceMode[md] = false
}
return true
}
// ParseDevice parses the device from file path and returns the device structure
func ParseDevice(device string) (*types.Device, error) {
mapDevice, err := ParseMapping(device)
if err != nil {
return nil, err
}
dev, err := DeviceFromPath(mapDevice.PathOnHost, mapDevice.Permissions)
if err != nil {
return nil, err
}
dev.Path = mapDevice.Path
return dev, nil
}
// GetDeviceRealPath get real path of device
func GetDeviceRealPath(path string) string {
resolvedPathOnHost := path
for {
linkedPathOnHost, err := os.Readlink(resolvedPathOnHost)
// regular file will return error
if err != nil {
break
}
base := filepath.Dir(resolvedPathOnHost)
resolvedPathOnHost = linkedPathOnHost
if !filepath.IsAbs(resolvedPathOnHost) {
resolvedPathOnHost = filepath.Join(base, linkedPathOnHost)
}
}
return resolvedPathOnHost
}
// GetDeviceNum get device major and minor number
func GetDeviceNum(path string) (int64, int64, error) {
dev, err := DeviceFromPath(path, "")
if err != nil {
return 0, 0, err
}
return dev.Major, dev.Minor, nil
}
// DeviceFromPath parses the given device path to a device structure
func DeviceFromPath(path, permissions string) (*types.Device, error) {
resolvedPathOnHost := GetDeviceRealPath(path)
fileInfo, err := osLstat(resolvedPathOnHost)
if err != nil {
return nil, err
}
var (
devType string
mode = fileInfo.Mode()
fileModePermissionBits = os.FileMode.Perm(mode)
)
switch {
case mode&os.ModeDevice == 0:
return nil, ErrNotADevice
case mode&os.ModeCharDevice != 0:
fileModePermissionBits |= syscall.S_IFCHR
devType = "c"
default:
fileModePermissionBits |= syscall.S_IFBLK
devType = "b"
}
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return nil, fmt.Errorf("cannot determine the device number for device %s", path)
}
devNumber := int(stat.Rdev)
return &types.Device{
Type: devType,
PathOnHost: path,
Major: Major(devNumber),
Minor: Minor(devNumber),
Permissions: permissions,
FileMode: fileModePermissionBits,
UID: stat.Uid,
GID: stat.Gid,
}, nil
}
// FindSubPartition will find all the sub-partitions for a base device.
func FindSubPartition(device *types.Device) []*types.Device {
var subDevices []*types.Device
cmd := exec.Command("lsblk", "-n", "-p", "-r", "-o", "NAME", device.PathOnHost)
out, err := cmd.CombinedOutput()
if err != nil {
logrus.Errorf("Failed to lsblk %s : %v", string(out), err)
return subDevices
}
rawString := strings.Split(string(out), "\n")
subDevNames := rawString[1 : len(rawString)-1]
for _, devName := range subDevNames {
tryDevice := devName
if device.Path != "" {
tryDevice = tryDevice + ":" + device.Path + string(devName[len(devName)-1])
}
tryDevice = tryDevice + ":" + device.Permissions
dev, err := ParseDevice(tryDevice)
if err != nil {
continue
}
dev.Parent = device.PathOnHost
subDevices = append(subDevices, dev)
}
return subDevices
}
// MknodDevice will create device in container by calling mknod system call
func MknodDevice(dest string, node *types.Device) error {
fileMode := node.FileMode
switch node.Type {
case "c":
fileMode |= syscall.S_IFCHR
case "b":
fileMode |= syscall.S_IFBLK
default:
return fmt.Errorf("%s is not a valid device type for device %s", node.Type, node.Path)
}
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil {
return err
}
return syscall.Chown(dest, int(node.UID), int(node.GID))
}
// SetDefaultPath set default path for device
func SetDefaultPath(dev *types.Device) {
if dev.Path == "" {
dev.Path = dev.PathOnHost
}
if dev.PathOnHost == "" {
dev.PathOnHost = dev.Path
}
}