mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
FS: Add /pkg/fs/duf to determine mount points and disk usage #4266
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
7
go.mod
7
go.mod
@@ -78,6 +78,7 @@ require github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082
|
|||||||
require golang.org/x/text v0.22.0
|
require golang.org/x/text v0.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/IGLOU-EU/go-wildcard v1.0.3
|
||||||
github.com/davidbyttow/govips/v2 v2.16.0
|
github.com/davidbyttow/govips/v2 v2.16.0
|
||||||
github.com/go-co-op/gocron/v2 v2.16.0
|
github.com/go-co-op/gocron/v2 v2.16.0
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
@@ -86,6 +87,7 @@ require (
|
|||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/urfave/cli/v2 v2.27.5
|
github.com/urfave/cli/v2 v2.27.5
|
||||||
github.com/zitadel/oidc/v3 v3.35.0
|
github.com/zitadel/oidc/v3 v3.35.0
|
||||||
|
golang.org/x/sys v0.30.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -130,7 +132,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.6 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/swaggo/swag v1.16.3 // indirect
|
github.com/swaggo/swag v1.16.3 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
@@ -144,7 +146,6 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||||
golang.org/x/oauth2 v0.26.0 // indirect
|
golang.org/x/oauth2 v0.26.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
|
||||||
golang.org/x/tools v0.30.0 // indirect
|
golang.org/x/tools v0.30.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.4 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
@@ -162,7 +163,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/emersion/go-webdav v0.6.0
|
github.com/emersion/go-webdav v0.6.0
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -19,6 +19,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
|||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0=
|
||||||
|
github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
@@ -304,8 +306,8 @@ github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GW
|
|||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
@@ -349,8 +351,8 @@ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkq
|
|||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
// KiloByte, MegaByte, and GigaByte size constants.
|
// Size constants for KByte, MByte, and GByte.
|
||||||
const (
|
const (
|
||||||
KiloByte = 1024
|
KB = 1024
|
||||||
MegaByte = KiloByte * 1024
|
MB = KB * 1024
|
||||||
GigaByte = MegaByte * 1024
|
GB = MB * 1024
|
||||||
)
|
)
|
||||||
|
|||||||
18
pkg/fs/duf/const.go
Normal file
18
pkg/fs/duf/const.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
// Supported device types.
|
||||||
|
const (
|
||||||
|
LocalDevice = "local"
|
||||||
|
NetworkDevice = "network"
|
||||||
|
FuseDevice = "fuse"
|
||||||
|
SpecialDevice = "special"
|
||||||
|
LoopsDevice = "loops"
|
||||||
|
BindsMount = "binds"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Size constants for KByte, MByte, and GByte.
|
||||||
|
const (
|
||||||
|
KB = 1024
|
||||||
|
MB = KB * 1024
|
||||||
|
GB = MB * 1024
|
||||||
|
)
|
||||||
40
pkg/fs/duf/duf.go
Normal file
40
pkg/fs/duf/duf.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Package duf provides file system usage information.
|
||||||
|
|
||||||
|
Copyright (c) 2018 - 2025 PhotoPrism UG. All rights reserved.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||||
|
<https://docs.photoprism.app/license/agpl>
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||||
|
which describe how our Brand Assets may be used:
|
||||||
|
<https://www.photoprism.app/trademark>
|
||||||
|
|
||||||
|
This code is copied and modified in part from:
|
||||||
|
|
||||||
|
- https://github.com/muesli/duf
|
||||||
|
MIT License, Copyright (c) 2020 Christian Muehlhaeuser
|
||||||
|
see https://github.com/muesli/duf?tab=License-1-ov-file#readme
|
||||||
|
|
||||||
|
- https://github.com/shirou/gopsutil
|
||||||
|
BSD License, Copyright (c) 2014, WAKAYAMA Shirou
|
||||||
|
see https://github.com/shirou/gopsutil?tab=License-1-ov-file#readme
|
||||||
|
|
||||||
|
Feel free to send an email to hello@photoprism.app if you have questions,
|
||||||
|
want to support our work, or just want to say hello.
|
||||||
|
|
||||||
|
Additional information can be found in our Developer Guide:
|
||||||
|
<https://docs.photoprism.app/developer-guide/>
|
||||||
|
*/
|
||||||
|
package duf
|
||||||
|
|
||||||
|
// Mounts returns the active file system mounts, along with any warnings or errors that have occurred.
|
||||||
|
func Mounts() (m []Mount, warnings []string, err error) {
|
||||||
|
return mounts()
|
||||||
|
}
|
||||||
40
pkg/fs/duf/duf_test.go
Normal file
40
pkg/fs/duf/duf_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMounts(t *testing.T) {
|
||||||
|
t.Run("Success", func(t *testing.T) {
|
||||||
|
// Get slice of mounted file systems.
|
||||||
|
results, warnings, err := Mounts()
|
||||||
|
|
||||||
|
// No warnings or errors are expected.
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, warnings)
|
||||||
|
|
||||||
|
// At least one mount returned?
|
||||||
|
if len(results) < 1 {
|
||||||
|
t.Error("at least one result expected")
|
||||||
|
} else {
|
||||||
|
// If so, check the first mount for plausibility.
|
||||||
|
result := results[0]
|
||||||
|
assert.NotEmpty(t, result.Device)
|
||||||
|
assert.Equal(t, "local", result.DeviceType)
|
||||||
|
assert.Equal(t, "/", result.Mountpoint)
|
||||||
|
assert.NotEmpty(t, result.Fstype)
|
||||||
|
assert.NotEmpty(t, result.Opts)
|
||||||
|
assert.NotEmpty(t, result.Total)
|
||||||
|
assert.NotEmpty(t, result.Used)
|
||||||
|
assert.NotEmpty(t, result.Free)
|
||||||
|
assert.NotEmpty(t, result.Inodes)
|
||||||
|
assert.NotEmpty(t, result.InodesFree)
|
||||||
|
assert.NotEmpty(t, result.InodesUsed)
|
||||||
|
assert.NotEmpty(t, result.Blocks)
|
||||||
|
assert.NotEmpty(t, result.BlockSize)
|
||||||
|
assert.NotEmpty(t, result.Metadata)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
68
pkg/fs/duf/filesystems.go
Normal file
68
pkg/fs/duf/filesystems.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findMounts(mounts []Mount, path string) ([]Mount, error) {
|
||||||
|
var err error
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err = filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m []Mount
|
||||||
|
for _, v := range mounts {
|
||||||
|
if path == v.Device {
|
||||||
|
return []Mount{v}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(path, v.Mountpoint) {
|
||||||
|
var nm []Mount
|
||||||
|
|
||||||
|
// keep all entries that are as close or closer to the target
|
||||||
|
for _, mv := range m {
|
||||||
|
if len(mv.Mountpoint) >= len(v.Mountpoint) {
|
||||||
|
nm = append(nm, mv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m = nm
|
||||||
|
|
||||||
|
// add entry only if we didn't already find something closer
|
||||||
|
if len(nm) == 0 || len(v.Mountpoint) >= len(nm[0].Mountpoint) {
|
||||||
|
m = append(m, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deviceType(m Mount) string {
|
||||||
|
if isNetworkFs(m) {
|
||||||
|
return NetworkDevice
|
||||||
|
}
|
||||||
|
if isSpecialFs(m) {
|
||||||
|
return SpecialDevice
|
||||||
|
}
|
||||||
|
if isFuseFs(m) {
|
||||||
|
return FuseDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote: [ "nfs", "smbfs", "cifs", "ncpfs", "afs", "coda", "ftpfs", "mfs", "sshfs", "fuse.sshfs", "nfs4" ]
|
||||||
|
// special: [ "tmpfs", "devpts", "devtmpfs", "proc", "sysfs", "usbfs", "devfs", "fdescfs", "linprocfs" ]
|
||||||
21
pkg/fs/duf/filesystems_darwin.go
Normal file
21
pkg/fs/duf/filesystems_darwin.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
func isFuseFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialFs(m Mount) bool {
|
||||||
|
return m.Fstype == "devfs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHiddenFs(m Mount) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
36
pkg/fs/duf/filesystems_freebsd.go
Normal file
36
pkg/fs/duf/filesystems_freebsd.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
func isFuseFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkFs(m Mount) bool {
|
||||||
|
fs := []string{"nfs", "smbfs"}
|
||||||
|
|
||||||
|
for _, v := range fs {
|
||||||
|
if m.Fstype == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialFs(m Mount) bool {
|
||||||
|
fs := []string{"devfs", "tmpfs", "linprocfs", "linsysfs", "fdescfs", "procfs"}
|
||||||
|
|
||||||
|
for _, v := range fs {
|
||||||
|
if m.Fstype == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHiddenFs(m Mount) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
300
pkg/fs/duf/filesystems_linux.go
Normal file
300
pkg/fs/duf/filesystems_linux.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
//nolint:revive,deadcode
|
||||||
|
const (
|
||||||
|
// man statfs
|
||||||
|
ADFS_SUPER_MAGIC = 0xadf5
|
||||||
|
AFFS_SUPER_MAGIC = 0xADFF
|
||||||
|
AUTOFS_SUPER_MAGIC = 0x0187
|
||||||
|
BDEVFS_MAGIC = 0x62646576
|
||||||
|
BEFS_SUPER_MAGIC = 0x42465331
|
||||||
|
BFS_MAGIC = 0x1BADFACE
|
||||||
|
BINFMTFS_MAGIC = 0x42494e4d
|
||||||
|
BPF_FS_MAGIC = 0xcafe4a11
|
||||||
|
BTRFS_SUPER_MAGIC = 0x9123683E
|
||||||
|
CGROUP_SUPER_MAGIC = 0x27e0eb
|
||||||
|
CGROUP2_SUPER_MAGIC = 0x63677270
|
||||||
|
CIFS_MAGIC_NUMBER = 0xFF534D42
|
||||||
|
CODA_SUPER_MAGIC = 0x73757245
|
||||||
|
COH_SUPER_MAGIC = 0x012FF7B7
|
||||||
|
CONFIGFS_MAGIC = 0x62656570
|
||||||
|
CRAMFS_MAGIC = 0x28cd3d45
|
||||||
|
DEBUGFS_MAGIC = 0x64626720
|
||||||
|
DEVFS_SUPER_MAGIC = 0x1373
|
||||||
|
DEVPTS_SUPER_MAGIC = 0x1cd1
|
||||||
|
EFIVARFS_MAGIC = 0xde5e81e4
|
||||||
|
EFS_SUPER_MAGIC = 0x00414A53
|
||||||
|
EXT_SUPER_MAGIC = 0x137D
|
||||||
|
EXT2_OLD_SUPER_MAGIC = 0xEF51
|
||||||
|
EXT2_SUPER_MAGIC = 0xEF53
|
||||||
|
EXT3_SUPER_MAGIC = 0xEF53
|
||||||
|
EXT4_SUPER_MAGIC = 0xEF53
|
||||||
|
FUSE_SUPER_MAGIC = 0x65735546
|
||||||
|
FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
|
||||||
|
HFS_SUPER_MAGIC = 0x4244
|
||||||
|
HFSPLUS_SUPER_MAGIC = 0x482b
|
||||||
|
HOSTFS_SUPER_MAGIC = 0x00c0ffee
|
||||||
|
HPFS_SUPER_MAGIC = 0xF995E849
|
||||||
|
HUGETLBFS_MAGIC = 0x958458f6
|
||||||
|
ISOFS_SUPER_MAGIC = 0x9660
|
||||||
|
JFFS2_SUPER_MAGIC = 0x72b6
|
||||||
|
JFS_SUPER_MAGIC = 0x3153464a
|
||||||
|
MINIX_SUPER_MAGIC = 0x137F /* orig. minix */
|
||||||
|
MINIX_SUPER_MAGIC2 = 0x138F /* 30 char minix */
|
||||||
|
MINIX2_SUPER_MAGIC = 0x2468 /* minix V2 */
|
||||||
|
MINIX2_SUPER_MAGIC2 = 0x2478 /* minix V2, 30 char names */
|
||||||
|
MINIX3_SUPER_MAGIC = 0x4d5a /* minix V3 fs, 60 char names */
|
||||||
|
MQUEUE_MAGIC = 0x19800202
|
||||||
|
MSDOS_SUPER_MAGIC = 0x4d44
|
||||||
|
NCP_SUPER_MAGIC = 0x564c
|
||||||
|
NFS_SUPER_MAGIC = 0x6969
|
||||||
|
NILFS_SUPER_MAGIC = 0x3434
|
||||||
|
NTFS_SB_MAGIC = 0x5346544e
|
||||||
|
OCFS2_SUPER_MAGIC = 0x7461636f
|
||||||
|
OPENPROM_SUPER_MAGIC = 0x9fa1
|
||||||
|
PIPEFS_MAGIC = 0x50495045
|
||||||
|
PROC_SUPER_MAGIC = 0x9fa0
|
||||||
|
PSTOREFS_MAGIC = 0x6165676C
|
||||||
|
QNX4_SUPER_MAGIC = 0x002f
|
||||||
|
QNX6_SUPER_MAGIC = 0x68191122
|
||||||
|
RAMFS_MAGIC = 0x858458f6
|
||||||
|
REISERFS_SUPER_MAGIC = 0x52654973
|
||||||
|
ROMFS_MAGIC = 0x7275
|
||||||
|
SELINUX_MAGIC = 0xf97cff8c
|
||||||
|
SMACK_MAGIC = 0x43415d53
|
||||||
|
SMB_SUPER_MAGIC = 0x517B
|
||||||
|
SMB2_MAGIC_NUMBER = 0xfe534d42
|
||||||
|
SOCKFS_MAGIC = 0x534F434B
|
||||||
|
SQUASHFS_MAGIC = 0x73717368
|
||||||
|
SYSFS_MAGIC = 0x62656572
|
||||||
|
SYSV2_SUPER_MAGIC = 0x012FF7B6
|
||||||
|
SYSV4_SUPER_MAGIC = 0x012FF7B5
|
||||||
|
TMPFS_MAGIC = 0x01021994
|
||||||
|
TRACEFS_MAGIC = 0x74726163
|
||||||
|
UDF_SUPER_MAGIC = 0x15013346
|
||||||
|
UFS_MAGIC = 0x00011954
|
||||||
|
USBDEVICE_SUPER_MAGIC = 0x9fa2
|
||||||
|
V9FS_MAGIC = 0x01021997
|
||||||
|
VXFS_SUPER_MAGIC = 0xa501FCF5
|
||||||
|
XENFS_SUPER_MAGIC = 0xabba1974
|
||||||
|
XENIX_SUPER_MAGIC = 0x012FF7B4
|
||||||
|
XFS_SUPER_MAGIC = 0x58465342
|
||||||
|
_XIAFS_SUPER_MAGIC = 0x012FD16D
|
||||||
|
|
||||||
|
AFS_SUPER_MAGIC = 0x5346414F
|
||||||
|
AUFS_SUPER_MAGIC = 0x61756673
|
||||||
|
ANON_INODE_FS_SUPER_MAGIC = 0x09041934
|
||||||
|
CEPH_SUPER_MAGIC = 0x00C36400
|
||||||
|
ECRYPTFS_SUPER_MAGIC = 0xF15F
|
||||||
|
FAT_SUPER_MAGIC = 0x4006
|
||||||
|
FHGFS_SUPER_MAGIC = 0x19830326
|
||||||
|
FUSEBLK_SUPER_MAGIC = 0x65735546
|
||||||
|
FUSECTL_SUPER_MAGIC = 0x65735543
|
||||||
|
GFS_SUPER_MAGIC = 0x1161970
|
||||||
|
GPFS_SUPER_MAGIC = 0x47504653
|
||||||
|
MTD_INODE_FS_SUPER_MAGIC = 0x11307854
|
||||||
|
INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA
|
||||||
|
ISOFS_R_WIN_SUPER_MAGIC = 0x4004
|
||||||
|
ISOFS_WIN_SUPER_MAGIC = 0x4000
|
||||||
|
JFFS_SUPER_MAGIC = 0x07C0
|
||||||
|
KAFS_SUPER_MAGIC = 0x6B414653
|
||||||
|
LUSTRE_SUPER_MAGIC = 0x0BD00BD0
|
||||||
|
NFSD_SUPER_MAGIC = 0x6E667364
|
||||||
|
PANFS_SUPER_MAGIC = 0xAAD7AAEA
|
||||||
|
RPC_PIPEFS_SUPER_MAGIC = 0x67596969
|
||||||
|
SECURITYFS_SUPER_MAGIC = 0x73636673
|
||||||
|
UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100
|
||||||
|
VMHGFS_SUPER_MAGIC = 0xBACBACBC
|
||||||
|
VZFS_SUPER_MAGIC = 0x565A4653
|
||||||
|
ZFS_SUPER_MAGIC = 0x2FC12FC1
|
||||||
|
)
|
||||||
|
|
||||||
|
// coreutils/src/stat.c
|
||||||
|
var fsTypeMap = map[int64]string{
|
||||||
|
ADFS_SUPER_MAGIC: "adfs", /* 0xADF5 local */
|
||||||
|
AFFS_SUPER_MAGIC: "affs", /* 0xADFF local */
|
||||||
|
AFS_SUPER_MAGIC: "afs", /* 0x5346414F remote */
|
||||||
|
ANON_INODE_FS_SUPER_MAGIC: "anon-inode FS", /* 0x09041934 local */
|
||||||
|
AUFS_SUPER_MAGIC: "aufs", /* 0x61756673 remote */
|
||||||
|
AUTOFS_SUPER_MAGIC: "autofs", /* 0x0187 local */
|
||||||
|
BEFS_SUPER_MAGIC: "befs", /* 0x42465331 local */
|
||||||
|
BDEVFS_MAGIC: "bdevfs", /* 0x62646576 local */
|
||||||
|
BFS_MAGIC: "bfs", /* 0x1BADFACE local */
|
||||||
|
BINFMTFS_MAGIC: "binfmt_misc", /* 0x42494E4D local */
|
||||||
|
BTRFS_SUPER_MAGIC: "btrfs", /* 0x9123683E local */
|
||||||
|
CEPH_SUPER_MAGIC: "ceph", /* 0x00C36400 remote */
|
||||||
|
CGROUP_SUPER_MAGIC: "cgroupfs", /* 0x0027E0EB local */
|
||||||
|
CIFS_MAGIC_NUMBER: "cifs", /* 0xFF534D42 remote */
|
||||||
|
CODA_SUPER_MAGIC: "coda", /* 0x73757245 remote */
|
||||||
|
COH_SUPER_MAGIC: "coh", /* 0x012FF7B7 local */
|
||||||
|
CRAMFS_MAGIC: "cramfs", /* 0x28CD3D45 local */
|
||||||
|
DEBUGFS_MAGIC: "debugfs", /* 0x64626720 local */
|
||||||
|
DEVFS_SUPER_MAGIC: "devfs", /* 0x1373 local */
|
||||||
|
DEVPTS_SUPER_MAGIC: "devpts", /* 0x1CD1 local */
|
||||||
|
ECRYPTFS_SUPER_MAGIC: "ecryptfs", /* 0xF15F local */
|
||||||
|
EFS_SUPER_MAGIC: "efs", /* 0x00414A53 local */
|
||||||
|
EXT_SUPER_MAGIC: "ext", /* 0x137D local */
|
||||||
|
EXT2_SUPER_MAGIC: "ext2/ext3", /* 0xEF53 local */
|
||||||
|
EXT2_OLD_SUPER_MAGIC: "ext2", /* 0xEF51 local */
|
||||||
|
FAT_SUPER_MAGIC: "fat", /* 0x4006 local */
|
||||||
|
FHGFS_SUPER_MAGIC: "fhgfs", /* 0x19830326 remote */
|
||||||
|
FUSEBLK_SUPER_MAGIC: "fuseblk", /* 0x65735546 remote */
|
||||||
|
FUSECTL_SUPER_MAGIC: "fusectl", /* 0x65735543 remote */
|
||||||
|
FUTEXFS_SUPER_MAGIC: "futexfs", /* 0x0BAD1DEA local */
|
||||||
|
GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */
|
||||||
|
GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */
|
||||||
|
HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */
|
||||||
|
HFSPLUS_SUPER_MAGIC: "hfsplus", /* 0x482b local */
|
||||||
|
HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */
|
||||||
|
HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */
|
||||||
|
MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */
|
||||||
|
INOTIFYFS_SUPER_MAGIC: "inotifyfs", /* 0x2BAD1DEA local */
|
||||||
|
ISOFS_SUPER_MAGIC: "isofs", /* 0x9660 local */
|
||||||
|
ISOFS_R_WIN_SUPER_MAGIC: "isofs", /* 0x4004 local */
|
||||||
|
ISOFS_WIN_SUPER_MAGIC: "isofs", /* 0x4000 local */
|
||||||
|
JFFS_SUPER_MAGIC: "jffs", /* 0x07C0 local */
|
||||||
|
JFFS2_SUPER_MAGIC: "jffs2", /* 0x72B6 local */
|
||||||
|
JFS_SUPER_MAGIC: "jfs", /* 0x3153464A local */
|
||||||
|
KAFS_SUPER_MAGIC: "k-afs", /* 0x6B414653 remote */
|
||||||
|
LUSTRE_SUPER_MAGIC: "lustre", /* 0x0BD00BD0 remote */
|
||||||
|
MINIX_SUPER_MAGIC: "minix", /* 0x137F local */
|
||||||
|
MINIX_SUPER_MAGIC2: "minix (30 char.)", /* 0x138F local */
|
||||||
|
MINIX2_SUPER_MAGIC: "minix v2", /* 0x2468 local */
|
||||||
|
MINIX2_SUPER_MAGIC2: "minix v2 (30 char.)", /* 0x2478 local */
|
||||||
|
MINIX3_SUPER_MAGIC: "minix3", /* 0x4D5A local */
|
||||||
|
MQUEUE_MAGIC: "mqueue", /* 0x19800202 local */
|
||||||
|
MSDOS_SUPER_MAGIC: "msdos", /* 0x4D44 local */
|
||||||
|
NCP_SUPER_MAGIC: "novell", /* 0x564C remote */
|
||||||
|
NFS_SUPER_MAGIC: "nfs", /* 0x6969 remote */
|
||||||
|
NFSD_SUPER_MAGIC: "nfsd", /* 0x6E667364 remote */
|
||||||
|
NILFS_SUPER_MAGIC: "nilfs", /* 0x3434 local */
|
||||||
|
NTFS_SB_MAGIC: "ntfs", /* 0x5346544E local */
|
||||||
|
OPENPROM_SUPER_MAGIC: "openprom", /* 0x9FA1 local */
|
||||||
|
OCFS2_SUPER_MAGIC: "ocfs2", /* 0x7461636f remote */
|
||||||
|
PANFS_SUPER_MAGIC: "panfs", /* 0xAAD7AAEA remote */
|
||||||
|
PIPEFS_MAGIC: "pipefs", /* 0x50495045 remote */
|
||||||
|
PROC_SUPER_MAGIC: "proc", /* 0x9FA0 local */
|
||||||
|
PSTOREFS_MAGIC: "pstorefs", /* 0x6165676C local */
|
||||||
|
QNX4_SUPER_MAGIC: "qnx4", /* 0x002F local */
|
||||||
|
QNX6_SUPER_MAGIC: "qnx6", /* 0x68191122 local */
|
||||||
|
RAMFS_MAGIC: "ramfs", /* 0x858458F6 local */
|
||||||
|
REISERFS_SUPER_MAGIC: "reiserfs", /* 0x52654973 local */
|
||||||
|
ROMFS_MAGIC: "romfs", /* 0x7275 local */
|
||||||
|
RPC_PIPEFS_SUPER_MAGIC: "rpc_pipefs", /* 0x67596969 local */
|
||||||
|
SECURITYFS_SUPER_MAGIC: "securityfs", /* 0x73636673 local */
|
||||||
|
SELINUX_MAGIC: "selinux", /* 0xF97CFF8C local */
|
||||||
|
SMB_SUPER_MAGIC: "smb", /* 0x517B remote */
|
||||||
|
SMB2_MAGIC_NUMBER: "smb2", /* 0xfe534d42 remote */
|
||||||
|
SOCKFS_MAGIC: "sockfs", /* 0x534F434B local */
|
||||||
|
SQUASHFS_MAGIC: "squashfs", /* 0x73717368 local */
|
||||||
|
SYSFS_MAGIC: "sysfs", /* 0x62656572 local */
|
||||||
|
SYSV2_SUPER_MAGIC: "sysv2", /* 0x012FF7B6 local */
|
||||||
|
SYSV4_SUPER_MAGIC: "sysv4", /* 0x012FF7B5 local */
|
||||||
|
TMPFS_MAGIC: "tmpfs", /* 0x01021994 local */
|
||||||
|
UDF_SUPER_MAGIC: "udf", /* 0x15013346 local */
|
||||||
|
UFS_MAGIC: "ufs", /* 0x00011954 local */
|
||||||
|
UFS_BYTESWAPPED_SUPER_MAGIC: "ufs", /* 0x54190100 local */
|
||||||
|
USBDEVICE_SUPER_MAGIC: "usbdevfs", /* 0x9FA2 local */
|
||||||
|
V9FS_MAGIC: "v9fs", /* 0x01021997 local */
|
||||||
|
VMHGFS_SUPER_MAGIC: "vmhgfs", /* 0xBACBACBC remote */
|
||||||
|
VXFS_SUPER_MAGIC: "vxfs", /* 0xA501FCF5 local */
|
||||||
|
VZFS_SUPER_MAGIC: "vzfs", /* 0x565A4653 local */
|
||||||
|
XENFS_SUPER_MAGIC: "xenfs", /* 0xABBA1974 local */
|
||||||
|
XENIX_SUPER_MAGIC: "xenix", /* 0x012FF7B4 local */
|
||||||
|
XFS_SUPER_MAGIC: "xfs", /* 0x58465342 local */
|
||||||
|
_XIAFS_SUPER_MAGIC: "xia", /* 0x012FD16D local */
|
||||||
|
ZFS_SUPER_MAGIC: "zfs", /* 0x2FC12FC1 local */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var localMap = map[int64]bool{
|
||||||
|
AFS_SUPER_MAGIC: true,
|
||||||
|
BTRFS_SUPER_MAGIC: true,
|
||||||
|
EXT_SUPER_MAGIC: true,
|
||||||
|
EXT2_OLD_SUPER_MAGIC: true,
|
||||||
|
EXT2_SUPER_MAGIC: true,
|
||||||
|
FAT_SUPER_MAGIC: true,
|
||||||
|
HPFS_SUPER_MAGIC: true,
|
||||||
|
MSDOS_SUPER_MAGIC: true,
|
||||||
|
NTFS_SB_MAGIC: true,
|
||||||
|
REISERFS_SUPER_MAGIC: true,
|
||||||
|
UDF_SUPER_MAGIC: true,
|
||||||
|
XFS_SUPER_MAGIC: true,
|
||||||
|
ZFS_SUPER_MAGIC: true,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var networkMap = map[int64]bool{
|
||||||
|
CIFS_MAGIC_NUMBER: true,
|
||||||
|
NFS_SUPER_MAGIC: true,
|
||||||
|
SMB_SUPER_MAGIC: true,
|
||||||
|
SMB2_MAGIC_NUMBER: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var specialMap = map[int64]bool{
|
||||||
|
AUTOFS_SUPER_MAGIC: true,
|
||||||
|
BINFMTFS_MAGIC: true,
|
||||||
|
BPF_FS_MAGIC: true,
|
||||||
|
CGROUP_SUPER_MAGIC: true,
|
||||||
|
CGROUP2_SUPER_MAGIC: true,
|
||||||
|
CONFIGFS_MAGIC: true,
|
||||||
|
DEBUGFS_MAGIC: true,
|
||||||
|
DEVPTS_SUPER_MAGIC: true,
|
||||||
|
EFIVARFS_MAGIC: true,
|
||||||
|
FUSECTL_SUPER_MAGIC: true,
|
||||||
|
HUGETLBFS_MAGIC: true,
|
||||||
|
MQUEUE_MAGIC: true,
|
||||||
|
PROC_SUPER_MAGIC: true,
|
||||||
|
PSTOREFS_MAGIC: true,
|
||||||
|
SECURITYFS_SUPER_MAGIC: true,
|
||||||
|
SYSFS_MAGIC: true,
|
||||||
|
TMPFS_MAGIC: true,
|
||||||
|
TRACEFS_MAGIC: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func isLocalFs(m Mount) bool {
|
||||||
|
return localMap[int64(m.Stat().Type)] //nolint:unconvert
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func isFuseFs(m Mount) bool {
|
||||||
|
return m.Stat().Type == FUSEBLK_SUPER_MAGIC ||
|
||||||
|
m.Stat().Type == FUSE_SUPER_MAGIC
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkFs(m Mount) bool {
|
||||||
|
return networkMap[int64(m.Stat().Type)] //nolint:unconvert
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialFs(m Mount) bool {
|
||||||
|
if m.Device == "nsfs" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return specialMap[int64(m.Stat().Type)] //nolint:unconvert
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHiddenFs(m Mount) bool {
|
||||||
|
switch m.Device {
|
||||||
|
case "shm":
|
||||||
|
return true
|
||||||
|
case "overlay":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m.Fstype {
|
||||||
|
case "autofs":
|
||||||
|
return true
|
||||||
|
case "squashfs":
|
||||||
|
if strings.HasPrefix(m.Mountpoint, "/snap") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
21
pkg/fs/duf/filesystems_openbsd.go
Normal file
21
pkg/fs/duf/filesystems_openbsd.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build openbsd
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
func isFuseFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialFs(m Mount) bool {
|
||||||
|
return m.Fstype == "devfs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHiddenFs(m Mount) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
55
pkg/fs/duf/filesystems_windows.go
Normal file
55
pkg/fs/duf/filesystems_windows.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WindowsSandboxMountPointRegistryPath = `Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\CPC\LocalMOF`
|
||||||
|
)
|
||||||
|
|
||||||
|
var windowsSandboxMountPoints = loadRegisteredWindowsSandboxMountPoints()
|
||||||
|
|
||||||
|
func loadRegisteredWindowsSandboxMountPoints() (ret map[string]struct{}) {
|
||||||
|
ret = make(map[string]struct{})
|
||||||
|
key, err := registry.OpenKey(registry.CURRENT_USER, WindowsSandboxMountPointRegistryPath, registry.READ)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyInfo, err := key.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoints, err := key.ReadValueNames(int(keyInfo.ValueCount))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range mountPoints {
|
||||||
|
ret[val] = struct{}{}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFuseFs(m Mount) bool {
|
||||||
|
//FIXME: implement
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkFs(m Mount) bool {
|
||||||
|
_, ok := m.Metadata.(*NetResource)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpecialFs(m Mount) bool {
|
||||||
|
_, ok := windowsSandboxMountPoints[m.Mountpoint]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHiddenFs(m Mount) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
23
pkg/fs/duf/filters.go
Normal file
23
pkg/fs/duf/filters.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
var (
|
||||||
|
all = false
|
||||||
|
hideDevices = ""
|
||||||
|
hideFs = ""
|
||||||
|
hideMp = ""
|
||||||
|
onlyDevices = ""
|
||||||
|
onlyFs = ""
|
||||||
|
onlyMp = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterOptions contains all filters.
|
||||||
|
type FilterOptions struct {
|
||||||
|
HiddenDevices map[string]struct{}
|
||||||
|
OnlyDevices map[string]struct{}
|
||||||
|
|
||||||
|
HiddenFilesystems map[string]struct{}
|
||||||
|
OnlyFilesystems map[string]struct{}
|
||||||
|
|
||||||
|
HiddenMountPoints map[string]struct{}
|
||||||
|
OnlyMountPoints map[string]struct{}
|
||||||
|
}
|
||||||
113
pkg/fs/duf/groups.go
Normal file
113
pkg/fs/duf/groups.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
groups = []string{LocalDevice, NetworkDevice, FuseDevice, SpecialDevice, LoopsDevice, BindsMount}
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupedMounts map[string][]Mount
|
||||||
|
|
||||||
|
func GroupMounts(m []Mount, filters FilterOptions) GroupedMounts {
|
||||||
|
deviceMounts := make(GroupedMounts)
|
||||||
|
hasOnlyDevices := len(filters.OnlyDevices) != 0
|
||||||
|
|
||||||
|
_, hideLocal := filters.HiddenDevices[LocalDevice]
|
||||||
|
_, hideNetwork := filters.HiddenDevices[NetworkDevice]
|
||||||
|
_, hideFuse := filters.HiddenDevices[FuseDevice]
|
||||||
|
_, hideSpecial := filters.HiddenDevices[SpecialDevice]
|
||||||
|
_, hideLoops := filters.HiddenDevices[LoopsDevice]
|
||||||
|
_, hideBinds := filters.HiddenDevices[BindsMount]
|
||||||
|
|
||||||
|
_, onlyLocal := filters.OnlyDevices[LocalDevice]
|
||||||
|
_, onlyNetwork := filters.OnlyDevices[NetworkDevice]
|
||||||
|
_, onlyFuse := filters.OnlyDevices[FuseDevice]
|
||||||
|
_, onlySpecial := filters.OnlyDevices[SpecialDevice]
|
||||||
|
_, onlyLoops := filters.OnlyDevices[LoopsDevice]
|
||||||
|
_, onlyBinds := filters.OnlyDevices[BindsMount]
|
||||||
|
|
||||||
|
// sort/filter devices
|
||||||
|
for _, v := range m {
|
||||||
|
if len(filters.OnlyFilesystems) != 0 {
|
||||||
|
// skip not onlyFs
|
||||||
|
if _, ok := filters.OnlyFilesystems[strings.ToLower(v.Fstype)]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// skip hideFs
|
||||||
|
if _, ok := filters.HiddenFilesystems[strings.ToLower(v.Fstype)]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip hidden devices
|
||||||
|
if isHiddenFs(v) && !all {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip bind-mounts
|
||||||
|
if strings.Contains(v.Opts, "bind") {
|
||||||
|
if (hasOnlyDevices && !onlyBinds) || (hideBinds && !all) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip loop devices
|
||||||
|
if strings.HasPrefix(v.Device, "/dev/loop") {
|
||||||
|
if (hasOnlyDevices && !onlyLoops) || (hideLoops && !all) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip special devices
|
||||||
|
if v.Blocks == 0 && !all {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip zero size devices
|
||||||
|
if v.BlockSize == 0 && !all {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip not only mount point
|
||||||
|
if len(filters.OnlyMountPoints) != 0 {
|
||||||
|
if !findInKey(v.Mountpoint, filters.OnlyMountPoints) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip hidden mount point
|
||||||
|
if len(filters.HiddenMountPoints) != 0 {
|
||||||
|
if findInKey(v.Mountpoint, filters.HiddenMountPoints) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := deviceType(v)
|
||||||
|
|
||||||
|
if !all {
|
||||||
|
switch {
|
||||||
|
case hasOnlyDevices && onlyLocal && t != LocalDevice:
|
||||||
|
continue
|
||||||
|
case hasOnlyDevices && onlyNetwork && t != NetworkDevice:
|
||||||
|
continue
|
||||||
|
case hasOnlyDevices && onlyFuse && t != FuseDevice:
|
||||||
|
continue
|
||||||
|
case hasOnlyDevices && onlySpecial && t != SpecialDevice:
|
||||||
|
continue
|
||||||
|
case
|
||||||
|
t == LocalDevice && hideLocal,
|
||||||
|
t == NetworkDevice && hideNetwork,
|
||||||
|
t == FuseDevice && hideFuse,
|
||||||
|
t == SpecialDevice && hideSpecial:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMounts[t] = append(deviceMounts[t], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceMounts
|
||||||
|
}
|
||||||
46
pkg/fs/duf/groups_test.go
Normal file
46
pkg/fs/duf/groups_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGroupMounts(t *testing.T) {
|
||||||
|
t.Run("Success", func(t *testing.T) {
|
||||||
|
// Get slice of mounted file systems.
|
||||||
|
m, warnings, err := Mounts()
|
||||||
|
|
||||||
|
// No warnings or errors are expected.
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, warnings)
|
||||||
|
|
||||||
|
filters := FilterOptions{
|
||||||
|
HiddenDevices: parseCommaSeparatedValues(hideDevices),
|
||||||
|
OnlyDevices: parseCommaSeparatedValues(onlyDevices),
|
||||||
|
HiddenFilesystems: parseCommaSeparatedValues(hideFs),
|
||||||
|
OnlyFilesystems: parseCommaSeparatedValues(onlyFs),
|
||||||
|
HiddenMountPoints: parseCommaSeparatedValues(hideMp),
|
||||||
|
OnlyMountPoints: parseCommaSeparatedValues(onlyMp),
|
||||||
|
}
|
||||||
|
|
||||||
|
results := GroupMounts(m, filters)
|
||||||
|
|
||||||
|
t.Logf("results, %#v", results)
|
||||||
|
|
||||||
|
// At least one mount returned?
|
||||||
|
if len(results) < 1 {
|
||||||
|
t.Error("at least one result expected")
|
||||||
|
} else if local, found := results[LocalDevice]; found {
|
||||||
|
for _, d := range local {
|
||||||
|
if d.Total <= 0 {
|
||||||
|
t.Error("total should be a positive integer")
|
||||||
|
} else {
|
||||||
|
t.Logf("%s is mounted at %s: %d of %d GB used (%.1f%%)", d.Device, d.Mountpoint, d.Used/GB, d.Total/GB, (float64(d.Used)/float64(d.Total))*100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("no local devices found")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
92
pkg/fs/duf/mounts.go
Normal file
92
pkg/fs/duf/mounts.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount contains all metadata for a single filesystem mount.
|
||||||
|
type Mount struct {
|
||||||
|
Device string `json:"device"`
|
||||||
|
DeviceType string `json:"device_type"`
|
||||||
|
Mountpoint string `json:"mount_point"`
|
||||||
|
Fstype string `json:"fs_type"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Opts string `json:"opts"`
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Free uint64 `json:"free"`
|
||||||
|
Used uint64 `json:"used"`
|
||||||
|
Inodes uint64 `json:"inodes"`
|
||||||
|
InodesFree uint64 `json:"inodes_free"`
|
||||||
|
InodesUsed uint64 `json:"inodes_used"`
|
||||||
|
Blocks uint64 `json:"blocks"`
|
||||||
|
BlockSize uint64 `json:"block_size"`
|
||||||
|
Metadata interface{} `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLines(filename string) ([]string, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close() //nolint:errcheck // ignore error
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
var s []string
|
||||||
|
for scanner.Scan() {
|
||||||
|
s = append(s, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescapeFstab(path string) string {
|
||||||
|
escaped, err := strconv.Unquote(`"` + path + `"`)
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return escaped
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:deadcode,unused // used on BSD
|
||||||
|
func byteToString(orig []byte) string {
|
||||||
|
n := -1
|
||||||
|
l := -1
|
||||||
|
for i, b := range orig {
|
||||||
|
// skip left side null
|
||||||
|
if l == -1 && b == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if l == -1 {
|
||||||
|
l = i
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n = i + 1
|
||||||
|
}
|
||||||
|
if n == -1 {
|
||||||
|
return string(orig)
|
||||||
|
}
|
||||||
|
return string(orig[l:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:deadcode,unused // used on OpenBSD
|
||||||
|
func intToString(orig []int8) string {
|
||||||
|
ret := make([]byte, len(orig))
|
||||||
|
size := -1
|
||||||
|
for i, o := range orig {
|
||||||
|
if o == 0 {
|
||||||
|
size = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ret[i] = byte(o)
|
||||||
|
}
|
||||||
|
if size == -1 {
|
||||||
|
size = len(orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(ret[0:size])
|
||||||
|
}
|
||||||
95
pkg/fs/duf/mounts_darwin.go
Normal file
95
pkg/fs/duf/mounts_darwin.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mount) Stat() unix.Statfs_t {
|
||||||
|
return m.Metadata.(unix.Statfs_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounts() ([]Mount, []string, error) {
|
||||||
|
var ret []Mount
|
||||||
|
var warnings []string
|
||||||
|
|
||||||
|
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
fs := make([]unix.Statfs_t, count)
|
||||||
|
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stat := range fs {
|
||||||
|
opts := "rw"
|
||||||
|
if stat.Flags&unix.MNT_RDONLY != 0 {
|
||||||
|
opts = "ro"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||||
|
opts += ",sync"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOEXEC != 0 {
|
||||||
|
opts += ",noexec"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOSUID != 0 {
|
||||||
|
opts += ",nosuid"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_UNION != 0 {
|
||||||
|
opts += ",union"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_ASYNC != 0 {
|
||||||
|
opts += ",async"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_DONTBROWSE != 0 {
|
||||||
|
opts += ",nobrowse"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_AUTOMOUNTED != 0 {
|
||||||
|
opts += ",automounted"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_JOURNALED != 0 {
|
||||||
|
opts += ",journaled"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_MULTILABEL != 0 {
|
||||||
|
opts += ",multilabel"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOATIME != 0 {
|
||||||
|
opts += ",noatime"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NODEV != 0 {
|
||||||
|
opts += ",nodev"
|
||||||
|
}
|
||||||
|
|
||||||
|
device := byteToString(stat.Mntfromname[:])
|
||||||
|
mountPoint := byteToString(stat.Mntonname[:])
|
||||||
|
fsType := byteToString(stat.Fstypename[:])
|
||||||
|
|
||||||
|
if len(device) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := Mount{
|
||||||
|
Device: device,
|
||||||
|
Mountpoint: mountPoint,
|
||||||
|
Fstype: fsType,
|
||||||
|
Type: fsType,
|
||||||
|
Opts: opts,
|
||||||
|
Metadata: stat,
|
||||||
|
Total: stat.Blocks * uint64(stat.Bsize),
|
||||||
|
Free: stat.Bavail * uint64(stat.Bsize),
|
||||||
|
Used: (stat.Blocks - stat.Bfree) * uint64(stat.Bsize),
|
||||||
|
Inodes: stat.Files,
|
||||||
|
InodesFree: stat.Ffree,
|
||||||
|
InodesUsed: stat.Files - stat.Ffree,
|
||||||
|
Blocks: stat.Blocks,
|
||||||
|
BlockSize: uint64(stat.Bsize),
|
||||||
|
}
|
||||||
|
d.DeviceType = deviceType(d)
|
||||||
|
|
||||||
|
ret = append(ret, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, warnings, nil
|
||||||
|
}
|
||||||
107
pkg/fs/duf/mounts_freebsd.go
Normal file
107
pkg/fs/duf/mounts_freebsd.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mount) Stat() unix.Statfs_t {
|
||||||
|
return m.Metadata.(unix.Statfs_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounts() ([]Mount, []string, error) {
|
||||||
|
var ret []Mount
|
||||||
|
var warnings []string
|
||||||
|
|
||||||
|
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
fs := make([]unix.Statfs_t, count)
|
||||||
|
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stat := range fs {
|
||||||
|
opts := "rw"
|
||||||
|
if stat.Flags&unix.MNT_RDONLY != 0 {
|
||||||
|
opts = "ro"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||||
|
opts += ",sync"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOEXEC != 0 {
|
||||||
|
opts += ",noexec"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOSUID != 0 {
|
||||||
|
opts += ",nosuid"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_UNION != 0 {
|
||||||
|
opts += ",union"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_ASYNC != 0 {
|
||||||
|
opts += ",async"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_SUIDDIR != 0 {
|
||||||
|
opts += ",suiddir"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_SOFTDEP != 0 {
|
||||||
|
opts += ",softdep"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOSYMFOLLOW != 0 {
|
||||||
|
opts += ",nosymfollow"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_GJOURNAL != 0 {
|
||||||
|
opts += ",gjournal"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_MULTILABEL != 0 {
|
||||||
|
opts += ",multilabel"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_ACLS != 0 {
|
||||||
|
opts += ",acls"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOATIME != 0 {
|
||||||
|
opts += ",noatime"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOCLUSTERR != 0 {
|
||||||
|
opts += ",noclusterr"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NOCLUSTERW != 0 {
|
||||||
|
opts += ",noclusterw"
|
||||||
|
}
|
||||||
|
if stat.Flags&unix.MNT_NFS4ACLS != 0 {
|
||||||
|
opts += ",nfsv4acls"
|
||||||
|
}
|
||||||
|
|
||||||
|
device := byteToString(stat.Mntfromname[:])
|
||||||
|
mountPoint := byteToString(stat.Mntonname[:])
|
||||||
|
fsType := byteToString(stat.Fstypename[:])
|
||||||
|
|
||||||
|
if len(device) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := Mount{
|
||||||
|
Device: device,
|
||||||
|
Mountpoint: mountPoint,
|
||||||
|
Fstype: fsType,
|
||||||
|
Type: fsType,
|
||||||
|
Opts: opts,
|
||||||
|
Metadata: stat,
|
||||||
|
Total: (uint64(stat.Blocks) * uint64(stat.Bsize)),
|
||||||
|
Free: (uint64(stat.Bavail) * uint64(stat.Bsize)),
|
||||||
|
Used: (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize),
|
||||||
|
Inodes: stat.Files,
|
||||||
|
InodesFree: uint64(stat.Ffree),
|
||||||
|
InodesUsed: stat.Files - uint64(stat.Ffree),
|
||||||
|
Blocks: uint64(stat.Blocks),
|
||||||
|
BlockSize: uint64(stat.Bsize),
|
||||||
|
}
|
||||||
|
d.DeviceType = deviceType(d)
|
||||||
|
|
||||||
|
ret = append(ret, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, warnings, nil
|
||||||
|
}
|
||||||
171
pkg/fs/duf/mounts_linux.go
Normal file
171
pkg/fs/duf/mounts_linux.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// A line of self/mountinfo has the following structure:
|
||||||
|
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||||
|
// (0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
|
||||||
|
//
|
||||||
|
// (0) mount ID: unique identifier of the mount (may be reused after umount).
|
||||||
|
//mountinfoMountID = 0
|
||||||
|
// (1) parent ID: ID of parent (or of self for the top of the mount tree).
|
||||||
|
//mountinfoParentID = 1
|
||||||
|
// (2) major:minor: value of st_dev for files on filesystem.
|
||||||
|
//mountinfoMajorMinor = 2
|
||||||
|
// (3) root: root of the mount within the filesystem.
|
||||||
|
//mountinfoRoot = 3
|
||||||
|
// (4) mount point: mount point relative to the process's root.
|
||||||
|
mountinfoMountPoint = 4
|
||||||
|
// (5) mount options: per mount options.
|
||||||
|
mountinfoMountOpts = 5
|
||||||
|
// (6) optional fields: zero or more fields terminated by "-".
|
||||||
|
mountinfoOptionalFields = 6
|
||||||
|
// (7) separator between optional fields.
|
||||||
|
//mountinfoSeparator = 7
|
||||||
|
// (8) filesystem type: name of filesystem of the form.
|
||||||
|
mountinfoFsType = 8
|
||||||
|
// (9) mount source: filesystem specific information or "none".
|
||||||
|
mountinfoMountSource = 9
|
||||||
|
// (10) super options: per super block options.
|
||||||
|
//mountinfoSuperOptions = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stat returns the mountpoint's stat information.
|
||||||
|
func (m *Mount) Stat() unix.Statfs_t {
|
||||||
|
return m.Metadata.(unix.Statfs_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounts() ([]Mount, []string, error) {
|
||||||
|
var warnings []string
|
||||||
|
|
||||||
|
filename := "/proc/self/mountinfo"
|
||||||
|
lines, err := readLines(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]Mount, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
nb, fields := parseMountInfoLine(line)
|
||||||
|
if nb == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the number of fields does not match the structure of mountinfo,
|
||||||
|
// emit a warning and ignore the line.
|
||||||
|
if nb < 10 || nb > 11 {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("found invalid mountinfo line: %s", line))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockDeviceID := fields[mountinfoMountID]
|
||||||
|
mountPoint := fields[mountinfoMountPoint]
|
||||||
|
mountOpts := fields[mountinfoMountOpts]
|
||||||
|
fstype := fields[mountinfoFsType]
|
||||||
|
device := fields[mountinfoMountSource]
|
||||||
|
|
||||||
|
var stat unix.Statfs_t
|
||||||
|
err := unix.Statfs(mountPoint, &stat)
|
||||||
|
if err != nil {
|
||||||
|
if err != os.ErrPermission {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", mountPoint, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stat = unix.Statfs_t{}
|
||||||
|
}
|
||||||
|
|
||||||
|
d := Mount{
|
||||||
|
Device: device,
|
||||||
|
Mountpoint: mountPoint,
|
||||||
|
Fstype: fstype,
|
||||||
|
Type: fsTypeMap[int64(stat.Type)], //nolint:unconvert
|
||||||
|
Opts: mountOpts,
|
||||||
|
Metadata: stat,
|
||||||
|
Total: (uint64(stat.Blocks) * uint64(stat.Bsize)), //nolint:unconvert
|
||||||
|
Free: (uint64(stat.Bavail) * uint64(stat.Bsize)), //nolint:unconvert
|
||||||
|
Used: (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize), //nolint:unconvert
|
||||||
|
Inodes: stat.Files,
|
||||||
|
InodesFree: stat.Ffree,
|
||||||
|
InodesUsed: stat.Files - stat.Ffree,
|
||||||
|
Blocks: uint64(stat.Blocks), //nolint:unconvert
|
||||||
|
BlockSize: uint64(stat.Bsize),
|
||||||
|
}
|
||||||
|
d.DeviceType = deviceType(d)
|
||||||
|
|
||||||
|
// resolve /dev/mapper/* device names
|
||||||
|
if strings.HasPrefix(d.Device, "/dev/mapper/") {
|
||||||
|
re := regexp.MustCompile(`^\/dev\/mapper\/(.*)-(.*)`)
|
||||||
|
match := re.FindAllStringSubmatch(d.Device, -1)
|
||||||
|
if len(match) > 0 && len(match[0]) == 3 {
|
||||||
|
d.Device = filepath.Join("/dev", match[0][1], match[0][2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMountInfoLine parses a line of /proc/self/mountinfo and returns the
|
||||||
|
// amount of parsed fields and their values.
|
||||||
|
func parseMountInfoLine(line string) (int, [11]string) {
|
||||||
|
var fields [11]string
|
||||||
|
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
// ignore comments and empty lines
|
||||||
|
return 0, fields
|
||||||
|
}
|
||||||
|
|
||||||
|
var i int
|
||||||
|
for _, f := range strings.Fields(line) {
|
||||||
|
// when parsing the optional fields, loop until we find the separator
|
||||||
|
if i == mountinfoOptionalFields {
|
||||||
|
// (6) optional fields: zero or more fields of the form
|
||||||
|
// "tag[:value]"; see below.
|
||||||
|
// (7) separator: the end of the optional fields is marked
|
||||||
|
// by a single hyphen.
|
||||||
|
if f != "-" {
|
||||||
|
if fields[i] == "" {
|
||||||
|
fields[i] += f
|
||||||
|
} else {
|
||||||
|
fields[i] += " " + f
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep reading until we reach the separator
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// separator found, continue parsing
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i {
|
||||||
|
case mountinfoMountPoint:
|
||||||
|
fallthrough
|
||||||
|
case mountinfoMountSource:
|
||||||
|
fallthrough
|
||||||
|
case mountinfoFsType:
|
||||||
|
fields[i] = unescapeFstab(f)
|
||||||
|
|
||||||
|
default:
|
||||||
|
fields[i] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, fields
|
||||||
|
}
|
||||||
207
pkg/fs/duf/mounts_linux_test.go
Normal file
207
pkg/fs/duf/mounts_linux_test.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLinuxMounts(t *testing.T) {
|
||||||
|
t.Run("Success", func(t *testing.T) {
|
||||||
|
// Get slice of mounted file systems.
|
||||||
|
results, warnings, err := mounts()
|
||||||
|
|
||||||
|
// No warnings or errors are expected.
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, warnings)
|
||||||
|
|
||||||
|
// At least one mount returned?
|
||||||
|
if len(results) < 1 {
|
||||||
|
t.Error("at least one result expected")
|
||||||
|
} else {
|
||||||
|
// If so, check the first mount for plausibility.
|
||||||
|
result := results[0]
|
||||||
|
assert.NotEmpty(t, result.Device)
|
||||||
|
assert.Equal(t, "local", result.DeviceType)
|
||||||
|
assert.Equal(t, "/", result.Mountpoint)
|
||||||
|
assert.NotEmpty(t, result.Fstype)
|
||||||
|
assert.NotEmpty(t, result.Opts)
|
||||||
|
assert.NotEmpty(t, result.Total)
|
||||||
|
assert.NotEmpty(t, result.Used)
|
||||||
|
assert.NotEmpty(t, result.Free)
|
||||||
|
assert.NotEmpty(t, result.Inodes)
|
||||||
|
assert.NotEmpty(t, result.InodesFree)
|
||||||
|
assert.NotEmpty(t, result.InodesUsed)
|
||||||
|
assert.NotEmpty(t, result.Blocks)
|
||||||
|
assert.NotEmpty(t, result.BlockSize)
|
||||||
|
assert.NotEmpty(t, result.Metadata)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFields(t *testing.T) {
|
||||||
|
var tt = []struct {
|
||||||
|
input string
|
||||||
|
number int
|
||||||
|
expected [11]string
|
||||||
|
}{
|
||||||
|
// Empty lines
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: " ",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: " ",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: " ",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
{
|
||||||
|
input: "#",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "# ",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "# ",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "# I'm a lazy dog",
|
||||||
|
number: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bad fields
|
||||||
|
{
|
||||||
|
input: "1 2",
|
||||||
|
number: 2,
|
||||||
|
expected: [11]string{"1", "2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2",
|
||||||
|
number: 2,
|
||||||
|
expected: [11]string{"1", "2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2 3",
|
||||||
|
number: 3,
|
||||||
|
expected: [11]string{"1", "2", "3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2 3 4",
|
||||||
|
number: 4,
|
||||||
|
expected: [11]string{"1", "2", "3", "4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// No optional separator or no options
|
||||||
|
{
|
||||||
|
input: "1 2 3 4 5 6 7 NotASeparator 9 10 11",
|
||||||
|
number: 6,
|
||||||
|
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 NotASeparator 9 10 11"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2 3 4 5 6 7 8 9 10 11",
|
||||||
|
number: 6,
|
||||||
|
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 8 9 10 11"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2 3 4 5 6 - 9 10 11",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"1", "2", "3", "4", "5", "6", "", "-", "9", "10", "11"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Normal mount table line
|
||||||
|
{
|
||||||
|
input: "22 27 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"22", "27", "0:21", "/", "/proc", "rw,nosuid,nodev,noexec,relatime", "shared:5", "-", "proc", "proc", "rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "31 23 0:27 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"31", "23", "0:27", "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "shared:9", "-", "cgroup2", "cgroup2", "rw,nsdelegate,memory_recursiveprot"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 - tmpfs tmpfs",
|
||||||
|
number: 10,
|
||||||
|
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18", "-", "tmpfs", "tmpfs"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 shared:22 - tmpfs tmpfs",
|
||||||
|
number: 10,
|
||||||
|
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18 shared:22", "-", "tmpfs", "tmpfs"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "50 27 0:33 / /tmp rw,nosuid,nodev - tmpfs tmpfs",
|
||||||
|
number: 10,
|
||||||
|
expected: [11]string{"50", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "", "-", "tmpfs", "tmpfs"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Exceptional mount table lines
|
||||||
|
{
|
||||||
|
input: "328 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs - rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"328", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "-", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "330 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs 👾 rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"330", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "335 27 0:73 / /mnt/👾 rw,relatime shared:206 - tmpfs 👾 rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"335", "27", "0:73", "/", "/mnt/👾", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "509 27 0:78 / /mnt/- rw,relatime shared:223 - tmpfs 👾 rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"509", "27", "0:78", "/", "/mnt/-", "rw,relatime", "shared:223", "-", "tmpfs", "👾", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "362 27 0:76 / /mnt/a\\040b rw,relatime shared:215 - tmpfs 👾 rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"362", "27", "0:76", "/", "/mnt/a b", "rw,relatime", "shared:215", "-", "tmpfs", "👾", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 2 3:3 / /mnt/\\011 rw shared:7 - tmpfs - rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"1", "2", "3:3", "/", "/mnt/\t", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "11 2 3:3 / /mnt/a\\012b rw shared:7 - tmpfs - rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"11", "2", "3:3", "/", "/mnt/a\nb", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "111 2 3:3 / /mnt/a\\134b rw shared:7 - tmpfs - rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"111", "2", "3:3", "/", "/mnt/a\\b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1111 2 3:3 / /mnt/a\\042b rw shared:7 - tmpfs - rw,inode64",
|
||||||
|
number: 11,
|
||||||
|
expected: [11]string{"1111", "2", "3:3", "/", "/mnt/a\"b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
nb, actual := parseMountInfoLine(tc.input)
|
||||||
|
if nb != tc.number || !reflect.DeepEqual(actual, tc.expected) {
|
||||||
|
t.Errorf("\nparseMountInfoLine(%q) == \n(%d) %q, \nexpected (%d) %q", tc.input, nb, actual, tc.number, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
pkg/fs/duf/mounts_openbsd.go
Normal file
86
pkg/fs/duf/mounts_openbsd.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//go:build openbsd
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Mount) Stat() unix.Statfs_t {
|
||||||
|
return m.Metadata.(unix.Statfs_t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounts() ([]Mount, []string, error) {
|
||||||
|
var ret []Mount
|
||||||
|
var warnings []string
|
||||||
|
|
||||||
|
count, err := unix.Getfsstat(nil, unix.MNT_WAIT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
fs := make([]unix.Statfs_t, count)
|
||||||
|
if _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stat := range fs {
|
||||||
|
opts := "rw"
|
||||||
|
if stat.F_flags&unix.MNT_RDONLY != 0 {
|
||||||
|
opts = "ro"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||||
|
opts += ",sync"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_NOEXEC != 0 {
|
||||||
|
opts += ",noexec"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_NOSUID != 0 {
|
||||||
|
opts += ",nosuid"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_NODEV != 0 {
|
||||||
|
opts += ",nodev"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_ASYNC != 0 {
|
||||||
|
opts += ",async"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_SOFTDEP != 0 {
|
||||||
|
opts += ",softdep"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_NOATIME != 0 {
|
||||||
|
opts += ",noatime"
|
||||||
|
}
|
||||||
|
if stat.F_flags&unix.MNT_WXALLOWED != 0 {
|
||||||
|
opts += ",wxallowed"
|
||||||
|
}
|
||||||
|
|
||||||
|
device := byteToString(stat.F_mntfromname[:])
|
||||||
|
mountPoint := byteToString(stat.F_mntonname[:])
|
||||||
|
fsType := byteToString(stat.F_fstypename[:])
|
||||||
|
|
||||||
|
if len(device) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := Mount{
|
||||||
|
Device: device,
|
||||||
|
Mountpoint: mountPoint,
|
||||||
|
Fstype: fsType,
|
||||||
|
Type: fsType,
|
||||||
|
Opts: opts,
|
||||||
|
Metadata: stat,
|
||||||
|
Total: (uint64(stat.F_blocks) * uint64(stat.F_bsize)),
|
||||||
|
Free: (uint64(stat.F_bavail) * uint64(stat.F_bsize)),
|
||||||
|
Used: (uint64(stat.F_blocks) - uint64(stat.F_bfree)) * uint64(stat.F_bsize),
|
||||||
|
Inodes: stat.F_files,
|
||||||
|
InodesFree: uint64(stat.F_ffree),
|
||||||
|
InodesUsed: stat.F_files - uint64(stat.F_ffree),
|
||||||
|
Blocks: uint64(stat.F_blocks),
|
||||||
|
BlockSize: uint64(stat.F_bsize),
|
||||||
|
}
|
||||||
|
d.DeviceType = deviceType(d)
|
||||||
|
|
||||||
|
ret = append(ret, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, warnings, nil
|
||||||
|
}
|
||||||
377
pkg/fs/duf/mounts_windows.go
Normal file
377
pkg/fs/duf/mounts_windows.go
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Local devices
|
||||||
|
const (
|
||||||
|
guidBufLen = windows.MAX_PATH + 1
|
||||||
|
volumeNameBufLen = windows.MAX_PATH + 1
|
||||||
|
rootPathBufLen = windows.MAX_PATH + 1
|
||||||
|
fileSystemBufLen = windows.MAX_PATH + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func getMountPoint(guidBuf []uint16) (mountPoint string, err error) {
|
||||||
|
var rootPathLen uint32
|
||||||
|
rootPathBuf := make([]uint16, rootPathBufLen)
|
||||||
|
|
||||||
|
err = windows.GetVolumePathNamesForVolumeName(&guidBuf[0], &rootPathBuf[0], rootPathBufLen*2, &rootPathLen)
|
||||||
|
if err != nil && err.(windows.Errno) == windows.ERROR_MORE_DATA {
|
||||||
|
// Retry if buffer size is too small
|
||||||
|
rootPathBuf = make([]uint16, (rootPathLen+1)/2)
|
||||||
|
err = windows.GetVolumePathNamesForVolumeName(
|
||||||
|
&guidBuf[0], &rootPathBuf[0], rootPathLen, &rootPathLen)
|
||||||
|
}
|
||||||
|
return windows.UTF16ToString(rootPathBuf), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVolumeInfo(guidOrMountPointBuf []uint16) (volumeName string, fsType string, err error) {
|
||||||
|
volumeNameBuf := make([]uint16, volumeNameBufLen)
|
||||||
|
fsTypeBuf := make([]uint16, fileSystemBufLen)
|
||||||
|
|
||||||
|
err = windows.GetVolumeInformation(&guidOrMountPointBuf[0], &volumeNameBuf[0], volumeNameBufLen*2,
|
||||||
|
nil, nil, nil,
|
||||||
|
&fsTypeBuf[0], fileSystemBufLen*2)
|
||||||
|
|
||||||
|
return windows.UTF16ToString(volumeNameBuf), windows.UTF16ToString(fsTypeBuf), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSpaceInfo(guidOrMountPointBuf []uint16) (totalBytes uint64, freeBytes uint64, err error) {
|
||||||
|
err = windows.GetDiskFreeSpaceEx(&guidOrMountPointBuf[0], nil, &totalBytes, &freeBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClusterInfo(guidOrMountPointBuf []uint16) (totalClusters uint32, clusterSize uint32, err error) {
|
||||||
|
var sectorsPerCluster uint32
|
||||||
|
var bytesPerSector uint32
|
||||||
|
err = GetDiskFreeSpace(&guidOrMountPointBuf[0], §orsPerCluster, &bytesPerSector, nil, &totalClusters)
|
||||||
|
clusterSize = bytesPerSector * sectorsPerCluster
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMount(guidOrMountPointBuf []uint16, isGUID bool) (m Mount, skip bool, warnings []string) {
|
||||||
|
var err error
|
||||||
|
guidOrMountPoint := windows.UTF16ToString(guidOrMountPointBuf)
|
||||||
|
|
||||||
|
mountPoint := guidOrMountPoint
|
||||||
|
if isGUID {
|
||||||
|
mountPoint, err = getMountPoint(guidOrMountPointBuf)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
|
||||||
|
}
|
||||||
|
// Skip unmounted volumes
|
||||||
|
if len(mountPoint) == 0 {
|
||||||
|
skip = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get volume name & filesystem type
|
||||||
|
volumeName, fsType, err := getVolumeInfo(guidOrMountPointBuf)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get space info
|
||||||
|
totalBytes, freeBytes, err := getSpaceInfo(guidOrMountPointBuf)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cluster info
|
||||||
|
totalClusters, clusterSize, err := getClusterInfo(guidOrMountPointBuf)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", guidOrMountPoint, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
m = Mount{
|
||||||
|
Device: volumeName,
|
||||||
|
Mountpoint: mountPoint,
|
||||||
|
Fstype: fsType,
|
||||||
|
Type: fsType,
|
||||||
|
Opts: "",
|
||||||
|
Total: totalBytes,
|
||||||
|
Free: freeBytes,
|
||||||
|
Used: totalBytes - freeBytes,
|
||||||
|
Blocks: uint64(totalClusters),
|
||||||
|
BlockSize: uint64(clusterSize),
|
||||||
|
}
|
||||||
|
m.DeviceType = deviceType(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMountFromGUID(guidBuf []uint16) (m Mount, skip bool, warnings []string) {
|
||||||
|
m, skip, warnings = getMount(guidBuf, true)
|
||||||
|
|
||||||
|
// Use GUID as volume name if no label was set
|
||||||
|
if len(m.Device) == 0 {
|
||||||
|
m.Device = windows.UTF16ToString(guidBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMountFromMountPoint(mountPointBuf []uint16) (m Mount, warnings []string) {
|
||||||
|
m, _, warnings = getMount(mountPointBuf, false)
|
||||||
|
|
||||||
|
// Use mount point as volume name if no label was set
|
||||||
|
if len(m.Device) == 0 {
|
||||||
|
m.Device = windows.UTF16ToString(mountPointBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendLocalMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {
|
||||||
|
guidBuf := make([]uint16, guidBufLen)
|
||||||
|
|
||||||
|
hFindVolume, err := windows.FindFirstVolume(&guidBuf[0], guidBufLen*2)
|
||||||
|
if err != nil {
|
||||||
|
return mounts, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
|
VolumeLoop:
|
||||||
|
for ; ; err = windows.FindNextVolume(hFindVolume, &guidBuf[0], guidBufLen*2) {
|
||||||
|
if err != nil {
|
||||||
|
switch err.(windows.Errno) {
|
||||||
|
case windows.ERROR_NO_MORE_FILES:
|
||||||
|
break VolumeLoop
|
||||||
|
default:
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s: %s", windows.UTF16ToString(guidBuf), err))
|
||||||
|
continue VolumeLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, skip, w := getMountFromGUID(guidBuf); !skip {
|
||||||
|
mounts = append(mounts, m)
|
||||||
|
warnings = append(warnings, w...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = windows.FindVolumeClose(hFindVolume); err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s", err))
|
||||||
|
}
|
||||||
|
return mounts, warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network devices
|
||||||
|
func getMountFromNetResource(netResource NetResource) (m Mount, warnings []string) {
|
||||||
|
mountPoint := windows.UTF16PtrToString(netResource.LocalName)
|
||||||
|
if !strings.HasSuffix(mountPoint, string(filepath.Separator)) {
|
||||||
|
mountPoint += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
mountPointBuf := windows.StringToUTF16(mountPoint)
|
||||||
|
|
||||||
|
m, _, warnings = getMount(mountPointBuf, false)
|
||||||
|
|
||||||
|
// Use remote name as volume name if no label was set
|
||||||
|
if len(m.Device) == 0 {
|
||||||
|
m.Device = windows.UTF16PtrToString(netResource.RemoteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendNetworkMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {
|
||||||
|
hEnumResource, err := WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, RESOURCEUSAGE_CONNECTABLE, nil)
|
||||||
|
if err != nil {
|
||||||
|
return mounts, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumLoop:
|
||||||
|
for {
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/wnet/enumerating-network-resources
|
||||||
|
var nrBuf [16384]byte
|
||||||
|
count := uint32(math.MaxUint32)
|
||||||
|
size := uint32(len(nrBuf))
|
||||||
|
if err := WNetEnumResource(hEnumResource, &count, &nrBuf[0], &size); err != nil {
|
||||||
|
switch err.(windows.Errno) {
|
||||||
|
case windows.ERROR_NO_MORE_ITEMS:
|
||||||
|
break EnumLoop
|
||||||
|
default:
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
break EnumLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
nr := (*NetResource)(unsafe.Pointer(&nrBuf[uintptr(i)*NetResourceSize]))
|
||||||
|
m, w := getMountFromNetResource(*nr)
|
||||||
|
mounts = append(mounts, m)
|
||||||
|
warnings = append(warnings, w...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = WNetCloseEnum(hEnumResource); err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s", err))
|
||||||
|
}
|
||||||
|
return mounts, warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mountPointAlreadyPresent(mounts []Mount, mountPoint string) bool {
|
||||||
|
for _, m := range mounts {
|
||||||
|
if m.Mountpoint == mountPoint {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendLogicalDrives(mounts []Mount, warnings []string) ([]Mount, []string) {
|
||||||
|
driveBitmap, err := windows.GetLogicalDrives()
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("GetLogicalDrives(): %s", err))
|
||||||
|
return mounts, warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
for drive := 'A'; drive <= 'Z'; drive, driveBitmap = drive+1, driveBitmap>>1 {
|
||||||
|
if driveBitmap&0x1 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoint := fmt.Sprintf("%c:\\", drive)
|
||||||
|
if mountPointAlreadyPresent(mounts, mountPoint) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPointBuf := windows.StringToUTF16(mountPoint)
|
||||||
|
m, w := getMountFromMountPoint(mountPointBuf)
|
||||||
|
mounts = append(mounts, m)
|
||||||
|
warnings = append(warnings, w...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mounts, warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func mounts() (ret []Mount, warnings []string, err error) {
|
||||||
|
ret = make([]Mount, 0)
|
||||||
|
|
||||||
|
// Local devices
|
||||||
|
if ret, warnings, err = appendLocalMounts(ret, warnings); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network devices
|
||||||
|
if ret, warnings, err = appendNetworkMounts(ret, warnings); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logical devices (from GetLogicalDrives bitflag)
|
||||||
|
// Check any possible logical drives, in case of some special virtual devices, such as RAM disk
|
||||||
|
ret, warnings = appendLogicalDrives(ret, warnings)
|
||||||
|
|
||||||
|
return ret, warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows API
|
||||||
|
const (
|
||||||
|
// Windows Networking const
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw
|
||||||
|
RESOURCE_CONNECTED = 0x00000001
|
||||||
|
RESOURCE_GLOBALNET = 0x00000002
|
||||||
|
RESOURCE_REMEMBERED = 0x00000003
|
||||||
|
RESOURCE_RECENT = 0x00000004
|
||||||
|
RESOURCE_CONTEXT = 0x00000005
|
||||||
|
|
||||||
|
RESOURCETYPE_ANY = 0x00000000
|
||||||
|
RESOURCETYPE_DISK = 0x00000001
|
||||||
|
RESOURCETYPE_PRINT = 0x00000002
|
||||||
|
RESOURCETYPE_RESERVED = 0x00000008
|
||||||
|
RESOURCETYPE_UNKNOWN = 0xFFFFFFFF
|
||||||
|
|
||||||
|
RESOURCEUSAGE_CONNECTABLE = 0x00000001
|
||||||
|
RESOURCEUSAGE_CONTAINER = 0x00000002
|
||||||
|
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004
|
||||||
|
RESOURCEUSAGE_SIBLING = 0x00000008
|
||||||
|
RESOURCEUSAGE_ATTACHED = 0x00000010
|
||||||
|
RESOURCEUSAGE_ALL = RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED
|
||||||
|
RESOURCEUSAGE_RESERVED = 0x80000000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Windows syscall
|
||||||
|
modmpr = windows.NewLazySystemDLL("mpr.dll")
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
|
||||||
|
procWNetOpenEnumW = modmpr.NewProc("WNetOpenEnumW")
|
||||||
|
procWNetCloseEnum = modmpr.NewProc("WNetCloseEnum")
|
||||||
|
procWNetEnumResourceW = modmpr.NewProc("WNetEnumResourceW")
|
||||||
|
procGetDiskFreeSpaceW = modkernel32.NewProc("GetDiskFreeSpaceW")
|
||||||
|
|
||||||
|
NetResourceSize = unsafe.Sizeof(NetResource{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/ns-winnetwk-netresourcew
|
||||||
|
type NetResource struct {
|
||||||
|
Scope uint32
|
||||||
|
Type uint32
|
||||||
|
DisplayType uint32
|
||||||
|
Usage uint32
|
||||||
|
LocalName *uint16
|
||||||
|
RemoteName *uint16
|
||||||
|
Comment *uint16
|
||||||
|
Provider *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw
|
||||||
|
func WNetOpenEnum(scope uint32, resourceType uint32, usage uint32, resource *NetResource) (handle windows.Handle, err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procWNetOpenEnumW.Addr(), 5, uintptr(scope), uintptr(resourceType), uintptr(usage), uintptr(unsafe.Pointer(resource)), uintptr(unsafe.Pointer(&handle)), 0)
|
||||||
|
if r1 != windows.NO_ERROR {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetenumresourcew
|
||||||
|
func WNetEnumResource(enumResource windows.Handle, count *uint32, buffer *byte, bufferSize *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procWNetEnumResourceW.Addr(), 4, uintptr(enumResource), uintptr(unsafe.Pointer(count)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferSize)), 0, 0)
|
||||||
|
if r1 != windows.NO_ERROR {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetcloseenum
|
||||||
|
func WNetCloseEnum(enumResource windows.Handle) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procWNetCloseEnum.Addr(), 1, uintptr(enumResource), 0, 0)
|
||||||
|
if r1 != windows.NO_ERROR {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespacew
|
||||||
|
func GetDiskFreeSpace(directoryName *uint16, sectorsPerCluster *uint32, bytesPerSector *uint32, numberOfFreeClusters *uint32, totalNumberOfClusters *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetDiskFreeSpaceW.Addr(), 5, uintptr(unsafe.Pointer(directoryName)), uintptr(unsafe.Pointer(sectorsPerCluster)), uintptr(unsafe.Pointer(bytesPerSector)), uintptr(unsafe.Pointer(numberOfFreeClusters)), uintptr(unsafe.Pointer(totalNumberOfClusters)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
53
pkg/fs/duf/util.go
Normal file
53
pkg/fs/duf/util.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package duf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/IGLOU-EU/go-wildcard"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseCommaSeparatedValues parses comma separated string into a map.
|
||||||
|
func parseCommaSeparatedValues(values string) map[string]struct{} {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, v := range strings.Split(values, ",") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if len(v) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v = strings.ToLower(v)
|
||||||
|
m[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateGroups validates the parsed group maps.
|
||||||
|
func validateGroups(m map[string]struct{}) error {
|
||||||
|
for k := range m {
|
||||||
|
found := false
|
||||||
|
for _, g := range groups {
|
||||||
|
if g == k {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("unknown device group: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findInKey parse a slice of pattern to match the given key.
|
||||||
|
func findInKey(str string, km map[string]struct{}) bool {
|
||||||
|
for p := range km {
|
||||||
|
if wildcard.Match(p, str) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user