github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/libsnap-confine-private/mountinfo.c (about) 1 /* 2 * Copyright (C) 2016 Canonical Ltd 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 3 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16 17 #include "mountinfo.h" 18 19 #include <errno.h> 20 #include <stdbool.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include "cleanup-funcs.h" 26 27 /** 28 * Parse a single mountinfo entry (line). 29 * 30 * The format, described by Linux kernel documentation, is as follows: 31 * 32 * 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue 33 * (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) 34 * 35 * (1) mount ID: unique identifier of the mount (may be reused after umount) 36 * (2) parent ID: ID of parent (or of self for the top of the mount tree) 37 * (3) major:minor: value of st_dev for files on filesystem 38 * (4) root: root of the mount within the filesystem 39 * (5) mount point: mount point relative to the process's root 40 * (6) mount options: per mount options 41 * (7) optional fields: zero or more fields of the form "tag[:value]" 42 * (8) separator: marks the end of the optional fields 43 * (9) filesystem type: name of filesystem of the form "type[.subtype]" 44 * (10) mount source: filesystem specific information or "none" 45 * (11) super options: per super block options 46 **/ 47 static sc_mountinfo_entry *sc_parse_mountinfo_entry(const char *line) 48 __attribute__((nonnull(1))); 49 50 /** 51 * Free a sc_mountinfo structure and all its entries. 52 **/ 53 static void sc_free_mountinfo(sc_mountinfo * info) 54 __attribute__((nonnull(1))); 55 56 /** 57 * Free a sc_mountinfo entry. 58 **/ 59 static void sc_free_mountinfo_entry(sc_mountinfo_entry * entry) 60 __attribute__((nonnull(1))); 61 62 sc_mountinfo_entry *sc_first_mountinfo_entry(sc_mountinfo * info) 63 { 64 return info->first; 65 } 66 67 sc_mountinfo_entry *sc_next_mountinfo_entry(sc_mountinfo_entry * entry) 68 { 69 return entry->next; 70 } 71 72 sc_mountinfo *sc_parse_mountinfo(const char *fname) 73 { 74 sc_mountinfo *info = calloc(1, sizeof *info); 75 if (info == NULL) { 76 return NULL; 77 } 78 if (fname == NULL) { 79 fname = "/proc/self/mountinfo"; 80 } 81 FILE *f SC_CLEANUP(sc_cleanup_file) = NULL; 82 f = fopen(fname, "rt"); 83 if (f == NULL) { 84 free(info); 85 return NULL; 86 } 87 char *line SC_CLEANUP(sc_cleanup_string) = NULL; 88 size_t line_size = 0; 89 sc_mountinfo_entry *entry, *last = NULL; 90 for (;;) { 91 errno = 0; 92 if (getline(&line, &line_size, f) == -1) { 93 if (errno != 0) { 94 sc_free_mountinfo(info); 95 return NULL; 96 } 97 break; 98 }; 99 entry = sc_parse_mountinfo_entry(line); 100 if (entry == NULL) { 101 sc_free_mountinfo(info); 102 return NULL; 103 } 104 if (last != NULL) { 105 last->next = entry; 106 } else { 107 info->first = entry; 108 } 109 last = entry; 110 } 111 return info; 112 } 113 114 static void show_buffers(const char *line, int offset, 115 sc_mountinfo_entry * entry) 116 { 117 #ifdef MOUNTINFO_DEBUG 118 fprintf(stderr, "Input buffer (first), with offset arrow\n"); 119 fprintf(stderr, "Output buffer (second)\n"); 120 121 fputc(' ', stderr); 122 for (int i = 0; i < offset - 1; ++i) 123 fputc('-', stderr); 124 fputc('v', stderr); 125 fputc('\n', stderr); 126 127 fprintf(stderr, ">%s<\n", line); 128 129 fputc('>', stderr); 130 for (size_t i = 0; i < strlen(line); ++i) { 131 int c = entry->line_buf[i]; 132 fputc(c == 0 ? '@' : c == 1 ? '#' : c, stderr); 133 } 134 fputc('<', stderr); 135 fputc('\n', stderr); 136 137 fputc('>', stderr); 138 for (size_t i = 0; i < strlen(line); ++i) 139 fputc('=', stderr); 140 fputc('<', stderr); 141 fputc('\n', stderr); 142 #endif // MOUNTINFO_DEBUG 143 } 144 145 static bool is_octal_digit(char c) 146 { 147 return c >= '0' && c <= '7'; 148 } 149 150 static char *parse_next_string_field(sc_mountinfo_entry * entry, 151 const char *line, size_t *offset) 152 { 153 const char *input = &line[*offset]; 154 char *output = &entry->line_buf[*offset]; 155 size_t input_idx = 0; // reading index 156 size_t output_idx = 0; // writing index 157 158 // Scan characters until we run out of memory to scan or we find a 159 // space. The kernel uses simple octal escape sequences for the 160 // following: space, tab, newline, backwards slash. Everything else is 161 // copied verbatim. 162 for (;;) { 163 int c = input[input_idx]; 164 if (c == '\0') { 165 // The string is over before we see anything then 166 // return NULL. This is an indication of end-of-input 167 // to the caller. 168 if (output_idx == 0) { 169 return NULL; 170 } 171 // The scanned line is NUL terminated. This ensures that the 172 // terminator is copied to the output buffer. 173 output[output_idx] = '\0'; 174 // NOTE: we must not advance the reading index since we 175 // reached the end of the buffer. 176 break; 177 } else if (c == ' ') { 178 // Fields are space delimited or end-of-string terminated. 179 // Represent either as the end-of-string marker, skip over it, 180 // and stop parsing by terminating the output, then 181 // breaking out of the loop but advancing the reading 182 // index which is needed for subsequent calls. 183 output[output_idx] = '\0'; 184 input_idx++; 185 break; 186 } else if (c == '\\') { 187 // Three *more* octal digits required for the escape 188 // sequence. For reference see mangle_path() in 189 // fs/seq_file.c. Note that is_octal_digit returns 190 // false on the string terminator character NUL and the 191 // short-circuiting behavior of && makes this check 192 // correct even if '\\' is the last character of the 193 // string. 194 const char *s = &input[input_idx]; 195 if (is_octal_digit(s[1]) && is_octal_digit(s[2]) 196 && is_octal_digit(s[3])) { 197 // Unescape the octal value encoded in s[1], 198 // s[2] and s[3]. Because we are working with 199 // byte values there are no issues related to 200 // byte order. 201 output[output_idx++] = 202 ((s[1] - '0') << 6) | 203 ((s[2] - '0') << 3) | ((s[3] - '0')); 204 // Advance the reading index by the length of the escape 205 // sequence. 206 input_idx += 4; 207 } else { 208 // Partial escape sequence, copy verbatim and 209 // continue (since we don't use this). 210 output[output_idx++] = c; 211 input_idx++; 212 } 213 } else { 214 // All other characters are simply copied verbatim. 215 output[output_idx++] = c; 216 input_idx++; 217 } 218 } 219 *offset += input_idx; 220 #ifdef MOUNTINFO_DEBUG 221 fprintf(stderr, 222 "\nscanned: >%s< (%zd bytes), input idx: %zd, output idx: %zd\n", 223 output, strlen(output), input_idx, output_idx); 224 #endif 225 show_buffers(line, *offset, entry); 226 return output; 227 } 228 229 static sc_mountinfo_entry *sc_parse_mountinfo_entry(const char *line) 230 { 231 // NOTE: the sc_mountinfo structure is allocated along with enough extra 232 // storage to hold the whole line we are parsing. This is used as backing 233 // store for all text fields. 234 // 235 // The idea is that since the line has a given length and we are only after 236 // set of substrings we can easily predict the amount of required space 237 // (after all, it is just a set of non-overlapping substrings) and append 238 // it to the allocated entry structure. 239 // 240 // The parsing code below, specifically parse_next_string_field(), uses 241 // this extra memory to hold data parsed from the original line. In the 242 // end, the result is similar to using strtok except that the source and 243 // destination buffers are separate. 244 // 245 // At the end of the parsing process, the input buffer (line) and the 246 // output buffer (entry->line_buf) are the same except for where spaces 247 // were converted into NUL bytes (string terminators) and except for the 248 // leading part of the buffer that contains mount_id, parent_id, dev_major 249 // and dev_minor integer fields that are parsed separately. 250 // 251 // If MOUNTINFO_DEBUG is defined then extra debugging is printed to stderr 252 // and this allows for visual analysis of what is going on. 253 sc_mountinfo_entry *entry = calloc(1, sizeof *entry + strlen(line) + 1); 254 if (entry == NULL) { 255 return NULL; 256 } 257 #ifdef MOUNTINFO_DEBUG 258 // Poison the buffer with '\1' bytes that are printed as '#' characters 259 // by show_buffers() below. This is "unaltered" memory. 260 memset(entry->line_buf, 1, strlen(line)); 261 #endif // MOUNTINFO_DEBUG 262 int nscanned, initial_offset = 0; 263 size_t offset = 0; 264 nscanned = sscanf(line, "%d %d %u:%u %n", 265 &entry->mount_id, &entry->parent_id, 266 &entry->dev_major, &entry->dev_minor, 267 &initial_offset); 268 if (nscanned != 4) 269 goto fail; 270 offset += initial_offset; 271 272 show_buffers(line, offset, entry); 273 274 if ((entry->root = 275 parse_next_string_field(entry, line, &offset)) == NULL) 276 goto fail; 277 if ((entry->mount_dir = 278 parse_next_string_field(entry, line, &offset)) == NULL) 279 goto fail; 280 if ((entry->mount_opts = 281 parse_next_string_field(entry, line, &offset)) == NULL) 282 goto fail; 283 entry->optional_fields = &entry->line_buf[0] + offset; 284 // NOTE: This ensures that optional_fields is never NULL. If this changes, 285 // must adjust all callers of parse_mountinfo_entry() accordingly. 286 for (int field_num = 0;; ++field_num) { 287 char *opt_field = parse_next_string_field(entry, line, &offset); 288 if (opt_field == NULL) 289 goto fail; 290 if (strcmp(opt_field, "-") == 0) { 291 opt_field[0] = 0; 292 break; 293 } 294 if (field_num > 0) { 295 opt_field[-1] = ' '; 296 } 297 } 298 if ((entry->fs_type = 299 parse_next_string_field(entry, line, &offset)) == NULL) 300 goto fail; 301 if ((entry->mount_source = 302 parse_next_string_field(entry, line, &offset)) == NULL) 303 goto fail; 304 if ((entry->super_opts = 305 parse_next_string_field(entry, line, &offset)) == NULL) 306 goto fail; 307 return entry; 308 fail: 309 free(entry); 310 return NULL; 311 } 312 313 void sc_cleanup_mountinfo(sc_mountinfo ** ptr) 314 { 315 if (*ptr != NULL) { 316 sc_free_mountinfo(*ptr); 317 *ptr = NULL; 318 } 319 } 320 321 static void sc_free_mountinfo(sc_mountinfo * info) 322 { 323 sc_mountinfo_entry *entry, *next; 324 for (entry = info->first; entry != NULL; entry = next) { 325 next = entry->next; 326 sc_free_mountinfo_entry(entry); 327 } 328 free(info); 329 } 330 331 static void sc_free_mountinfo_entry(sc_mountinfo_entry * entry) 332 { 333 free(entry); 334 }