github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  }