fs
packageAPI reference for the fs
package.
Imports
(17)GetFileDiff
GetFileDiff compares the content of two files and returns the changes
Parameters
Returns
func GetFileDiff(firstFile, secondFile string) (types.FileDiffInfo, error)
{
firstContent, err := os.ReadFile(firstFile)
if err != nil {
return types.FileDiffInfo{}, err
}
secondContent, err := os.ReadFile(secondFile)
if err != nil {
return types.FileDiffInfo{}, err
}
diff := types.FileDiffInfo{}
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(firstContent), string(secondContent), false)
for _, d := range diffs {
switch d.Type {
case diffmatchpatch.DiffInsert:
diff.AddedLines = append(diff.AddedLines, d.Text)
case diffmatchpatch.DiffDelete:
diff.RemovedLines = append(diff.RemovedLines, d.Text)
}
}
return diff, nil
}
Example
diff, err := fs.GetFileDiff("/tmp/batman", "/tmp/robin")
if err != nil {
fmt.Printf("Error getting file diff: %v", err)
return
}
fmt.Printf("Added lines: %v\n", diff.AddedLines)
fmt.Printf("Removed lines: %v\n", diff.RemovedLines)
GetFilesystemInfo
GetFilesystemInfo returns information about the filesystem of a file or
partition by reading from /etc/mtab.
Parameters
Returns
func GetFilesystemInfo(path string) map[string]string
{
info := make(map[string]string)
u := udev.Udev{}
d := u.NewDeviceFromSyspath(filepath.Join("/sys/class/block", filepath.Base(path)))
info["LABEL"] = d.PropertyValue("ID_FS_LABEL")
info["TYPE"] = d.PropertyValue("ID_FS_TYPE")
info["UUID"] = d.PropertyValue("ID_FS_UUID")
info["PARTUUID"] = d.PropertyValue("ID_PART_ENTRY_UUID")
return info
}
GetFileList
GetFileList returns a list of files in the specified directory.
If recursive is true, the function will recursively search for files, if
fullPaths is true, the full path of the file will be returned instead of
the relative path.
Parameters
Returns
func GetFileList(directory string, recursive, fullPaths bool) ([]types.FileInfo, error)
{
var fileList []types.FileInfo
walkFunc := func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !recursive && path != directory {
return fs.SkipDir
}
if d.IsDir() {
return nil
}
info, err := d.Info()
if err != nil {
return err
}
filePath := path
if !fullPaths {
filePath, _ = filepath.Rel(directory, path)
}
permissions := convertPermissions(info.Mode())
fileList = append(fileList, types.FileInfo{
Path: filePath,
ParentPath: filepath.Dir(filePath),
IsDirectory: false,
Size: info.Size(),
Permissions: permissions,
Extension: GetFileExtension(path),
})
return nil
}
if err := filepath.WalkDir(directory, walkFunc); err != nil {
return nil, err
}
return fileList, nil
}
Example
fileList, err := fs.GetFileList("/batmans/cave", true, false)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, file := range fileList {
fmt.Printf("Path: %s\n", file.Path)
fmt.Printf("Size: %d\n", file.Size)
fmt.Printf("Permissions: %s\n", file.Permissions.String())
fmt.Printf("Extension: %s\n", file.Extension)
}
convertPermissions
convertPermissions converts file mode to Permission type
Parameters
Returns
func convertPermissions(mode os.FileMode) types.Permission
{
return types.Permission{
OwnerRead: mode&0400 != 0,
OwnerWrite: mode&0200 != 0,
OwnerExecute: mode&0100 != 0,
GroupRead: mode&0040 != 0,
GroupWrite: mode&0020 != 0,
GroupExecute: mode&0010 != 0,
OthersRead: mode&0004 != 0,
OthersWrite: mode&0002 != 0,
OthersExecute: mode&0001 != 0,
}
}
GetFile
GetFile returns the file info of the specified file.
If fullPath is true, the full path of the file will be returned instead of
the relative path.
Parameters
Returns
func GetFile(filePath string, fullPath bool) (types.FileInfo, error)
{
info, err := os.Stat(filePath)
if err != nil {
return types.FileInfo{}, err
}
permissions := convertPermissions(info.Mode())
file := types.FileInfo{
Path: filePath,
ParentPath: filepath.Dir(filePath),
IsDirectory: info.IsDir(),
Size: info.Size(),
Permissions: permissions,
Extension: GetFileExtension(filePath),
}
if !fullPath {
file.Path, _ = filepath.Rel(".", filePath)
}
return file, nil
}
Example
file, err := fs.GetFile("/batmans/cave/batmobile.txt", false)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Path: %s\n", file.Path)
fmt.Printf("Size: %d\n", file.Size)
fmt.Printf("Permissions: %s\n", file.Permissions.String())
GetFileExtension
GetFileExtension returns the extension of the given file
Parameters
Returns
func GetFileExtension(filePath string) string
{
ext := filepath.Ext(filePath)
if ext != "" {
return strings.TrimPrefix(ext, ".")
}
return ""
}
Example
extension := fs.GetFileExtension("/batmans/cave/batmobile.txt")
fmt.Printf("Extension: %s\n", extension)
IsFile
IsFile checks whether the given path is a regular file
Parameters
Returns
func IsFile(filePath string) bool
{
info, err := os.Stat(filePath)
if err != nil {
return false
}
return info.Mode().IsRegular()
}
Example
if fs.IsFile("/batmans/cave/batmobile.txt") {
fmt.Println("It's a file!")
}
IsDirectory
IsDirectory checks whether the given path is a directory
Parameters
Returns
func IsDirectory(dirPath string) bool
{
info, err := os.Stat(dirPath)
if err != nil {
return false
}
return info.Mode().IsDir()
}
Example
if fs.IsDirectory("/batmans/cave") {
fmt.Println("It's a directory!")
}
GetFileSize
GetFileSize returns the size of the specified file in bytes
Parameters
Returns
func GetFileSize(filePath string) int64
{
info, err := os.Stat(filePath)
if err != nil {
return 0
}
return info.Size()
}
Example
size := fs.GetFileSize("/batmans/cave/batmobile.txt")
fmt.Printf("Size: %d\n", size)
WriteFileContent
WriteFileContent writes content to the specified file
Parameters
Returns
func WriteFileContent(filePath, content string) error
{
err := os.WriteFile(filePath, []byte(content), 0644)
if err != nil {
return err
}
return nil
}
Example
err := fs.WriteFileContent("/tmp/batman", "I'm Batman!")
if err != nil {
fmt.Printf("Error writing file content: %v", err)
return
}
FileExists
FileExists checks if a file exists
Parameters
Returns
func FileExists(filePath string) bool
{
_, err := os.Stat(filePath)
return err == nil
}
Example
if fs.FileExists("/tmp/batman") {
fmt.Println("The file exists!")
}
DirectoryExists
DirectoryExists checks if a directory exists
Parameters
Returns
func DirectoryExists(directoryPath string) bool
{
fileInfo, err := os.Stat(directoryPath)
return err == nil && fileInfo.IsDir()
}
Example
if fs.DirectoryExists("/tmp/batman") {
fmt.Println("The directory exists!")
}
ListDirectories
ListDirectories returns a list of directories in the specified directory
Parameters
Returns
func ListDirectories(directoryPath string) ([]string, error)
{
var directories []string
files, err := os.ReadDir(directoryPath)
if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() {
directories = append(directories, file.Name())
}
}
return directories, nil
}
Example
directories, err := fs.ListDirectories("/tmp")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, directory := range directories {
fmt.Printf("Directory: %s\n", directory)
}
MountInfo
MountInfo is not supported on non-Linux platforms.
type MountInfo struct
GetMountInfo
Returns
func GetMountInfo() ([]MountInfo, error)
{
return nil, fmt.Errorf("mountinfo is only supported on Linux")
}
GetMountpoint
Parameters
Returns
func GetMountpoint(source string) (string, error)
{
return "", fmt.Errorf("mountinfo is only supported on Linux")
}
GetDiskList
GetDiskList returns a list of disks on the system
Returns
func GetDiskList() ([]types.DiskInfo, error)
{
var disks []types.DiskInfo
files, err := os.ReadDir("/sys/class/block")
if err != nil {
return nil, err
}
diskMap := make(map[string]types.DiskInfo)
for _, file := range files {
name := file.Name()
if isPartition(name) {
continue
}
// Skip non-disk entries
if strings.HasPrefix(name, "loop") ||
strings.HasPrefix(name, "ram") ||
strings.HasPrefix(name, "zram") {
continue
}
diskPath := filepath.Join("/dev", name)
info, err := GetDiskInfo(diskPath)
if err != nil {
return nil, err
}
partitions, err := GetPartitionList(diskPath)
if err != nil {
return nil, err
}
diskInfo := types.DiskInfo{
BaseInfo: info,
Partitions: partitions,
}
diskMap[diskPath] = diskInfo
}
for _, disk := range diskMap {
disks = append(disks, disk)
}
return disks, nil
}
Example
disks, err := fs.GetDiskList()
if err != nil {
fmt.Printf("Error getting disk list: %v", err)
return
}
for _, disk := range disks {
fmt.Printf("Disk: %s\n", disk.Path)
fmt.Printf("Size: %d\n", disk.Size)
fmt.Printf("HumanSize: %s\n", disk.HumanSize)
for _, partition := range disk.Partitions {
fmt.Printf("Partition: %s\n", partition.Path)
fmt.Printf("Size: %d\n", partition.Size)
fmt.Printf("HumanSize: %s\n", partition.HumanSize)
fmt.Printf("Filesystem: %s\n", partition.Filesystem)
fmt.Printf("Mountpoint: %s\n", partition.Mountpoint)
fmt.Printf("Label: %s\n", partition.Label)
fmt.Printf("UUID: %s\n", partition.UUID)
fmt.Printf("PARTUUID: %s\n", partition.PARTUUID)
fmt.Printf("Flags: %v\n", partition.Flags)
}
}
GetPartitionList
GetPartitionList returns a list of disk partitions on the specified disk
Parameters
Returns
func GetPartitionList(diskPath string) ([]types.PartitionInfo, error)
{
var partitions []types.PartitionInfo
files, err := os.ReadDir(filepath.Join("/sys/class/block", filepath.Base(diskPath)))
if err != nil {
return nil, err
}
for _, file := range files {
name := file.Name()
// Skip non-partition entries
if !strings.HasPrefix(name, filepath.Base(diskPath)) {
continue
}
partitionPath := filepath.Join("/dev", name)
info, err := GetDiskInfo(partitionPath)
if err != nil {
return nil, err
}
partitionInfo := types.PartitionInfo{
BaseInfo: info,
}
partitions = append(partitions, partitionInfo)
}
return partitions, nil
}
Example
partitions, err := fs.GetPartitionList("/dev/sda")
if err != nil {
fmt.Printf("Error getting partition list: %v", err)
return
}
for _, partition := range partitions {
fmt.Printf("Partition: %s\n", partition.Path)
fmt.Printf("Size: %d\n", partition.Size)
fmt.Printf("HumanSize: %s\n", partition.HumanSize)
}
GetDiskInfo
GetDiskInfo returns information about a specific disk partition
Parameters
Returns
func GetDiskInfo(partitionPath string) (types.BaseInfo, error)
{
info := types.BaseInfo{
Path: partitionPath,
}
sizePath := filepath.Join("/sys/class/block", filepath.Base(partitionPath), "size")
size, err := os.ReadFile(sizePath)
if err != nil {
return info, err
}
sectorSize := 512
info.Size = int64(sectorSize) * int64(parseUint64(strings.TrimSpace(string(size))))
info.HumanSize = GetHumanSize(info.Size)
fsInfo := GetFilesystemInfo(partitionPath)
info.Filesystem = fsInfo["TYPE"]
info.Label = fsInfo["LABEL"]
info.UUID = fsInfo["UUID"]
info.PARTUUID = fsInfo["PARTUUID"]
if mp, err := GetMountpoint(partitionPath); err == nil {
info.Mountpoint = mp
}
return info, nil
}
Example
info, err := fs.GetDiskInfo("/dev/sda1")
if err != nil {
fmt.Printf("Error getting partition info: %v", err)
return
}
fmt.Printf("Path: %s\n", info.Path)
fmt.Printf("Size: %d\n", info.Size)
fmt.Printf("HumanSize: %s\n", info.HumanSize)
parseUint64
parseUint64 parses a string into a uint64 or returns 0 if parsing fails
Parameters
Returns
func parseUint64(s string) uint64
{
value, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0
}
return value
}
GetHumanSize
GetHumanSize converts the size from bytes to a human-readable format.
For example, 1024 bytes would be converted to “1 kB”.
Parameters
Returns
func GetHumanSize(size int64) string
{
const (
kB = 1024.0
mB = kB * 1024.0
gB = mB * 1024.0
tB = gB * 1024.0
)
sizeFloat := float64(size)
switch {
case size < int64(kB):
return fmt.Sprintf("%d B", size)
case size < int64(mB):
return fmt.Sprintf("%.2f kB", sizeFloat/kB)
case size < int64(gB):
return fmt.Sprintf("%.2f MB", sizeFloat/mB)
case size < int64(tB):
return fmt.Sprintf("%.2f GB", sizeFloat/gB)
default:
return fmt.Sprintf("%.2f TB", sizeFloat/tB)
}
}
Example
fmt.Println(GetHumanSize(1024)) // 1 kB
isPartition
isPartition returns true if the specified block device is a partition
Parameters
Returns
func isPartition(deviceName string) bool
{
path := filepath.Join("/sys/class/block", deviceName, "partition")
info, err := os.Stat(path)
if err == nil && !info.IsDir() {
return true
}
return false
}
MountInfo
MountInfo represents a single entry from /proc/self/mountinfo.
Format reference: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
Fields are kept close to the kernel representation to avoid lossy conversions.
Only a subset is currently exposed as helpers in this package.
type MountInfo struct
Fields
| Name | Type | Description |
|---|---|---|
| MountID | int | |
| ParentID | int | |
| Major | int | |
| Minor | int | |
| Root | string | |
| MountPoint | string | |
| Options | []string | |
| OptionalFields | []string | |
| FSType | string | |
| Source | string | |
| SuperOptions | []string |
parseMountInfo
func parseMountInfo(r io.Reader) ([]MountInfo, error)
{
entries := make([]MountInfo, 0)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
mi, err := parseMountInfoLine(line)
if err != nil {
return nil, err
}
entries = append(entries, mi)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return entries, nil
}
parseMountInfoLine
Parameters
Returns
func parseMountInfoLine(line string) (MountInfo, error)
{
// Split on the separator " - " (space, dash, space)
parts := strings.SplitN(line, " - ", 2)
if len(parts) != 2 {
return MountInfo{}, fmt.Errorf("invalid mountinfo line: missing separator")
}
pre := strings.Fields(parts[0])
if len(pre) < 6 {
return MountInfo{}, fmt.Errorf("invalid mountinfo line: too few fields")
}
post := strings.Fields(parts[1])
if len(post) < 3 {
return MountInfo{}, fmt.Errorf("invalid mountinfo line: too few post-separator fields")
}
mountID, err := strconv.Atoi(pre[0])
if err != nil {
return MountInfo{}, err
}
parentID, err := strconv.Atoi(pre[1])
if err != nil {
return MountInfo{}, err
}
majMin := strings.SplitN(pre[2], ":", 2)
if len(majMin) != 2 {
return MountInfo{}, fmt.Errorf("invalid mountinfo line: invalid major:minor")
}
major, err := strconv.Atoi(majMin[0])
if err != nil {
return MountInfo{}, err
}
minor, err := strconv.Atoi(majMin[1])
if err != nil {
return MountInfo{}, err
}
root := unescapeMountField(pre[3])
mountPoint := unescapeMountField(pre[4])
options := splitComma(pre[5])
optional := make([]string, 0)
if len(pre) > 6 {
for _, f := range pre[6:] {
optional = append(optional, unescapeMountField(f))
}
}
fsType := post[0]
source := unescapeMountField(post[1])
superOptions := splitComma(post[2])
return MountInfo{
MountID: mountID,
ParentID: parentID,
Major: major,
Minor: minor,
Root: root,
MountPoint: mountPoint,
Options: options,
OptionalFields: optional,
FSType: fsType,
Source: source,
SuperOptions: superOptions,
}, nil
}
Uses
splitComma
Parameters
Returns
func splitComma(s string) []string
{
if s == "" {
return nil
}
return strings.Split(s, ",")
}
unescapeMountField
Parameters
Returns
func unescapeMountField(s string) string
{
// mountinfo uses octal escapes for special characters (e.g. \040 for space)
if !strings.Contains(s, "\\") {
return s
}
b := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
if s[i] != '\\' || i+3 >= len(s) {
b = append(b, s[i])
continue
}
o := s[i+1 : i+4]
v, err := strconv.ParseInt(o, 8, 32)
if err != nil {
b = append(b, s[i])
continue
}
b = append(b, byte(v))
i += 3
}
return string(b)
}
GetMountInfo
GetMountInfo returns the current mount table by reading /proc/self/mountinfo.
Returns
func GetMountInfo() ([]MountInfo, error)
{
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()
return parseMountInfo(f)
}
Example
entries, err := fs.GetMountInfo()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, e := range entries {
fmt.Printf("%s -> %s (%s)\n", e.Source, e.MountPoint, e.FSType)
}
GetMountpoint
GetMountpoint returns the mountpoint for a given mount source (e.g. /dev/sda1).
If the source is not mounted, an empty string is returned.
Parameters
Returns
func GetMountpoint(source string) (string, error)
{
entries, err := GetMountInfo()
if err != nil {
return "", err
}
for _, e := range entries {
if e.Source == source {
return e.MountPoint, nil
}
}
return "", nil
}
Example
mp, err := fs.GetMountpoint("/dev/sda1")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Mountpoint: %s\n", mp)
CopyTreeOptions
CopyTreeOptions controls CopyTree behavior.
type CopyTreeOptions struct
Fields
| Name | Type | Description |
|---|---|---|
| Workers | int | |
| PreserveOwnership | bool | |
| PreserveTimestamps | bool | |
| PreservePermissions | bool | |
| AllowSpecial | bool | |
| OnProgress | func(CopyTreeProgress) |
CopyTreeProgress
CopyTreeProgress is emitted via CopyTreeOptions.OnProgress when set.
type CopyTreeProgress struct
Fields
| Name | Type | Description |
|---|---|---|
| SourcePath | string | |
| DestinationPath | string | |
| BytesCopied | int64 |
dirMeta
type dirMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| path | string | |
| mode | os.FileMode | |
| uid | int | |
| gid | int | |
| at | time.Time | |
| mt | time.Time |
CopyTree
CopyTree copies a directory tree from source to destination.
Parameters
Returns
func CopyTree(source, destination string, opts CopyTreeOptions) error
{
if opts.Workers <= 0 {
opts.Workers = runtime.GOMAXPROCS(0)
if opts.Workers < 1 {
opts.Workers = 1
}
}
if !opts.PreservePermissions {
opts.PreservePermissions = true
}
if !opts.PreserveOwnership {
opts.PreserveOwnership = true
}
if !opts.PreserveTimestamps {
opts.PreserveTimestamps = true
}
if err := os.MkdirAll(destination, 0o755); err != nil {
return err
}
jobs := make(chan fileJob, opts.Workers*2)
var wg sync.WaitGroup
var firstErr error
var errMu sync.Mutex
setErr := func(err error) {
errMu.Lock()
defer errMu.Unlock()
if firstErr == nil {
firstErr = err
}
}
progress := func(p CopyTreeProgress) {
if opts.OnProgress != nil {
opts.OnProgress(p)
}
}
for i := 0; i < opts.Workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := range jobs {
if err := copyRegularFile(j.src, j.dst, j.mode, j.uid, j.gid, j.at, j.mt, opts, progress); err != nil {
setErr(err)
}
}
}()
}
dirs := make([]dirMeta, 0)
walkErr := filepath.WalkDir(source, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(source, path)
if err != nil {
return err
}
if rel == "." {
return nil
}
dst := filepath.Join(destination, rel)
info, err := d.Info()
if err != nil {
return err
}
mode := info.Mode()
uid, gid, at, mt := extractUnixMeta(info)
switch {
case mode.IsDir():
if err := os.MkdirAll(dst, 0o755); err != nil {
return err
}
dirs = append(dirs, dirMeta{path: dst, mode: mode, uid: uid, gid: gid, at: at, mt: mt})
return nil
case mode&os.ModeSymlink != 0:
link, err := os.Readlink(path)
if err != nil {
return err
}
if err := os.Symlink(link, dst); err != nil {
if errors.Is(err, os.ErrExist) {
_ = os.Remove(dst)
return os.Symlink(link, dst)
}
return err
}
if opts.PreserveOwnership {
_ = os.Lchown(dst, uid, gid)
}
return nil
case mode.IsRegular():
jobs <- fileJob{src: path, dst: dst, mode: mode, uid: uid, gid: gid, at: at, mt: mt}
return nil
default:
if opts.AllowSpecial {
// Special files support can be added later via mknod.
return nil
}
return fmt.Errorf("unsupported file type: %s", path)
}
})
close(jobs)
wg.Wait()
if walkErr != nil {
return walkErr
}
if firstErr != nil {
return firstErr
}
// Apply dir metadata bottom-up to keep directory mtimes stable.
for i := len(dirs) - 1; i >= 0; i-- {
dm := dirs[i]
if opts.PreservePermissions {
_ = os.Chmod(dm.path, dm.mode)
}
if opts.PreserveOwnership {
_ = os.Chown(dm.path, dm.uid, dm.gid)
}
if opts.PreserveTimestamps {
_ = os.Chtimes(dm.path, dm.at, dm.mt)
}
}
return nil
}
Example
err := fs.CopyTree("/source", "/destination", fs.CopyTreeOptions{Workers: 4})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
Notes
- It is streaming (does not load whole files into memory).
- It preserves symlinks (does not follow them).
- It preserves permissions/ownership/timestamps when requested.
Uses
fileJob
type fileJob struct
Fields
| Name | Type | Description |
|---|---|---|
| src | string | |
| dst | string | |
| mode | os.FileMode | |
| uid | int | |
| gid | int | |
| at | time.Time | |
| mt | time.Time |
copyRegularFile
Parameters
Returns
func copyRegularFile(src, dst string, mode os.FileMode, uid, gid int, at, mt time.Time, opts CopyTreeOptions, progress func(CopyTreeProgress)) error
{
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return err
}
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600)
if err != nil {
return err
}
buf := make([]byte, 1024*1024)
n, copyErr := io.CopyBuffer(out, in, buf)
closeErr := out.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}
if opts.PreservePermissions {
_ = os.Chmod(dst, mode)
}
if opts.PreserveOwnership {
_ = os.Chown(dst, uid, gid)
}
if opts.PreserveTimestamps {
_ = os.Chtimes(dst, at, mt)
}
progress(CopyTreeProgress{SourcePath: src, DestinationPath: dst, BytesCopied: n})
return nil
}
Uses
extractUnixMeta
Parameters
func extractUnixMeta(info os.FileInfo) (uid, gid int, at, mt time.Time)
{
mt = info.ModTime()
at = mt
st, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return 0, 0, at, mt
}
uid = int(st.Uid)
gid = int(st.Gid)
at = time.Unix(int64(st.Atim.Sec), int64(st.Atim.Nsec))
mt = time.Unix(int64(st.Mtim.Sec), int64(st.Mtim.Nsec))
return uid, gid, at, mt
}
resolveDiskSymlink
Parameters
Returns
func resolveDiskSymlink(dir, value string) (string, error)
{
p := filepath.Join(dir, value)
_, err := os.Lstat(p)
if err != nil {
return "", err
}
resolved, err := filepath.EvalSymlinks(p)
if err != nil {
return "", err
}
return resolved, nil
}
GetDeviceByUUID
GetDeviceByUUID resolves /dev/disk/by-uuid/
Parameters
Returns
func GetDeviceByUUID(uuid string) (string, error)
{
return resolveDiskSymlink("/dev/disk/by-uuid", uuid)
}
Example
dev, err := fs.GetDeviceByUUID("5a1b2c3d-4e5f-6789-abcd-ef0123456789")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Device: %s\n", dev)
GetDeviceByPARTUUID
GetDeviceByPARTUUID resolves /dev/disk/by-partuuid/
Parameters
Returns
func GetDeviceByPARTUUID(partuuid string) (string, error)
{
return resolveDiskSymlink("/dev/disk/by-partuuid", partuuid)
}
Example
dev, err := fs.GetDeviceByPARTUUID("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Device: %s\n", dev)
GetDeviceByLabel
GetDeviceByLabel resolves /dev/disk/by-label/
Parameters
Returns
func GetDeviceByLabel(label string) (string, error)
{
return resolveDiskSymlink("/dev/disk/by-label", label)
}
Example
dev, err := fs.GetDeviceByLabel("VANILLA")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Device: %s\n", dev)
IsRemovableDevice
IsRemovableDevice checks if a block device is marked as removable via sysfs.
Parameters
Returns
func IsRemovableDevice(devicePath string) (bool, error)
{
name := filepath.Base(devicePath)
b, err := os.ReadFile(filepath.Join("/sys/class/block", name, "removable"))
if err != nil {
return false, err
}
return strings.TrimSpace(string(b)) == "1", nil
}
Example
rem, err := fs.IsRemovableDevice("/dev/sda")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Removable: %v\n", rem)
GetDeviceSysPath
GetDeviceSysPath returns the sysfs path for a /dev/* device.
Parameters
Returns
func GetDeviceSysPath(devicePath string) (string, error)
{
name := filepath.Base(devicePath)
p := filepath.Join("/sys/class/block", name)
if _, err := os.Stat(p); err != nil {
return "", fmt.Errorf("device not found in sysfs: %w", err)
}
return p, nil
}
Example
sp, err := fs.GetDeviceSysPath("/dev/sda")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sysfs path: %s\n", sp)
GetFilesystemInfo
GetFilesystemInfo returns information about the filesystem of a file or
partition by reading from /etc/mtab.
Parameters
Returns
func GetFilesystemInfo(path string) map[string]string
{
return map[string]string{
"LABEL": "",
"TYPE": "",
"UUID": "",
"PARTUUID": "",
}
}
CopyFile
CopyFile copies the contents of one file to another
Parameters
Returns
func CopyFile(sourcePath, destinationPath string) error
{
source, err := os.ReadFile(sourcePath)
if err != nil {
return err
}
err = os.WriteFile(destinationPath, source, 0644)
if err != nil {
return err
}
return nil
}
Example
err := fs.CopyFile("/tmp/batman", "/tmp/robin")
if err != nil {
fmt.Printf("Error copying file: %v", err)
return
}
MoveFile
MoveFile moves a file from one location to another
Parameters
Returns
func MoveFile(sourcePath, destinationPath string) error
{
err := os.Rename(sourcePath, destinationPath)
if err != nil {
return err
}
return nil
}
Example
err := fs.MoveFile("/tmp/batman", "/tmp/robin")
if err != nil {
fmt.Printf("Error moving file: %v", err)
return
}
DeleteFile
DeleteFile deletes a file
Parameters
Returns
func DeleteFile(filePath string) error
{
err := os.Remove(filePath)
if err != nil {
return err
}
return nil
}
Example
err := fs.DeleteFile("/tmp/batman")
if err != nil {
fmt.Printf("Error deleting file: %v", err)
return
}
CreateDirectory
CreateDirectory creates a new directory
Parameters
Returns
func CreateDirectory(directoryPath string) error
{
err := os.Mkdir(directoryPath, 0755)
if err != nil {
return err
}
return nil
}
Example
err := fs.CreateDirectory("/tmp/batman")
if err != nil {
fmt.Printf("Error creating directory: %v", err)
return
}
DeleteDirectory
DeleteDirectory deletes a directory and its contents
Parameters
Returns
func DeleteDirectory(directoryPath string) error
{
err := os.RemoveAll(directoryPath)
if err != nil {
return err
}
return nil
}
Example
err := fs.DeleteDirectory("/tmp/batman")
if err != nil {
fmt.Printf("Error deleting directory: %v", err)
return
}
IsMounted
IsMounted checks if the given source path is mounted in the given
destination path. It does so by reading the /proc/mounts file.
Parameters
Returns
func IsMounted(source string, destination string) (bool, error)
{
entries, err := GetMountInfo()
if err != nil {
return false, err
}
for _, e := range entries {
if e.Source == source && e.MountPoint == destination {
return true, nil
}
}
return false, nil
}
Example
mounted, err := fs.IsMounted("tmpfs", "/tmp")
if err != nil {
fmt.Printf("Error checking if /tmp is mounted: %v", err)
return
}
fmt.Printf("/tmp is mounted: %v", mounted)
Mount
Mount mounts the given source path in the given destination path. It also
creates the destination path if it does not exist. An error is returned if
the source path does not exist.
Notes (for developers):
Avoid mapping the fsType into a custom type, as it would require further
maintenance once a new filesystem type is added to Vanilla OS.
Parameters
Returns
func Mount(source, destination, fsType, data string, mode uintptr) error
{
info, err := os.Stat(source)
if err != nil {
return err
}
if info.IsDir() {
_ = os.MkdirAll(destination, 0o755)
} else {
file, _ := os.Create(destination)
defer func() { _ = file.Close() }()
}
return syscall.Mount(source, destination, fsType, mode, data)
}
Example
err := fs.Mount("/dev/sda1", "/mnt", "ext4", "", syscall.MS_RDONLY)
if err != nil {
fmt.Printf("Error mounting /dev/sda1: %v", err)
return
}
MountBind
MountBind mounts bind the given source path in the given destination path.
Parameters
Returns
func MountBind(src, dest string) error
{
return Mount(
src,
dest,
"bind",
"",
syscall.MS_BIND|
syscall.MS_REC|
syscall.MS_RDONLY|
syscall.MS_NOSUID|
syscall.MS_NOEXEC|
syscall.MS_NODEV|
syscall.MS_PRIVATE,
)
}
Example
err := fs.MountBind("/bruce", "/batman")
if err != nil {
fmt.Printf("Error bind mounting /batman: %v", err)
return
}
Notes
This is just a wrapper of the Mount function, for convenience.
MountOverlay
MountOverlay mounts the given lower, upper and work directories in the
given destination path as an overlay filesystem.
Parameters
Returns
func MountOverlay(lowerDir, upperDir, workDir string) error
{
return Mount(
"overlay",
lowerDir,
"overlay",
fmt.Sprintf(
"lowerdir=%s,upperdir=%s,workdir=%s,userxattr",
lowerDir, upperDir, workDir,
),
0,
)
}
Example
err := fs.MountOverlay("/batman/lower", "/batman/upper", "/batman/work")
if err != nil {
fmt.Printf("Error overlay mounting /batman: %v", err)
return
}
Notes
This is just a wrapper of the Mount function, for convenience.
MountFuseOverlay
MountFuseOverlay mounts the given lower, upper and work directories in the
given destination path as an overlay filesystem using fuse-overlayfs.
Parameters
Returns
func MountFuseOverlay(targetDir, lowerDir, upperDir, workDir string) error
{
// Note: implemented via kernel overlayfs to avoid spawning external commands.
return Mount(
"overlay",
targetDir,
"overlay",
fmt.Sprintf(
"lowerdir=%s,upperdir=%s,workdir=%s,userxattr",
lowerDir, upperDir, workDir,
),
0,
)
}
Example
err := fs.MountFuseOverlay("/batman", "/batman/lower", "/batman/upper", "/batman/work")
if err != nil {
fmt.Printf("Error fuse-overlayfs mounting /batman: %v", err)
return
}
Notes
This implementation uses the fuse-overlayfs command-line tool, if that
is not available in the system, this function will return an error.
Unmount
Unmount unmounts the given path. An error is returned if the path is not
mounted.
Parameters
Returns
func Unmount(target string) error
{
return syscall.Unmount(target, 0)
}
Example
err := fs.Unmount("/mnt")
if err != nil {
fmt.Printf("Error unmounting /mnt: %v", err)
return
}
UnmountFuseOverlay
UnmountFuseOverlay unmounts the given path using the fuse-overlayfs command-
line tool. An error is returned if the path is not mounted.
Parameters
Returns
func UnmountFuseOverlay(targetDir string) error
{
return Unmount(targetDir)
}
Example
err := fs.UnmountFuseOverlay("/batman")
if err != nil {
fmt.Printf("Error fuse-overlayfs unmounting /batman: %v", err)
return
}
Notes
This implementation uses the fuse-overlayfs command-line tool, if that
is not available in the system, this function will return an error.
AtomicSwap
AtomicSwap atomically swaps two files or directories
Parameters
Returns
func AtomicSwap(sourcePath, destinationPath string) error
{
orig, err := os.Open(sourcePath)
if err != nil {
return err
}
newfile, err := os.Open(destinationPath)
if err != nil {
return err
}
err = unix.Renameat2(int(orig.Fd()), sourcePath, int(newfile.Fd()), destinationPath, unix.RENAME_EXCHANGE)
if err != nil {
return err
}
return nil
}
Example
err := fs.AtomicSwap("/tmp/file1", "/tmp/file2")
if err != nil {
fmt.Printf("Error swapping files: %v", err)
return
}
GetFreeSpaceBytes
GetFreeSpaceBytes returns the available free space for the filesystem hosting path.
Parameters
Returns
func GetFreeSpaceBytes(path string) (uint64, error)
{
var st syscall.Statfs_t
err := syscall.Statfs(path, &st)
if err != nil {
return 0, err
}
return st.Bavail * uint64(st.Bsize), nil
}
Example
free, err := fs.GetFreeSpaceBytes("/")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Free bytes: %d\n", free)
GetTotalSpaceBytes
GetTotalSpaceBytes returns the total size for the filesystem hosting path.
Parameters
Returns
func GetTotalSpaceBytes(path string) (uint64, error)
{
var st syscall.Statfs_t
err := syscall.Statfs(path, &st)
if err != nil {
return 0, err
}
return st.Blocks * uint64(st.Bsize), nil
}
Example
total, err := fs.GetTotalSpaceBytes("/")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Total bytes: %d\n", total)
IsWritableDir
IsWritableDir checks if dir is writable by attempting to create a temporary file.
Parameters
Returns
func IsWritableDir(dir string) (bool, error)
{
testPath := filepath.Join(dir, ".vos-sdk-writable-check")
f, err := os.OpenFile(testPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if err != nil {
if os.IsPermission(err) {
return false, nil
}
return false, err
}
_ = f.Close()
_ = os.Remove(testPath)
return true, nil
}
Example
writable, err := fs.IsWritableDir("/tmp")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Writable: %v\n", writable)
extractUnixMeta
Parameters
func extractUnixMeta(info os.FileInfo) (uid, gid int, at, mt time.Time)
{
mt = info.ModTime()
at = mt
return 0, 0, at, mt
}