azure-storage-fuse/common/util.go

503 строки
12 KiB
Go
Исходник Постоянная ссылка Обычный вид История

2022-02-09 19:42:28 +03:00
/*
_____ _____ _____ ____ ______ _____ ------
| | | | | | | | | | | | |
| | | | | | | | | | | | |
| --- | | | | |-----| |---- | | |-----| |----- ------
| | | | | | | | | | | | |
| ____| |_____ | ____| | ____| | |_____| _____| |_____ |_____
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright © 2020-2024 Microsoft Corporation. All rights reserved.
2022-02-09 19:42:28 +03:00
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"
2022-02-09 19:42:28 +03:00
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"os"
"os/exec"
2022-02-09 19:42:28 +03:00
"os/user"
"path/filepath"
2022-02-09 19:42:28 +03:00
"strconv"
"strings"
"sync"
"syscall"
2022-02-09 19:42:28 +03:00
"gopkg.in/ini.v1"
)
var RootMount bool
var ForegroundMount bool
var IsStream bool
2022-02-09 19:42:28 +03:00
// IsDirectoryMounted is a utility function that returns true if the directory is already mounted using fuse
2022-02-09 19:42:28 +03:00
func IsDirectoryMounted(path string) bool {
mntList, err := os.ReadFile("/etc/mtab")
2022-02-09 19:42:28 +03:00
if err != nil {
//fmt.Println("failed to read mount points : ", err.Error())
return false
}
2022-04-01 08:10:49 +03:00
// removing trailing / from the path
path = strings.TrimRight(path, "/")
2022-02-09 19:42:28 +03:00
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") {
2022-02-09 19:42:28 +03:00
//fmt.Println(path, " is already mounted.")
return true
}
}
}
}
return false
}
func IsMountActive(path string) (bool, error) {
// Get the process details for this path using ps -aux
var out bytes.Buffer
cmd := exec.Command("pidof", "blobfuse2")
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
if err.Error() == "exit status 1" {
return false, nil
} else {
return true, fmt.Errorf("failed to get pid of blobfuse2 [%v]", err.Error())
}
}
// out contains the list of pids of the processes that are running
pidString := strings.Replace(out.String(), "\n", " ", -1)
pids := strings.Split(pidString, " ")
for _, pid := range pids {
// Get the mount path for this pid
// For this we need to check the command line arguments given to this command
// If the path is same then we need to return true
if pid == "" {
continue
}
cmd = exec.Command("ps", "-o", "args=", "-p", pid)
out.Reset()
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return true, fmt.Errorf("failed to get command line arguments for pid %s [%v]", pid, err.Error())
}
if strings.Contains(out.String(), path) {
return true, nil
}
}
return false, nil
}
// IsDirectoryEmpty is a utility function that returns true if the directory at that path is empty or not
2022-02-09 19:42:28 +03:00
func IsDirectoryEmpty(path string) bool {
if !DirectoryExists(path) {
// Directory does not exists so safe to assume its empty
return true
}
2022-02-09 19:42:28 +03:00
f, _ := os.Open(path)
defer f.Close()
_, err := f.Readdirnames(1)
// If there is nothing in the directory then it is empty
return err == io.EOF
}
func TempCacheCleanup(path string) error {
if !IsDirectoryEmpty(path) {
// List the first level children of the directory
dirents, err := os.ReadDir(path)
if err != nil {
// Failed to list, return back error
return fmt.Errorf("failed to list directory contents : %s", err.Error())
}
2022-02-09 19:42:28 +03:00
// Delete all first level children with their hierarchy
for _, entry := range dirents {
os.RemoveAll(filepath.Join(path, entry.Name()))
}
2022-02-09 19:42:28 +03:00
}
return nil
2022-02-09 19:42:28 +03:00
}
// DirectoryExists is a utility function that returns true if the directory at that path exists and returns false if it does not exist.
2022-02-09 19:42:28 +03:00
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.
2022-02-09 19:42:28 +03:00
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
}
2022-02-09 19:42:28 +03:00
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
}
Perf and Memory Optimizations (#704) * First commit of blobfuse v2 code * Adding hadle optimization for v2 * Making direct read a configurable option, disabled by default * Rearrange structures to reduce padding and save memory * Correcting attr-cache UT for flag changes * Merging file-cache-test * Make flag check a function * Adding comments to handle methods * Adding more fuse options for optimization * Adding more fuse options for optimization * Removing exectime from red flows * Correcting profiling code to work for both cpu and memory * Cleanup old pprof reports * Adding error log for policy local file removal case and adding 10mil max file limit in attr-cache * Correcting file-cache UT for notInCache cases * Correcting libfuse UT to get handle object out of open return parameters * Addressing review comments * Removing 7.5 old curl from nightly as it does not make sense for v2 to test different curl versions * Tuning fuse parameters for better perf * Fuse parameter tuning * Removing some fuse options as mount is failing on some platforms * Exit pipeline stage if mount failed * Populate fuse connection flag based on kernel capabilities * Log kernel fuse capabilities and the one chosen by blobfuse on given platform * Fail pipeline if blobfuse2 binary is not found in df output * Wait for some time for mount to go through before validating * Correcting validation of mount point * Correcting validation of mount point * Use fuse2 on Deb-10 as with fuse3 we are not able to mount on that distro * Update mount validation for huge listing * Image signing to happen only when release option is chosen Co-authored-by: Gauri Prasad <gapra@microsoft.com> Co-authored-by: Ubuntu <vibhansa@vibhansa-perftest.l1vcg1e4u55e3gwz1dsfkxy2if.tx.internal.cloudapp.net>
2022-02-19 13:26:43 +03:00
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
}
type WriteToFileOptions struct {
Flags int
Permission os.FileMode
}
func WriteToFile(filename string, data string, options WriteToFileOptions) error {
// Open the file with the provided flags, create it if it doesn't exist
//check if options.Permission is 0 if so then assign 0777
if options.Permission == 0 {
options.Permission = 0777
}
file, err := os.OpenFile(filename, options.Flags|os.O_CREATE|os.O_WRONLY, options.Permission)
if err != nil {
return fmt.Errorf("error opening file: [%s]", err.Error())
}
defer file.Close() // Ensure the file is closed when we're done
// Write the data content to the file
if _, err := file.WriteString(data); err != nil {
return fmt.Errorf("error writing to file [%s]", err.Error())
}
return nil
}