master
c 287 lines 7.43 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <stdbool.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <sys/types.h>
8 #include <sys/sysmacros.h>
9 #include "mountutil.h"
10
11 #define MOUNT_FIELD_SEP " "
12 #define MOUNT_OPTIONAL_FIELD_TERMINATOR "-"
13 #define MOUNT_DEVICE_SEP ':'
14 #define MOUNT_ESCAPE_CHAR '\\'
15 #define MOUNT_ESCAPE_LENGTH (3)
16
17 // Field indices of fields in the /proc/self/mountinfo file.
18 // N.B. There can be one or more optional fields, terminated by a single hyphen. This enumeration
19 // counts all optional fields including the hyphen separator as a single field.
20 typedef enum _MOUNT_FIELD
21 {
22 MountFieldId,
23 MountFieldParentId,
24 MountFieldDevice,
25 MountFieldRoot,
26 MountFieldMountPoint,
27 MountFieldMountOptions,
28 MountFieldOptionalFields,
29 MountFieldFileSystemType,
30 MountFieldSource,
31 MountFieldSuperOptions,
32 MountFieldMax = MountFieldSuperOptions
33 } MOUNT_FIELD,
34 *PMOUNT_FIELD;
35
36 void MountFieldUnescape(char* field);
37
38 bool MountFieldUnescapeOctal(const char* string, char* unescaped);
39
40 char* MountNextField(char** line, MOUNT_FIELD field);
41
42 int MountParseDevice(char* field, dev_t* device);
43
44 // Initializes a MOUNT_ENUM structure.
45 int MountEnumCreate(PMOUNT_ENUM mountEnum)
46 {
47 return MountEnumCreateEx(mountEnum, MOUNT_INFO_FILE);
48 }
49
50 // Initializes a MOUNT_ENUM structure from a custom mount info file.
51 int MountEnumCreateEx(PMOUNT_ENUM mountEnum, const char* mountInfoFile)
52 {
53 int result;
54
55 memset(mountEnum, 0, sizeof(*mountEnum));
56 mountEnum->MountInfo = fopen(mountInfoFile, "r");
57 if (mountEnum->MountInfo == NULL)
58 {
59 result = -1;
60 goto MountEnumCreateEnd;
61 }
62
63 result = 0;
64
65 MountEnumCreateEnd:
66 return result;
67 }
68
69 // Frees the resources held by a MOUNT_ENUM structure.
70 void MountEnumFree(PMOUNT_ENUM mountEnum)
71 {
72 if (mountEnum->Line != NULL)
73 {
74 free(mountEnum->Line);
75 }
76
77 if (mountEnum->MountInfo != NULL)
78 {
79 fclose(mountEnum->MountInfo);
80 }
81 }
82
83 // Reads the next line of the /proc/self/mountinfo file, and parses it.
84 int MountEnumNext(PMOUNT_ENUM mountEnum)
85 {
86 ssize_t bytesRead;
87 int result;
88
89 do
90 {
91 // Get the next line.
92 // N.B. Set errno to zero first so EOF can be distinguished.
93 errno = 0;
94 bytesRead = getline(&mountEnum->Line, &mountEnum->LineLength, mountEnum->MountInfo);
95 if (bytesRead < 0)
96 {
97 result = -1;
98 goto MountEnumNextEnd;
99 }
100
101 // Parse the line. Invalid lines are skipped.
102 result = MountParseMountInfoLine(mountEnum->Line, &mountEnum->Current);
103 } while (result < 0);
104
105 result = 0;
106
107 MountEnumNextEnd:
108 return result;
109 }
110
111 // Unescape a field in a mount entry that uses octal escape sequences.
112 void MountFieldUnescape(char* field)
113 {
114 size_t index;
115 size_t insertionIndex;
116 char unescaped;
117
118 // Scan until the end of the string.
119 // N.B. The last field may contain a newline character, which is stripped
120 // by this function.
121 for (index = 0, insertionIndex = 0; field[index] != '\0'; index += 1, insertionIndex += 1)
122 {
123 // If the character is a \, see if the following three characters can
124 // be unescaped. Otherwise, move the current character back if a
125 // previous escape sequence was encountered.
126 if ((field[index] == MOUNT_ESCAPE_CHAR) && (MountFieldUnescapeOctal(&field[index + 1], &unescaped) != false))
127 {
128 field[insertionIndex] = unescaped;
129 index += MOUNT_ESCAPE_LENGTH;
130 }
131 else if (insertionIndex < index)
132 {
133 field[insertionIndex] = field[index];
134 }
135 }
136
137 // NULL terminate the field (if there were no escapes, this will just overwrite the existing
138 // terminator).
139 field[insertionIndex] = '\0';
140 }
141
142 // Unescape a single octal escape sequence.
143 bool MountFieldUnescapeOctal(const char* string, char* unescaped)
144 {
145 char localUnescaped;
146 char character;
147 size_t index;
148
149 localUnescaped = 0;
150 for (index = 0; index < MOUNT_ESCAPE_LENGTH; index += 1)
151 {
152 character = string[index];
153 if ((character < '0') || (character > '7'))
154 {
155 return false;
156 }
157
158 localUnescaped <<= 3;
159 localUnescaped |= (character - '0');
160 }
161
162 *unescaped = localUnescaped;
163 return true;
164 }
165
166 // Find the next field in the mountinfo line, and make sure it's NULL terminated.
167 // N.B. The start of the line is updated to the remaining text after the returned field.
168 char* MountNextField(char** line, MOUNT_FIELD field)
169 {
170 if (field == 0)
171 {
172 return strtok_r(*line, MOUNT_FIELD_SEP, line);
173 }
174 else if (field < MountFieldMax)
175 {
176 return strtok_r(NULL, MOUNT_FIELD_SEP, line);
177 }
178
179 // The last field may contain separators, so don't look for the next separator in that case.
180 // However, it may end with a newline.
181 return strtok_r(NULL, "\n", line);
182 }
183
184 // Parse a device number of the form "major:minor" into a dev_t.
185 int MountParseDevice(char* field, dev_t* device)
186 {
187 int major;
188 int minor;
189 int result;
190 char* separator;
191
192 separator = strchr(field, MOUNT_DEVICE_SEP);
193 if (separator == NULL)
194 {
195 result = -1;
196 goto MountParseDeviceEnd;
197 }
198
199 *separator = '\0';
200 major = atoi(field);
201 minor = atoi(separator + 1);
202 *device = makedev(major, minor);
203 result = 0;
204
205 MountParseDeviceEnd:
206 return result;
207 }
208
209 // Parse a line from the /proc/self/mountinfo file.
210 int MountParseMountInfoLine(char* line, PMOUNT_ENTRY entry)
211 {
212 char* current;
213 MOUNT_FIELD field;
214 int result;
215
216 for (field = 0, current = MountNextField(&line, field); ((current != NULL) && (field <= MountFieldMax));
217 field += 1, current = MountNextField(&line, field))
218 {
219 switch (field)
220 {
221 case MountFieldId:
222 entry->Id = atoi(current);
223 break;
224
225 case MountFieldParentId:
226 entry->ParentId = atoi(current);
227 break;
228
229 case MountFieldDevice:
230 result = MountParseDevice(current, &entry->Device);
231 if (result < 0)
232 {
233 goto ParseMountInfoLineEnd;
234 }
235 break;
236
237 case MountFieldRoot:
238 entry->Root = current;
239 break;
240
241 case MountFieldMountPoint:
242 entry->MountPoint = current;
243 break;
244
245 case MountFieldMountOptions:
246 entry->MountOptions = current;
247 break;
248
249 case MountFieldOptionalFields:
250 // Find the end of the optional fields.
251 while (current != NULL && strcmp(current, MOUNT_OPTIONAL_FIELD_TERMINATOR) != 0)
252 {
253 current = MountNextField(&line, field);
254 }
255
256 break;
257
258 case MountFieldFileSystemType:
259 entry->FileSystemType = current;
260 break;
261
262 case MountFieldSource:
263 entry->Source = current;
264 break;
265
266 case MountFieldSuperOptions:
267 entry->SuperOptions = current;
268 break;
269 }
270 }
271
272 // Check if all the fields were found. If not, this is a malformed line.
273 if (field <= MountFieldMax)
274 {
275 result = -1;
276 goto ParseMountInfoLineEnd;
277 }
278
279 // Decode any octal escape sequences in the fields that may be escaped.
280 MountFieldUnescape(entry->Root);
281 MountFieldUnescape(entry->Source);
282 MountFieldUnescape(entry->MountPoint);
283 result = 0;
284
285 ParseMountInfoLineEnd:
286 return result;
287 }