azure-storage-fuse/common/util.go

419 строки
10 KiB
Go

/*
_____ _____ _____ ____ ______ _____ ------
| | | | | | | | | | | | |
| | | | | | | | | | | | |
| --- | | | | |-----| |---- | | |-----| |----- ------
| | | | | | | | | | | | |
| ____| |_____ | ____| | ____| | |_____| _____| |_____ |_____
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright © 2020-2024 Microsoft Corporation. All rights reserved.
Author : <blobfusedev@microsoft.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/
package common
import (
"bufio"
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"gopkg.in/ini.v1"
)
var RootMount bool
var ForegroundMount bool
// IsDirectoryMounted is a utility function that returns true if the directory is already mounted using fuse
func IsDirectoryMounted(path string) bool {
mntList, err := os.ReadFile("/etc/mtab")
if err != nil {
//fmt.Println("failed to read mount points : ", err.Error())
return false
}
// removing trailing / from the path
path = strings.TrimRight(path, "/")
for _, line := range strings.Split(string(mntList), "\n") {
if strings.TrimSpace(line) != "" {
mntPoint := strings.Split(line, " ")[1]
if path == mntPoint {
// with earlier fuse driver ' fuse.' was searched in /etc/mtab
// however with libfuse entry does not have that signature
// if this path is already mounted using fuse then fail
if strings.Contains(line, "fuse") {
//fmt.Println(path, " is already mounted.")
return true
}
}
}
}
return false
}
// IsDirectoryEmpty is a utility function that returns true if the directory at that path is empty or not
func IsDirectoryEmpty(path string) bool {
f, _ := os.Open(path)
defer f.Close()
_, err := f.Readdirnames(1)
if err == io.EOF {
return true
}
if err != nil && err.Error() == "invalid argument" {
fmt.Println("Broken Mount : First Unmount ", path)
}
return false
}
// DirectoryExists is a utility function that returns true if the directory at that path exists and returns false if it does not exist.
func DirectoryExists(path string) bool {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false
} else if err != nil {
return false
}
return true
}
// GetCurrentUser is a utility function that returns the UID and GID of the user that invokes the blobfuse2 command.
func GetCurrentUser() (uint32, uint32, error) {
var (
currentUser *user.User
userUID, userGID uint64
)
currentUser, err := user.Current()
if err != nil {
return 0, 0, err
}
userUID, err = strconv.ParseUint(currentUser.Uid, 10, 32)
if err != nil {
return 0, 0, err
}
userGID, err = strconv.ParseUint(currentUser.Gid, 10, 32)
if err != nil {
return 0, 0, err
}
if currentUser.Name == "root" || userUID == 0 {
RootMount = true
} else {
RootMount = false
}
return uint32(userUID), uint32(userGID), nil
}
// normalizeObjectName : If file contains \\ in name replace it with ..
func NormalizeObjectName(name string) string {
return strings.ReplaceAll(name, "\\", "/")
}
// List all mount points which were mounted using blobfuse2
func ListMountPoints() ([]string, error) {
file, err := os.Open("/etc/mtab")
if err != nil {
return nil, err
}
defer file.Close()
// Read /etc/mtab file line by line
var mntList []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// If there is any directory mounted using blobfuse2 its of our interest
if strings.HasPrefix(line, "blobfuse2") {
// Extract the mount path from this line
mntPath := strings.Split(line, " ")[1]
mntList = append(mntList, mntPath)
}
}
return mntList, nil
}
// Encrypt given data using the key provided
func EncryptData(plainData []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, plainData, nil)
return ciphertext, nil
}
// Decrypt given data using the key provided
func DecryptData(cipherData []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := cipherData[:gcm.NonceSize()]
ciphertext := cipherData[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
func GetCurrentDistro() string {
cfg, err := ini.Load("/etc/os-release")
if err != nil {
return ""
}
distro := cfg.Section("").Key("PRETTY_NAME").String()
return distro
}
type BitMap16 uint16
// IsSet : Check whether the given bit is set or not
func (bm BitMap16) IsSet(bit uint16) bool { return (bm & (1 << bit)) != 0 }
// Set : Set the given bit in bitmap
func (bm *BitMap16) Set(bit uint16) { *bm |= (1 << bit) }
// Clear : Clear the given bit from bitmap
func (bm *BitMap16) Clear(bit uint16) { *bm &= ^(1 << bit) }
// Reset : Reset the whole bitmap by setting it to 0
func (bm *BitMap16) Reset() { *bm = 0 }
type KeyedMutex struct {
mutexes sync.Map // Zero value is empty and ready for use
}
func (m *KeyedMutex) GetLock(key string) *sync.Mutex {
value, _ := m.mutexes.LoadOrStore(key, &sync.Mutex{})
mtx := value.(*sync.Mutex)
return mtx
}
// check if health monitor is enabled and blofuse stats monitor is not disabled
func MonitorBfs() bool {
return EnableMonitoring && !BfsDisabled
}
// convert ~ to $HOME in path
func ExpandPath(path string) string {
if path == "" {
return path
}
if strings.HasPrefix(path, "~/") {
homeDir, err := os.UserHomeDir()
if err != nil {
return path
}
path = filepath.Join(homeDir, path[2:])
}
path = os.ExpandEnv(path)
path, _ = filepath.Abs(path)
return path
}
// NotifyMountToParent : Send a signal to parent process about successful mount
func NotifyMountToParent() error {
if !ForegroundMount {
ppid := syscall.Getppid()
if ppid > 1 {
if err := syscall.Kill(ppid, syscall.SIGUSR2); err != nil {
return err
}
} else {
return fmt.Errorf("failed to get parent pid, received : %v", ppid)
}
}
return nil
}
var duPath []string = []string{"/usr/bin/du", "/usr/local/bin/du", "/usr/sbin/du", "/usr/local/sbin/du", "/sbin/du", "/bin/du"}
var selectedDuPath string = ""
// GetUsage: The current disk usage in MB
func GetUsage(path string) (float64, error) {
var currSize float64
var out bytes.Buffer
if selectedDuPath == "" {
selectedDuPath = "-"
for _, dup := range duPath {
_, err := os.Stat(dup)
if err == nil {
selectedDuPath = dup
break
}
}
}
if selectedDuPath == "-" {
return 0, fmt.Errorf("failed to find du")
}
// du - estimates file space usage
// https://man7.org/linux/man-pages/man1/du.1.html
// Note: We cannot just pass -BM as a parameter here since it will result in less accurate estimates of the size of the path
// (i.e. du will round up to 1M if the path is smaller than 1M).
cmd := exec.Command(selectedDuPath, "-sh", path)
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return 0, err
}
size := strings.Split(out.String(), "\t")[0]
if size == "0" {
return 0, nil
}
// some OS's use "," instead of "." that will not work for float parsing - replace it
size = strings.Replace(size, ",", ".", 1)
parsed, err := strconv.ParseFloat(size[:len(size)-1], 64)
if err != nil {
return 0, fmt.Errorf("failed to parse du output")
}
switch size[len(size)-1] {
case 'K':
currSize = parsed / float64(1024)
case 'M':
currSize = parsed
case 'G':
currSize = parsed * 1024
case 'T':
currSize = parsed * 1024 * 1024
}
return currSize, nil
}
var currentUID int = -1
// GetDiskUsageFromStatfs: Current disk usage of temp path
func GetDiskUsageFromStatfs(path string) (float64, float64, error) {
// We need to compute the disk usage percentage for the temp path
var stat syscall.Statfs_t
err := syscall.Statfs(path, &stat)
if err != nil {
return 0, 0, err
}
if currentUID == -1 {
currentUID = os.Getuid()
}
var availableSpace uint64
if currentUID == 0 {
// Sudo has mounted
availableSpace = stat.Bfree * uint64(stat.Frsize)
} else {
// non Sudo has mounted
availableSpace = stat.Bavail * uint64(stat.Frsize)
}
totalSpace := stat.Blocks * uint64(stat.Frsize)
usedSpace := float64(totalSpace - availableSpace)
return usedSpace, float64(usedSpace) / float64(totalSpace) * 100, nil
}
func GetFuseMinorVersion() int {
var out bytes.Buffer
cmd := exec.Command("fusermount3", "--version")
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return 0
}
output := strings.Split(out.String(), ":")
if len(output) < 2 {
return 0
}
version := strings.Trim(output[1], " ")
if version == "" {
return 0
}
output = strings.Split(version, ".")
if len(output) < 2 {
return 0
}
val, err := strconv.Atoi(output[1])
if err != nil {
return 0
}
return val
}