-
Notifications
You must be signed in to change notification settings - Fork 357
Parsing Mountinfo to get information not in mountstats #173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
995bc4f
Add mountinfo parsing to procfs
d52e842
Return expressive error message
00e851a
Minor changes to comply with repo standards
36e5957
Slight changes to comply with requests from SuperQ
af9defb
Few more changes suggested by SuperQ
770427a
Reset copyright date for proc.go to 2018
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| // Copyright 2019 The Prometheus Authors | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package procfs | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "strconv" | ||
| "strings" | ||
| ) | ||
|
|
||
| var validOptionalFields = map[string]bool{ | ||
| "shared": true, | ||
| "master": true, | ||
| "propagate_from": true, | ||
| "unbindable": true, | ||
| } | ||
|
|
||
| // A MountInfo is a type that describes the details, options | ||
| // for each mount, parsed from /proc/self/mountinfo. | ||
| // The fields described in each entry of /proc/self/mountinfo | ||
| // is described in the following man page. | ||
| // http://man7.org/linux/man-pages/man5/proc.5.html | ||
| type MountInfo struct { | ||
| // Unique Id for the mount | ||
| MountId int | ||
| // The Id of the parent mount | ||
| ParentId int | ||
| // The value of `st_dev` for the files on this FS | ||
| MajorMinorVer string | ||
| // The pathname of the directory in the FS that forms | ||
| // the root for this mount | ||
| Root string | ||
| // The pathname of the mount point relative to the root | ||
| MountPoint string | ||
| // Mount options | ||
| Options map[string]string | ||
| // Zero or more optional fields | ||
| OptionalFields map[string]string | ||
| // The Filesystem type | ||
| FSType string | ||
| // FS specific information or "none" | ||
| Source string | ||
| // Superblock options | ||
| SuperOptions map[string]string | ||
| } | ||
|
|
||
| // Returns part of the mountinfo line, if it exists, else an empty string. | ||
| func getStringSliceElement(parts []string, idx int, defaultValue string) string { | ||
| if idx >= len(parts) { | ||
| return defaultValue | ||
| } | ||
| return parts[idx] | ||
| } | ||
|
|
||
| // Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs. | ||
| func parseMountInfo(r io.Reader) ([]*MountInfo, error) { | ||
| mounts := []*MountInfo{} | ||
| scanner := bufio.NewScanner(r) | ||
| for scanner.Scan() { | ||
| mountString := scanner.Text() | ||
| parsedMounts, err := parseMountInfoString(mountString) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| mounts = append(mounts, parsedMounts) | ||
| } | ||
|
|
||
| err := scanner.Err() | ||
| return mounts, err | ||
| } | ||
|
|
||
| // Parses a mountinfo file line, and converts it to a MountInfo struct. | ||
| // An important check here is to see if the hyphen separator, as if it does not exist, | ||
| // it means that the line is malformed. | ||
| func parseMountInfoString(mountString string) (*MountInfo, error) { | ||
| var err error | ||
|
|
||
| // OptionalFields can be zero, hence these checks to ensure we do not populate the wrong values in the wrong spots | ||
| separatorIndex := strings.Index(mountString, "-") | ||
| if separatorIndex == -1 { | ||
| return nil, fmt.Errorf("no separator found in mountinfo string: %s", mountString) | ||
| } | ||
| beforeFields := strings.Fields(mountString[:separatorIndex]) | ||
| afterFields := strings.Fields(mountString[separatorIndex+1:]) | ||
| if (len(beforeFields) + len(afterFields)) < 7 { | ||
| return nil, fmt.Errorf("too few fields") | ||
| } | ||
|
|
||
| mount := &MountInfo{ | ||
| MajorMinorVer: getStringSliceElement(beforeFields, 2, ""), | ||
| Root: getStringSliceElement(beforeFields, 3, ""), | ||
| MountPoint: getStringSliceElement(beforeFields, 4, ""), | ||
| Options: mountOptionsParser(getStringSliceElement(beforeFields, 5, "")), | ||
| OptionalFields: nil, | ||
| FSType: getStringSliceElement(afterFields, 0, ""), | ||
| Source: getStringSliceElement(afterFields, 1, ""), | ||
| SuperOptions: mountOptionsParser(getStringSliceElement(afterFields, 2, "")), | ||
| } | ||
|
|
||
| mount.MountId, err = strconv.Atoi(getStringSliceElement(beforeFields, 0, "")) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse mount ID") | ||
| } | ||
| mount.ParentId, err = strconv.Atoi(getStringSliceElement(beforeFields, 1, "")) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse parent ID") | ||
| } | ||
| // Has optional fields, which is a space separated list of values. | ||
| // Example: shared:2 master:7 | ||
| if len(beforeFields) > 6 { | ||
| mount.OptionalFields = make(map[string]string) | ||
| optionalFields := beforeFields[6:] | ||
| for _, field := range optionalFields { | ||
| optionSplit := strings.Split(field, ":") | ||
pgier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| target, value := optionSplit[0], "" | ||
| if len(optionSplit) == 2 { | ||
| value = optionSplit[1] | ||
| } | ||
| // Checks if the 'keys' in the optional fields in the mountinfo line are acceptable. | ||
| // Allowed 'keys' are shared, master, propagate_from, unbindable. | ||
| if _, ok := validOptionalFields[target]; ok { | ||
| mount.OptionalFields[target] = value | ||
| } | ||
| } | ||
| } | ||
| return mount, nil | ||
| } | ||
|
|
||
| // Parses the mount options, superblock options. | ||
| func mountOptionsParser(mountOptions string) map[string]string { | ||
| opts := make(map[string]string) | ||
| options := strings.Split(mountOptions, ",") | ||
| for _, opt := range options { | ||
| splitOption := strings.Split(opt, "=") | ||
| if len(splitOption) < 2 { | ||
| key := splitOption[0] | ||
| opts[key] = "" | ||
| } else { | ||
| key, value := splitOption[0], splitOption[1] | ||
| opts[key] = value | ||
| } | ||
| } | ||
| return opts | ||
| } | ||
|
|
||
| // Retrieves mountinfo information from `/proc/self/mountinfo`. | ||
| func GetMounts() ([]*MountInfo, error) { | ||
| f, err := os.Open("/proc/self/mountinfo") | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
| return parseMountInfo(f) | ||
| } | ||
|
|
||
| // Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`. | ||
| func GetProcMounts(pid int) ([]*MountInfo, error) { | ||
| f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
| return parseMountInfo(f) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| // Copyright 2019 The Prometheus Authors | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| package procfs | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestMountInfo(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| s string | ||
| mount *MountInfo | ||
| invalid bool | ||
| }{ | ||
| { | ||
| name: "Regular sysfs mounted at /sys", | ||
| s: "16 21 0:16 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw", | ||
| invalid: false, | ||
| mount: &MountInfo{ | ||
| MountId: 16, | ||
| ParentId: 21, | ||
| MajorMinorVer: "0:16", | ||
| Root: "/", | ||
| MountPoint: "/sys", | ||
| Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "noexec": "", "relatime": ""}, | ||
| OptionalFields: map[string]string{"shared": "7"}, | ||
| FSType: "sysfs", | ||
| Source: "sysfs", | ||
| SuperOptions: map[string]string{"rw": ""}, | ||
| }, | ||
| }, | ||
| { | ||
| name: "Not enough information", | ||
| s: "hello", | ||
| invalid: true, | ||
| }, | ||
| { | ||
| name: "Tmpfs mounted at /run", | ||
| 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", | ||
| mount: &MountInfo{ | ||
| MountId: 225, | ||
| ParentId: 20, | ||
| MajorMinorVer: "0:39", | ||
| Root: "/", | ||
| MountPoint: "/run/user/112", | ||
| Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""}, | ||
| OptionalFields: map[string]string{"shared": "177"}, | ||
| FSType: "tmpfs", | ||
| Source: "tmpfs", | ||
| SuperOptions: map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"}, | ||
| }, | ||
| invalid: false, | ||
| }, | ||
| { | ||
| name: "Tmpfs mounted at /run, but no optional values", | ||
| s: "225 20 0:39 / /run/user/112 rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=405096k,mode=700,uid=112,gid=116", | ||
| mount: &MountInfo{ | ||
| MountId: 225, | ||
| ParentId: 20, | ||
| MajorMinorVer: "0:39", | ||
| Root: "/", | ||
| MountPoint: "/run/user/112", | ||
| Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""}, | ||
| OptionalFields: nil, | ||
| FSType: "tmpfs", | ||
| Source: "tmpfs", | ||
| SuperOptions: map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"}, | ||
| }, | ||
| invalid: false, | ||
| }, | ||
| { | ||
| name: "Tmpfs mounted at /run, with multiple optional values", | ||
| 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", | ||
| mount: &MountInfo{ | ||
| MountId: 225, | ||
| ParentId: 20, | ||
| MajorMinorVer: "0:39", | ||
| Root: "/", | ||
| MountPoint: "/run/user/112", | ||
| Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""}, | ||
| OptionalFields: map[string]string{"shared": "177", "master": "8"}, | ||
| FSType: "tmpfs", | ||
| Source: "tmpfs", | ||
| SuperOptions: map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"}, | ||
| }, | ||
| invalid: false, | ||
| }, | ||
| { | ||
| name: "Tmpfs mounted at /run, with a mixture of valid and invalid optional values", | ||
| 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", | ||
| mount: &MountInfo{ | ||
| MountId: 225, | ||
| ParentId: 20, | ||
| MajorMinorVer: "0:39", | ||
| Root: "/", | ||
| MountPoint: "/run/user/112", | ||
| Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""}, | ||
| OptionalFields: map[string]string{"shared": "177", "master": "8"}, | ||
| FSType: "tmpfs", | ||
| Source: "tmpfs", | ||
| SuperOptions: map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"}, | ||
| }, | ||
| invalid: false, | ||
| }, | ||
| } | ||
|
|
||
| for i, test := range tests { | ||
| t.Logf("[%02d] test %q", i, test.name) | ||
|
|
||
| mount, err := parseMountInfoString(test.s) | ||
|
|
||
| if test.invalid && err == nil { | ||
| t.Error("expected an error, but none occurred") | ||
| } | ||
| if !test.invalid && err != nil { | ||
| t.Errorf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| if want, have := test.mount, mount; !reflect.DeepEqual(want, have) { | ||
| t.Errorf("mounts:\nwant:\n%+v\nhave:\n%+v", want, have) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.