forked from dell/gofsutil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgofsutil_mount.go
291 lines (252 loc) · 8.83 KB
/
gofsutil_mount.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Copyright © 2022 Dell Inc. or its subsidiaries. All Rights Reserved.
//
// 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 gofsutil
import (
"bufio"
"context"
"fmt"
"hash/fnv"
"io"
"path"
"regexp"
"strings"
)
// ProcMountsFields is fields per line in procMountsPath as per
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
const ProcMountsFields = 9
// Info describes a mounted filesystem.
//
// Please note that all fields that represent filesystem paths must
// be absolute and not contain any symlinks.
type Info struct {
// Device is the filesystem path of the device to which the filesystem is
// mounted.
Device string
// Path is the filesystem path to which Device is mounted.
Path string
// Source may be set to one of two values:
//
// 1. If this is a bind mount created with "bindfs" then Source
// is set to the filesystem path (absolute, no symlinks)
// bind mounted to Path.
//
// 2. If this is any other type of mount then Source is set to
// a concatenation of the mount source and the root of
// the mount within the file system (fields 10 & 4 from
// the section on /proc/<pid>/mountinfo at
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt).
//
// It is not possible to diffentiate a native bind mount from a
// non-bind mount after the native bind mount has been created.
// Therefore, while the Source field will be set to the filesystem
// path bind mounted to Path for native bind mounts, the value of
// the Source field can in no way be used to determine *if* a mount
// is a bind mount.
Source string
// Type is the filesystem type.
Type string
// Opts are the mount options (https://linux.die.net/man/8/mount)
// used to mount the filesystem.
Opts []string
}
// DeviceMountInfo describes the filesystem mount information
// related to the mounted CSI device
type DeviceMountInfo struct {
DeviceNames []string
MPathName string
PPathName string
MountPoint string
}
// Entry is a superset of Info and maps to the fields of a mount table
// entry:
//
// (1) mount ID: unique identifier of the mount (may be reused after umount)
// (2) parent ID: ID of parent (or of self for the top of the mount tree)
// (3) major:minor: value of st_dev for files on filesystem
// (4) root: root of the mount within the filesystem
// (5) mount point: mount point relative to the process's root
// (6) mount options: per mount options
// (7) optional fields: zero or more fields of the form "tag[:value]"
// (8) separator: marks the end of the optional fields
// (9) filesystem type: name of filesystem of the form "type[.subtype]"
// (10) mount source: filesystem specific information or "none"
// (11) super options: per super block options
type Entry struct {
// Root of the mount within the filesystem.
Root string
// MountPoint relative to the process's root
MountPoint string
// MountOpts are per-mount options.
MountOpts []string
// FSType is the name of filesystem of the form "type[.subtype]".
FSType string
// MountSource is filesystem specific information or "none"
MountSource string
}
// EntryScanFunc defines the signature of the function that is optionally
// provided to the functions in this package that scan the mount table.
// The mount entry table is ignored when this function returns a false
// value or error.
type EntryScanFunc func(
ctx context.Context,
entry Entry,
cache map[string]Entry) (Info, bool, error)
// DefaultEntryScanFunc returns the default entry scan function.
func DefaultEntryScanFunc() EntryScanFunc {
return defaultEntryScanFunc
}
func defaultEntryScanFunc(
_ context.Context,
entry Entry,
cache map[string]Entry,
) (info Info, valid bool, failed error) {
// Validate the mount table entry.
validFSType, _ := regexp.MatchString(
`(?i)^devtmpfs|(?:fuse\..*)|(?:nfs\d?)$`, entry.FSType)
sourceHasSlashPrefix := strings.HasPrefix(entry.MountSource, "/")
if valid = validFSType || sourceHasSlashPrefix; !valid {
return
}
// Copy the Entry object's fields to the Info object.
info.Device = entry.MountSource
info.Opts = make([]string, len(entry.MountOpts))
copy(info.Opts, entry.MountOpts)
info.Path = entry.MountPoint
info.Type = entry.FSType
info.Source = entry.MountSource
// If this is the first time a source is encountered in the
// output then cache its mountPoint field as the filesystem path
// to which the source is mounted as a non-bind mount.
//
// Subsequent encounters with the source will resolve it
// to the cached root value in order to set the mount info's
// Source field to the the cached mountPont field value + the
// value of the current line's root field.
if cachedEntry, ok := cache[entry.MountSource]; ok {
info.Source = path.Join(cachedEntry.MountPoint, entry.Root)
} else {
cache[entry.MountSource] = entry
}
return
}
/*
ReadProcMountsFrom parses the contents of a mount table file, typically
"/proc/self/mountinfo".
From https://www.kernel.org/doc/Documentation/filesystems/proc.txt:
3.5 /proc/<pid>/mountinfo - Information about mounts
--------------------------------------------------------
This file contains lines of the form:
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options
Parsers should ignore all unrecognised optional fields. Currently the
possible optional fields are:
shared:X mount is shared in peer group X
master:X mount is slave to peer group X
propagate_from:X mount is slave and receives propagation from peer group X (*)
unbindable mount is unbindable
*/
func ReadProcMountsFrom(
ctx context.Context,
file io.Reader,
_ bool,
expectedFields int,
scanEntry EntryScanFunc,
) ([]Info, uint32, error) {
if scanEntry == nil {
scanEntry = defaultEntryScanFunc
}
var (
infos []Info
hash = fnv.New32a()
fscan = bufio.NewScanner(file)
cache = map[string]Entry{}
)
for fscan.Scan() {
// Read the next line of text and attempt to parse it into
// distinct, space-separated fields.
line := fscan.Text()
fields := strings.Fields(line)
// Remove the optional fields that should be ignored.
for {
val := fields[6]
fields = append(fields[:6], fields[7:]...)
if val == "-" {
break
}
}
if len(fields) != expectedFields {
return nil, 0, fmt.Errorf(
"readProcMountsFrom: invalid field count: exp=%d, act=%d: %s",
expectedFields, len(fields), line)
}
// Create a new Entry object from the mount table entry.
e := Entry{
Root: fields[3],
MountPoint: fields[4],
MountOpts: strings.Split(fields[5], ","),
FSType: fields[6],
MountSource: fields[7],
}
// If the ScanFunc indicates the mount table entry is invalid
// then do not consider it for the checksum and continue to the
// next mount table entry.
i, valid, err := scanEntry(ctx, e, cache)
if err != nil {
return nil, 0, err
}
if !valid {
continue
}
fmt.Fprint(hash, line)
infos = append(infos, i)
}
return infos, hash.Sum32(), nil
}
// MakeMountArgs makes the arguments to the mount(8) command.
//
// The argument list returned is built as follows:
//
// mount [-t $fsType] [-o $options] [$source] $target
func MakeMountArgs(
_ context.Context,
source, target, fsType string,
opts ...string,
) []string {
args := []string{}
if len(fsType) > 0 {
args = append(args, "-t", fsType)
}
if len(opts) > 0 {
// Remove any duplicates or empty options from the provided list and
// check the length of the list once more in case the list is now empty
// once empty options were removed.
if opts = RemoveDuplicates(opts); len(opts) > 0 {
args = append(args, "-o", strings.Join(opts, ","))
}
}
if len(source) > 0 {
args = append(args, source)
}
args = append(args, target)
return args
}