Skip to content

Commit e1a9a68

Browse files
committed
FS: Add /pkg/fs/duf to determine mount points and disk usage photoprism#4266
Signed-off-by: Michael Mayer <michael@photoprism.app>
1 parent 5c1b0ba commit e1a9a68

23 files changed

+1983
-11
lines changed

go.mod

+4-3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ require github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082
7878
require golang.org/x/text v0.22.0
7979

8080
require (
81+
github.com/IGLOU-EU/go-wildcard v1.0.3
8182
github.com/davidbyttow/govips/v2 v2.16.0
8283
github.com/go-co-op/gocron/v2 v2.16.0
8384
github.com/pquerna/otp v1.4.0
@@ -86,6 +87,7 @@ require (
8687
github.com/swaggo/gin-swagger v1.6.0
8788
github.com/urfave/cli/v2 v2.27.5
8889
github.com/zitadel/oidc/v3 v3.35.0
90+
golang.org/x/sys v0.30.0
8991
)
9092

9193
require (
@@ -130,7 +132,7 @@ require (
130132
github.com/pmezard/go-difflib v1.0.0 // indirect
131133
github.com/prometheus/client_model v0.6.1 // indirect
132134
github.com/prometheus/procfs v0.15.1 // indirect
133-
github.com/rivo/uniseg v0.4.6 // indirect
135+
github.com/rivo/uniseg v0.4.7 // indirect
134136
github.com/russross/blackfriday/v2 v2.1.0 // indirect
135137
github.com/swaggo/swag v1.16.3 // indirect
136138
github.com/tidwall/match v1.1.1 // indirect
@@ -144,7 +146,6 @@ require (
144146
go.opentelemetry.io/otel/trace v1.29.0 // indirect
145147
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
146148
golang.org/x/oauth2 v0.26.0 // indirect
147-
golang.org/x/sys v0.30.0 // indirect
148149
golang.org/x/tools v0.30.0 // indirect
149150
google.golang.org/protobuf v1.36.4 // indirect
150151
gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -162,7 +163,7 @@ require (
162163

163164
require (
164165
github.com/emersion/go-webdav v0.6.0
165-
github.com/mattn/go-runewidth v0.0.15 // indirect
166+
github.com/mattn/go-runewidth v0.0.16 // indirect
166167
github.com/tidwall/pretty v1.2.1 // indirect
167168
)
168169

go.sum

+6-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
1919
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
2020
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2121
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
22+
github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0=
23+
github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY=
2224
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
2325
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
2426
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
304306
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
305307
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
306308
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
307-
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
308-
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
309+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
310+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
309311
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
310312
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
311313
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
349351
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
350352
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
351353
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
352-
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
353-
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
354+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
355+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
354356
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
355357
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
356358
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

pkg/fs/bytes.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package fs
22

3-
// KiloByte, MegaByte, and GigaByte size constants.
3+
// Size constants for KByte, MByte, and GByte.
44
const (
5-
KiloByte = 1024
6-
MegaByte = KiloByte * 1024
7-
GigaByte = MegaByte * 1024
5+
KB = 1024
6+
MB = KB * 1024
7+
GB = MB * 1024
88
)

pkg/fs/duf/const.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package duf
2+
3+
// Supported device types.
4+
const (
5+
LocalDevice = "local"
6+
NetworkDevice = "network"
7+
FuseDevice = "fuse"
8+
SpecialDevice = "special"
9+
LoopsDevice = "loops"
10+
BindsMount = "binds"
11+
)
12+
13+
// Size constants for KByte, MByte, and GByte.
14+
const (
15+
KB = 1024
16+
MB = KB * 1024
17+
GB = MB * 1024
18+
)

pkg/fs/duf/duf.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Package duf provides file system usage information.
3+
4+
Copyright (c) 2018 - 2025 PhotoPrism UG. All rights reserved.
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
8+
<https://docs.photoprism.app/license/agpl>
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
The AGPL is supplemented by our Trademark and Brand Guidelines,
16+
which describe how our Brand Assets may be used:
17+
<https://www.photoprism.app/trademark>
18+
19+
This code is copied and modified in part from:
20+
21+
- https://github.com/muesli/duf
22+
MIT License, Copyright (c) 2020 Christian Muehlhaeuser
23+
see https://github.com/muesli/duf?tab=License-1-ov-file#readme
24+
25+
- https://github.com/shirou/gopsutil
26+
BSD License, Copyright (c) 2014, WAKAYAMA Shirou
27+
see https://github.com/shirou/gopsutil?tab=License-1-ov-file#readme
28+
29+
Feel free to send an email to hello@photoprism.app if you have questions,
30+
want to support our work, or just want to say hello.
31+
32+
Additional information can be found in our Developer Guide:
33+
<https://docs.photoprism.app/developer-guide/>
34+
*/
35+
package duf
36+
37+
// Mounts returns the active file system mounts, along with any warnings or errors that have occurred.
38+
func Mounts() (m []Mount, warnings []string, err error) {
39+
return mounts()
40+
}

pkg/fs/duf/duf_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package duf
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestMounts(t *testing.T) {
10+
t.Run("Success", func(t *testing.T) {
11+
// Get slice of mounted file systems.
12+
results, warnings, err := Mounts()
13+
14+
// No warnings or errors are expected.
15+
assert.NoError(t, err)
16+
assert.Empty(t, warnings)
17+
18+
// At least one mount returned?
19+
if len(results) < 1 {
20+
t.Error("at least one result expected")
21+
} else {
22+
// If so, check the first mount for plausibility.
23+
result := results[0]
24+
assert.NotEmpty(t, result.Device)
25+
assert.Equal(t, "local", result.DeviceType)
26+
assert.Equal(t, "/", result.Mountpoint)
27+
assert.NotEmpty(t, result.Fstype)
28+
assert.NotEmpty(t, result.Opts)
29+
assert.NotEmpty(t, result.Total)
30+
assert.NotEmpty(t, result.Used)
31+
assert.NotEmpty(t, result.Free)
32+
assert.NotEmpty(t, result.Inodes)
33+
assert.NotEmpty(t, result.InodesFree)
34+
assert.NotEmpty(t, result.InodesUsed)
35+
assert.NotEmpty(t, result.Blocks)
36+
assert.NotEmpty(t, result.BlockSize)
37+
assert.NotEmpty(t, result.Metadata)
38+
}
39+
})
40+
}

pkg/fs/duf/filesystems.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package duf
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
)
8+
9+
func findMounts(mounts []Mount, path string) ([]Mount, error) {
10+
var err error
11+
path, err = filepath.Abs(path)
12+
if err != nil {
13+
return nil, err
14+
}
15+
16+
path, err = filepath.EvalSymlinks(path)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
_, err = os.Stat(path)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
var m []Mount
27+
for _, v := range mounts {
28+
if path == v.Device {
29+
return []Mount{v}, nil
30+
}
31+
32+
if strings.HasPrefix(path, v.Mountpoint) {
33+
var nm []Mount
34+
35+
// keep all entries that are as close or closer to the target
36+
for _, mv := range m {
37+
if len(mv.Mountpoint) >= len(v.Mountpoint) {
38+
nm = append(nm, mv)
39+
}
40+
}
41+
m = nm
42+
43+
// add entry only if we didn't already find something closer
44+
if len(nm) == 0 || len(v.Mountpoint) >= len(nm[0].Mountpoint) {
45+
m = append(m, v)
46+
}
47+
}
48+
}
49+
50+
return m, nil
51+
}
52+
53+
func deviceType(m Mount) string {
54+
if isNetworkFs(m) {
55+
return NetworkDevice
56+
}
57+
if isSpecialFs(m) {
58+
return SpecialDevice
59+
}
60+
if isFuseFs(m) {
61+
return FuseDevice
62+
}
63+
64+
return LocalDevice
65+
}
66+
67+
// remote: [ "nfs", "smbfs", "cifs", "ncpfs", "afs", "coda", "ftpfs", "mfs", "sshfs", "fuse.sshfs", "nfs4" ]
68+
// special: [ "tmpfs", "devpts", "devtmpfs", "proc", "sysfs", "usbfs", "devfs", "fdescfs", "linprocfs" ]

pkg/fs/duf/filesystems_darwin.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//go:build darwin
2+
3+
package duf
4+
5+
func isFuseFs(m Mount) bool {
6+
//FIXME: implement
7+
return false
8+
}
9+
10+
func isNetworkFs(m Mount) bool {
11+
//FIXME: implement
12+
return false
13+
}
14+
15+
func isSpecialFs(m Mount) bool {
16+
return m.Fstype == "devfs"
17+
}
18+
19+
func isHiddenFs(m Mount) bool {
20+
return false
21+
}

pkg/fs/duf/filesystems_freebsd.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build freebsd
2+
3+
package duf
4+
5+
func isFuseFs(m Mount) bool {
6+
//FIXME: implement
7+
return false
8+
}
9+
10+
func isNetworkFs(m Mount) bool {
11+
fs := []string{"nfs", "smbfs"}
12+
13+
for _, v := range fs {
14+
if m.Fstype == v {
15+
return true
16+
}
17+
}
18+
19+
return false
20+
}
21+
22+
func isSpecialFs(m Mount) bool {
23+
fs := []string{"devfs", "tmpfs", "linprocfs", "linsysfs", "fdescfs", "procfs"}
24+
25+
for _, v := range fs {
26+
if m.Fstype == v {
27+
return true
28+
}
29+
}
30+
31+
return false
32+
}
33+
34+
func isHiddenFs(m Mount) bool {
35+
return false
36+
}

0 commit comments

Comments
 (0)