1// Copyright 2019 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13package procfs
14
15import (
16	"reflect"
17	"testing"
18)
19
20func TestMountInfo(t *testing.T) {
21	tests := []struct {
22		name    string
23		s       string
24		mount   *MountInfo
25		invalid bool
26	}{
27		{
28			name:    "Regular sysfs mounted at /sys",
29			s:       "16 21 0:16 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw",
30			invalid: false,
31			mount: &MountInfo{
32				MountID:        16,
33				ParentID:       21,
34				MajorMinorVer:  "0:16",
35				Root:           "/",
36				MountPoint:     "/sys",
37				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "noexec": "", "relatime": ""},
38				OptionalFields: map[string]string{"shared": "7"},
39				FSType:         "sysfs",
40				Source:         "sysfs",
41				SuperOptions:   map[string]string{"rw": ""},
42			},
43		},
44		{
45			name:    "Not enough information",
46			s:       "hello",
47			invalid: true,
48		},
49		{
50			name: "Tmpfs mounted at /run",
51			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
52			mount: &MountInfo{
53				MountID:        225,
54				ParentID:       20,
55				MajorMinorVer:  "0:39",
56				Root:           "/",
57				MountPoint:     "/run/user/112",
58				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
59				OptionalFields: map[string]string{"shared": "177"},
60				FSType:         "tmpfs",
61				Source:         "tmpfs",
62				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
63			},
64			invalid: false,
65		},
66		{
67			name: "Tmpfs mounted at /run, but no optional values",
68			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime  - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
69			mount: &MountInfo{
70				MountID:        225,
71				ParentID:       20,
72				MajorMinorVer:  "0:39",
73				Root:           "/",
74				MountPoint:     "/run/user/112",
75				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
76				OptionalFields: nil,
77				FSType:         "tmpfs",
78				Source:         "tmpfs",
79				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
80			},
81			invalid: false,
82		},
83		{
84			name: "Tmpfs mounted at /run, with multiple optional values",
85			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
86			mount: &MountInfo{
87				MountID:        225,
88				ParentID:       20,
89				MajorMinorVer:  "0:39",
90				Root:           "/",
91				MountPoint:     "/run/user/112",
92				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
93				OptionalFields: map[string]string{"shared": "177", "master": "8"},
94				FSType:         "tmpfs",
95				Source:         "tmpfs",
96				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
97			},
98			invalid: false,
99		},
100		{
101			name: "Tmpfs mounted at /run, with a mixture of valid and invalid optional values",
102			s:    "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime shared:177 master:8 foo:bar - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116",
103			mount: &MountInfo{
104				MountID:        225,
105				ParentID:       20,
106				MajorMinorVer:  "0:39",
107				Root:           "/",
108				MountPoint:     "/run/user/112",
109				Options:        map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""},
110				OptionalFields: map[string]string{"shared": "177", "master": "8"},
111				FSType:         "tmpfs",
112				Source:         "tmpfs",
113				SuperOptions:   map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"},
114			},
115			invalid: false,
116		},
117		{
118			name: "CIFS mounted at /with/a-hyphen",
119			s:    "454 29 0:87 / /with/a-hyphen rw,relatime shared:255 - cifs //remote-storage/Path rw,vers=3.1.1,cache=strict,username=user,uid=1000,forceuid,gid=0,noforcegid,addr=127.0.0.1,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,echo_interval=60,actimeo=1",
120			mount: &MountInfo{
121				MountID:        454,
122				ParentID:       29,
123				MajorMinorVer:  "0:87",
124				Root:           "/",
125				MountPoint:     "/with/a-hyphen",
126				Options:        map[string]string{"rw": "", "relatime": ""},
127				OptionalFields: map[string]string{"shared": "255"},
128				FSType:         "cifs",
129				Source:         "//remote-storage/Path",
130				SuperOptions:   map[string]string{"rw": "", "vers": "3.1.1", "cache": "strict", "username": "user", "uid": "1000", "forceuid": "", "gid": "0", "noforcegid": "", "addr": "127.0.0.1", "file_mode": "0755", "dir_mode": "0755", "soft": "", "nounix": "", "serverino": "", "mapposix": "", "echo_interval": "60", "actimeo": "1"},
131			},
132			invalid: false,
133		},
134		{
135			name: "Docker overlay with 10 fields (no optional fields)",
136			s:    "137 45 253:2 /lib/docker/overlay2 /var/lib/docker/overlay2 rw,relatime - ext4 /dev/mapper/vg0-lv_var rw,data=ordered",
137			mount: &MountInfo{
138				MountID:        137,
139				ParentID:       45,
140				MajorMinorVer:  "253:2",
141				Root:           "/lib/docker/overlay2",
142				MountPoint:     "/var/lib/docker/overlay2",
143				Options:        map[string]string{"rw": "", "relatime": ""},
144				OptionalFields: map[string]string{},
145				FSType:         "ext4",
146				Source:         "/dev/mapper/vg0-lv_var",
147				SuperOptions:   map[string]string{"rw": "", "data": "ordered"},
148			},
149		},
150		{
151			name: "bind chroot bind mount with 10 fields (no optional fields)",
152			s:    "157 47 253:2 /etc/named /var/named/chroot/etc/named rw,relatime - ext4 /dev/mapper/vg0-lv_root rw,data=ordered",
153			mount: &MountInfo{
154				MountID:        157,
155				ParentID:       47,
156				MajorMinorVer:  "253:2",
157				Root:           "/etc/named",
158				MountPoint:     "/var/named/chroot/etc/named",
159				Options:        map[string]string{"rw": "", "relatime": ""},
160				OptionalFields: map[string]string{},
161				FSType:         "ext4",
162				Source:         "/dev/mapper/vg0-lv_root",
163				SuperOptions:   map[string]string{"rw": "", "data": "ordered"},
164			},
165		},
166	}
167
168	for i, test := range tests {
169		t.Logf("[%02d] test %q", i, test.name)
170
171		mount, err := parseMountInfoString(test.s)
172
173		if test.invalid && err == nil {
174			t.Error("expected an error, but none occurred")
175		}
176		if !test.invalid && err != nil {
177			t.Errorf("unexpected error: %v", err)
178		}
179
180		if want, have := test.mount, mount; !reflect.DeepEqual(want, have) {
181			t.Errorf("mounts:\nwant:\n%+v\nhave:\n%+v", want, have)
182		}
183	}
184}
185