Skip to content

Commit 434fb7d

Browse files
authored
filesystem: take super options into account for read-only (prometheus#3387)
* filesystem: take super options into account for read-only With the latest change implemented to use `mountinfo` instead of `mounts` there was a regression in filesystem readonly detection due to super options not taken into account: filesystems that would previously be marked a "read-only" would not anymore because that information had moved to super options instead of mount options on certain occasions. fixes prometheus#3157 Signed-off-by: nicbaz <[email protected]> * filesystem: use faster integer to string implementation Signed-off-by: nicbaz <[email protected]> --------- Signed-off-by: nicbaz <[email protected]>
1 parent b4c3b68 commit 434fb7d

File tree

3 files changed

+63
-14
lines changed

3 files changed

+63
-14
lines changed

collector/filesystem_common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ type filesystemCollector struct {
8282
}
8383

8484
type filesystemLabels struct {
85-
device, mountPoint, fsType, options, deviceError, major, minor string
85+
device, mountPoint, fsType, mountOptions, superOptions, deviceError, major, minor string
8686
}
8787

8888
type filesystemStats struct {

collector/filesystem_linux.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"io"
2424
"log/slog"
2525
"os"
26+
"slices"
27+
"strconv"
2628
"strings"
2729
"sync"
2830
"time"
@@ -110,11 +112,8 @@ func (c *filesystemCollector) GetStats() ([]filesystemStats, error) {
110112

111113
func (c *filesystemCollector) processStat(labels filesystemLabels) filesystemStats {
112114
var ro float64
113-
for _, option := range strings.Split(labels.options, ",") {
114-
if option == "ro" {
115-
ro = 1
116-
break
117-
}
115+
if isFilesystemReadOnly(labels) {
116+
ro = 1
118117
}
119118

120119
success := make(chan struct{})
@@ -198,7 +197,7 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
198197
for scanner.Scan() {
199198
parts := strings.Fields(scanner.Text())
200199

201-
if len(parts) < 10 {
200+
if len(parts) < 11 {
202201
return nil, fmt.Errorf("malformed mount point information: %q", scanner.Text())
203202
}
204203

@@ -219,15 +218,26 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
219218
parts[4] = strings.ReplaceAll(parts[4], "\\011", "\t")
220219

221220
filesystems = append(filesystems, filesystemLabels{
222-
device: parts[m+3],
223-
mountPoint: rootfsStripPrefix(parts[4]),
224-
fsType: parts[m+2],
225-
options: parts[5],
226-
major: fmt.Sprint(major),
227-
minor: fmt.Sprint(minor),
228-
deviceError: "",
221+
device: parts[m+3],
222+
mountPoint: rootfsStripPrefix(parts[4]),
223+
fsType: parts[m+2],
224+
mountOptions: parts[5],
225+
superOptions: parts[10],
226+
major: strconv.Itoa(major),
227+
minor: strconv.Itoa(minor),
228+
deviceError: "",
229229
})
230230
}
231231

232232
return filesystems, scanner.Err()
233233
}
234+
235+
// see https://github.com/prometheus/node_exporter/issues/3157#issuecomment-2422761187
236+
// if either mount or super options contain "ro" the filesystem is read-only
237+
func isFilesystemReadOnly(labels filesystemLabels) bool {
238+
if slices.Contains(strings.Split(labels.mountOptions, ","), "ro") || slices.Contains(strings.Split(labels.superOptions, ","), "ro") {
239+
return true
240+
}
241+
242+
return false
243+
}

collector/filesystem_linux_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,45 @@ func Test_parseFilesystemLabelsError(t *testing.T) {
4545
}
4646
}
4747

48+
func Test_isFilesystemReadOnly(t *testing.T) {
49+
tests := map[string]struct {
50+
labels filesystemLabels
51+
expected bool
52+
}{
53+
"/media/volume1": {
54+
labels: filesystemLabels{
55+
mountOptions: "rw,nosuid,nodev,noexec,relatime",
56+
superOptions: "rw,devices",
57+
},
58+
expected: false,
59+
},
60+
"/media/volume2": {
61+
labels: filesystemLabels{
62+
mountOptions: "ro,relatime",
63+
superOptions: "rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct",
64+
}, expected: true,
65+
},
66+
"/media/volume3": {
67+
labels: filesystemLabels{
68+
mountOptions: "rw,user_id=1000,group_id=1000",
69+
superOptions: "ro",
70+
}, expected: true,
71+
},
72+
"/media/volume4": {
73+
labels: filesystemLabels{
74+
mountOptions: "ro,nosuid,noexec",
75+
superOptions: "ro,nodev",
76+
}, expected: true,
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
if got := isFilesystemReadOnly(tt.labels); got != tt.expected {
82+
t.Errorf("Expected %t, got %t", tt.expected, got)
83+
}
84+
}
85+
}
86+
4887
func TestMountPointDetails(t *testing.T) {
4988
if _, err := kingpin.CommandLine.Parse([]string{"--path.procfs", "./fixtures/proc"}); err != nil {
5089
t.Fatal(err)

0 commit comments

Comments
 (0)