diff options
Diffstat (limited to 'libmount/src/tab_parse.c')
-rw-r--r-- | libmount/src/tab_parse.c | 1343 |
1 files changed, 1343 insertions, 0 deletions
diff --git a/libmount/src/tab_parse.c b/libmount/src/tab_parse.c new file mode 100644 index 0000000..fa2d31b --- /dev/null +++ b/libmount/src/tab_parse.c @@ -0,0 +1,1343 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com> + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ +#ifdef HAVE_SCANDIRAT +#ifndef __USE_GNU +#define __USE_GNU +#endif /* !__USE_GNU */ +#endif /* HAVE_SCANDIRAT */ + +#include <ctype.h> +#include <limits.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include "fileutils.h" +#include "mangle.h" +#include "mountP.h" +#include "pathnames.h" +#include "strutils.h" + +struct libmnt_parser { + FILE *f; /* fstab, mtab, swaps or mountinfo ... */ + const char *filename; /* file name or NULL */ + char *buf; /* buffer (the current line content) */ + size_t bufsiz; /* size of the buffer */ + size_t line; /* current line */ +}; + +static void parser_cleanup(struct libmnt_parser *pa) +{ + if (!pa) + return; + free(pa->buf); + memset(pa, 0, sizeof(*pa)); +} + +static const char *next_s32(const char *s, int *num, int *rc) +{ + char *end = NULL; + + if (!s || !*s) + return s; + + *rc = -EINVAL; + *num = strtol(s, &end, 10); + if (end == NULL || s == end) + return s; + if (*end == ' ' || *end == '\t' || *end == '\0') + *rc = 0; + return end; +} + +static const char *next_u64(const char *s, uint64_t *num, int *rc) +{ + char *end = NULL; + + if (!s || !*s) + return s; + + *rc = -EINVAL; + *num = (uint64_t) strtoumax(s, &end, 10); + if (end == NULL || s == end) + return s; + if (*end == ' ' || *end == '\t' || *end == '\0') + *rc = 0; + return end; +} + +static inline const char *skip_separator(const char *p) +{ + while (p && (*p == ' ' || *p == '\t')) + ++p; + return p; +} + +static inline const char *skip_nonspearator(const char *p) +{ + while (p && *p && !(*p == ' ' || *p == '\t')) + p++; + return p; +} + +/* + * Parses one line from {fs,m}tab + */ +static int mnt_parse_table_line(struct libmnt_fs *fs, const char *s) +{ + int rc = 0; + char *p = NULL; + + fs->passno = fs->freq = 0; + + /* (1) source */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [source]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (2) target */ + fs->target = unmangle(s, &s); + if (!fs->target) { + DBG(TAB, ul_debug("tab parse error: [target]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) FS type */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [fstype]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (4) options (optional) */ + p = unmangle(s, &s); + if (p && (rc = mnt_fs_set_options(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [options]")); + free(p); + goto fail; + } + if (!p) + goto done; + free(p); + + s = skip_separator(s); + if (!s || !*s) + goto done; + + /* (5) freq (optional) */ + s = next_s32(s, &fs->freq, &rc); + if (s && *s && rc) { + DBG(TAB, ul_debug("tab parse error: [freq]")); + goto fail; + } + + s = skip_separator(s); + if (!s || !*s) + goto done; + + /* (6) passno (optional) */ + s = next_s32(s, &fs->passno, &rc); + if (s && *s && rc) { + DBG(TAB, ul_debug("tab parse error: [passno]")); + goto fail; + } + +done: + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + + +/* + * Parses one line from a mountinfo file + */ +static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s) +{ + int rc = 0; + unsigned int maj, min; + char *p; + + fs->flags |= MNT_FS_KERNEL; + + /* (1) id */ + s = next_s32(s, &fs->id, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [id]")); + goto fail; + } + + s = skip_separator(s); + + /* (2) parent */ + s = next_s32(s, &fs->parent, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [parent]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) maj:min */ + if (sscanf(s, "%u:%u", &maj, &min) != 2) { + DBG(TAB, ul_debug("tab parse error: [maj:min]")); + goto fail; + } + fs->devno = makedev(maj, min); + s = skip_nonspearator(s); + s = skip_separator(s); + + /* (4) mountroot */ + fs->root = unmangle(s, &s); + if (!fs->root) { + DBG(TAB, ul_debug("tab parse error: [mountroot]")); + goto fail; + } + + s = skip_separator(s); + + /* (5) target */ + fs->target = unmangle(s, &s); + if (!fs->target) { + DBG(TAB, ul_debug("tab parse error: [target]")); + goto fail; + } + + /* remove "\040(deleted)" suffix */ + p = (char *) endswith(fs->target, PATH_DELETED_SUFFIX); + if (p && *p) + *p = '\0'; + + s = skip_separator(s); + + /* (6) vfs options (fs-independent) */ + fs->vfs_optstr = unmangle(s, &s); + if (!fs->vfs_optstr) { + DBG(TAB, ul_debug("tab parse error: [VFS options]")); + goto fail; + } + + /* (7) optional fields, terminated by " - " */ + p = strstr(s, " - "); + if (!p) { + DBG(TAB, ul_debug("mountinfo parse error: separator not found")); + return -EINVAL; + } + if (p > s + 1) + fs->opt_fields = strndup(s + 1, p - s - 1); + + s = skip_separator(p + 3); + + /* (8) FS type */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [fstype]")); + free(p); + goto fail; + } + + /* (9) source -- maybe empty string */ + if (!s || !*s) { + DBG(TAB, ul_debug("tab parse error: [source]")); + goto fail; + } else if (*s == ' ' && *(s+1) == ' ') { + if ((rc = mnt_fs_set_source(fs, ""))) { + DBG(TAB, ul_debug("tab parse error: [empty source]")); + goto fail; + } + } else { + s = skip_separator(s); + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [regular source]")); + free(p); + goto fail; + } + } + + s = skip_separator(s); + + /* (10) fs options (fs specific) */ + fs->fs_optstr = unmangle(s, &s); + if (!fs->fs_optstr) { + DBG(TAB, ul_debug("tab parse error: [FS options]")); + goto fail; + } + + /* merge VFS and FS options to one string */ + fs->optstr = mnt_fs_strdup_options(fs); + if (!fs->optstr) { + rc = -ENOMEM; + DBG(TAB, ul_debug("tab parse error: [merge VFS and FS options]")); + goto fail; + } + + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + +/* + * Parses one line from utab file + */ +static int mnt_parse_utab_line(struct libmnt_fs *fs, const char *s) +{ + const char *p = s; + + assert(fs); + assert(s); + assert(!fs->source); + assert(!fs->target); + + while (p && *p) { + const char *end = NULL; + + while (*p == ' ') p++; + if (!*p) + break; + + if (!fs->source && !strncmp(p, "SRC=", 4)) { + char *v = unmangle(p + 4, &end); + if (!v) + goto enomem; + if (__mnt_fs_set_source_ptr(fs, v)) + free(v); + + } else if (!fs->target && !strncmp(p, "TARGET=", 7)) { + fs->target = unmangle(p + 7, &end); + if (!fs->target) + goto enomem; + + } else if (!fs->root && !strncmp(p, "ROOT=", 5)) { + fs->root = unmangle(p + 5, &end); + if (!fs->root) + goto enomem; + + } else if (!fs->bindsrc && !strncmp(p, "BINDSRC=", 8)) { + fs->bindsrc = unmangle(p + 8, &end); + if (!fs->bindsrc) + goto enomem; + + } else if (!fs->user_optstr && !strncmp(p, "OPTS=", 5)) { + fs->user_optstr = unmangle(p + 5, &end); + if (!fs->user_optstr) + goto enomem; + + } else if (!fs->attrs && !strncmp(p, "ATTRS=", 6)) { + fs->attrs = unmangle(p + 6, &end); + if (!fs->attrs) + goto enomem; + + } else { + /* unknown variable */ + while (*p && *p != ' ') p++; + } + if (end) + p = end; + } + + return 0; +enomem: + DBG(TAB, ul_debug("utab parse error: ENOMEM")); + return -ENOMEM; +} + +/* + * Parses one line from /proc/swaps + */ +static int mnt_parse_swaps_line(struct libmnt_fs *fs, const char *s) +{ + uint64_t num; + int rc = 0; + char *p; + + /* (1) source */ + p = unmangle(s, &s); + if (p) { + char *x = (char *) endswith(p, PATH_DELETED_SUFFIX); + if (x && *x) + *x = '\0'; + } + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [source]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (2) type */ + fs->swaptype = unmangle(s, &s); + if (!fs->swaptype) { + DBG(TAB, ul_debug("tab parse error: [swaptype]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) size */ + s = next_u64(s, &num, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [size]")); + goto fail; + } + fs->size = num; + + s = skip_separator(s); + + /* (4) size */ + s = next_u64(s, &num, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [used size]")); + goto fail; + } + fs->usedsize = num; + + s = skip_separator(s); + + /* (5) priority */ + s = next_s32(s, &fs->priority, &rc); + if (rc) { + DBG(TAB, ul_debug("tab parse error: [priority]")); + goto fail; + } + + mnt_fs_set_fstype(fs, "swap"); + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + + +/* + * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) + * + * Note that we aren't trying to guess the utab file format, because this file + * always has to be parsed by private libmount routines with an explicitly defined + * format. + * + * mountinfo: "<number> <number> ... " + */ +static int guess_table_format(const char *line) +{ + unsigned int a, b; + + DBG(TAB, ul_debug("trying to guess table type")); + + if (sscanf(line, "%u %u", &a, &b) == 2) + return MNT_FMT_MOUNTINFO; + + if (strncmp(line, "Filename\t", 9) == 0) + return MNT_FMT_SWAPS; + + return MNT_FMT_FSTAB; /* fstab, mtab or /proc/mounts */ +} + +static int is_comment_line(const char *line) +{ + const char *p = skip_blank(line); + + if (p && (*p == '#' || *p == '\n')) + return 1; + return 0; +} + +/* returns 1 if the last line in the @str is blank */ +static int is_terminated_by_blank(const char *str) +{ + size_t sz = str ? strlen(str) : 0; + const char *p = sz ? str + (sz - 1) : NULL; + + if (!sz || !p || *p != '\n') + return 0; /* empty or not terminated by '\n' */ + if (p == str) + return 1; /* only '\n' */ + p--; + while (p >= str && (*p == ' ' || *p == '\t')) + p--; + return *p == '\n' ? 1 : 0; +} + +/* + * Reads the next line from the file. + * + * Returns 0 if the line is a comment + * 1 if the line is not a comment + * <0 on error + */ +static int next_comment_line(struct libmnt_parser *pa, char **last) +{ + if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0) + return feof(pa->f) ? 1 : -errno; + + pa->line++; + *last = strchr(pa->buf, '\n'); + + return is_comment_line(pa->buf) ? 0 : 1; +} + +static int append_comment(struct libmnt_table *tb, + struct libmnt_fs *fs, + const char *comm, + int eof) +{ + int rc, intro = mnt_table_get_nents(tb) == 0; + + if (intro && is_terminated_by_blank(mnt_table_get_intro_comment(tb))) + intro = 0; + + DBG(TAB, ul_debugobj(tb, "appending %s comment", + intro ? "intro" : + eof ? "trailing" : "fs")); + if (intro) + rc = mnt_table_append_intro_comment(tb, comm); + else if (eof) { + rc = mnt_table_set_trailing_comment(tb, + mnt_fs_get_comment(fs)); + if (!rc) + rc = mnt_table_append_trailing_comment(tb, comm); + if (!rc) + rc = mnt_fs_set_comment(fs, NULL); + } else + rc = mnt_fs_append_comment(fs, comm); + return rc; +} + +/* + * Read and parse the next line from {fs,m}tab or mountinfo + */ +static int mnt_table_parse_next(struct libmnt_parser *pa, + struct libmnt_table *tb, + struct libmnt_fs *fs) +{ + char *s; + int rc; + + assert(tb); + assert(pa); + assert(fs); + + /* read the next non-blank non-comment line */ +next_line: + do { + if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0) + return -EINVAL; + pa->line++; + s = strchr(pa->buf, '\n'); + if (!s) { + /* Missing final newline? Otherwise an extremely */ + /* long line - assume file was corrupted */ + if (feof(pa->f)) { + DBG(TAB, ul_debugobj(tb, + "%s: no final newline", pa->filename)); + s = strchr(pa->buf, '\0'); + } else { + DBG(TAB, ul_debugobj(tb, + "%s:%zu: missing newline at line", + pa->filename, pa->line)); + goto err; + } + } + + /* comments parser */ + if (tb->comms + && (tb->fmt == MNT_FMT_GUESS || tb->fmt == MNT_FMT_FSTAB) + && is_comment_line(pa->buf)) { + do { + rc = append_comment(tb, fs, pa->buf, feof(pa->f)); + if (!rc) + rc = next_comment_line(pa, &s); + } while (rc == 0); + + if (rc == 1 && feof(pa->f)) + rc = append_comment(tb, fs, NULL, 1); + if (rc < 0) + return rc; + + } + + *s = '\0'; + if (--s >= pa->buf && *s == '\r') + *s = '\0'; + s = (char *) skip_blank(pa->buf); + } while (*s == '\0' || *s == '#'); + + if (tb->fmt == MNT_FMT_GUESS) { + tb->fmt = guess_table_format(s); + if (tb->fmt == MNT_FMT_SWAPS) + goto next_line; /* skip swap header */ + } + + switch (tb->fmt) { + case MNT_FMT_FSTAB: + rc = mnt_parse_table_line(fs, s); + break; + case MNT_FMT_MOUNTINFO: + rc = mnt_parse_mountinfo_line(fs, s); + break; + case MNT_FMT_UTAB: + rc = mnt_parse_utab_line(fs, s); + break; + case MNT_FMT_SWAPS: + if (strncmp(s, "Filename\t", 9) == 0) + goto next_line; /* skip swap header */ + rc = mnt_parse_swaps_line(fs, s); + break; + default: + rc = -1; /* unknown format */ + break; + } + + if (rc == 0) + return 0; +err: + DBG(TAB, ul_debugobj(tb, "%s:%zu: %s parse error", pa->filename, pa->line, + tb->fmt == MNT_FMT_MOUNTINFO ? "mountinfo" : + tb->fmt == MNT_FMT_SWAPS ? "swaps" : + tb->fmt == MNT_FMT_FSTAB ? "tab" : "utab")); + + /* by default all errors are recoverable, otherwise behavior depends on + * the errcb() function. See mnt_table_set_parser_errcb(). + */ + return tb->errcb ? tb->errcb(tb, pa->filename, pa->line) : 1; +} + +static pid_t path_to_tid(const char *filename) +{ + char *path = mnt_resolve_path(filename, NULL); + char *p, *end = NULL; + pid_t tid = 0; + + if (!path) + goto done; + p = strrchr(path, '/'); + if (!p) + goto done; + *p = '\0'; + p = strrchr(path, '/'); + if (!p) + goto done; + p++; + + errno = 0; + tid = strtol(p, &end, 10); + if (errno || p == end || (end && *end)) { + tid = 0; + goto done; + } + DBG(TAB, ul_debug("TID for %s is %d", filename, tid)); +done: + free(path); + return tid; +} + +static int kernel_fs_postparse(struct libmnt_table *tb, + struct libmnt_fs *fs, pid_t *tid, + const char *filename) +{ + int rc = 0; + const char *src = mnt_fs_get_srcpath(fs); + + /* This is a filesystem description from /proc, so we're in some process + * namespace. Let's remember the process PID. + */ + if (filename && *tid == -1) + *tid = path_to_tid(filename); + + fs->tid = *tid; + + /* + * Convert obscure /dev/root to something more usable + */ + if (src && strcmp(src, "/dev/root") == 0) { + char *real = NULL; + + rc = mnt_guess_system_root(fs->devno, tb->cache, &real); + if (rc < 0) + return rc; + + if (rc == 0 && real) { + DBG(TAB, ul_debugobj(tb, "canonical root FS: %s", real)); + rc = __mnt_fs_set_source_ptr(fs, real); + + } else if (rc == 1) { + /* mnt_guess_system_root() returns 1 if not able to convert to + * the real devname; ignore this problem */ + rc = 0; + } + } + + return rc; +} + +static int __table_parse_stream(struct libmnt_table *tb, FILE *f, const char *filename) +{ + int rc = -1; + int flags = 0; + pid_t tid = -1; + struct libmnt_parser pa = { .line = 0 }; + + assert(tb); + assert(f); + assert(filename); + + DBG(TAB, ul_debugobj(tb, "%s: start parsing [entries=%d, filter=%s]", + filename, mnt_table_get_nents(tb), + tb->fltrcb ? "yes" : "not")); + + pa.filename = filename; + pa.f = f; + + /* necessary for /proc/mounts only, the /proc/self/mountinfo + * parser sets the flag properly + */ + if (filename && strcmp(filename, _PATH_PROC_MOUNTS) == 0) + flags = MNT_FS_KERNEL; + + do { + struct libmnt_fs *fs; + + if (feof(f)) { + DBG(TAB, ul_debugobj(tb, "end-of-file")); + break; + } + fs = mnt_new_fs(); + if (!fs) + goto err; + + /* parse */ + rc = mnt_table_parse_next(&pa, tb, fs); + + if (rc == 0 && tb->fltrcb && tb->fltrcb(fs, tb->fltrcb_data)) + rc = 1; /* filtered out by callback... */ + + /* add to the table */ + if (rc == 0) { + rc = mnt_table_add_fs(tb, fs); + fs->flags |= flags; + + if (rc == 0 && tb->fmt == MNT_FMT_MOUNTINFO) { + rc = kernel_fs_postparse(tb, fs, &tid, filename); + if (rc) + mnt_table_remove_fs(tb, fs); + } + } + + /* remove reference (or deallocate on error) */ + mnt_unref_fs(fs); + + /* recoverable error */ + if (rc > 0) { + DBG(TAB, ul_debugobj(tb, "recoverable error (continue)")); + continue; + } + + /* fatal errors */ + if (rc < 0 && !feof(f)) { + DBG(TAB, ul_debugobj(tb, "fatal error")); + goto err; + } + } while (1); + + DBG(TAB, ul_debugobj(tb, "%s: stop parsing (%d entries)", + filename, mnt_table_get_nents(tb))); + parser_cleanup(&pa); + return 0; +err: + DBG(TAB, ul_debugobj(tb, "%s: parse error (rc=%d)", filename, rc)); + parser_cleanup(&pa); + return rc; +} + +/** + * mnt_table_parse_stream: + * @tb: tab pointer + * @f: file stream + * @filename: filename used for debug and error messages + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f, const char *filename) +{ + int fd, rc; + FILE *memf = NULL; + char *membuf = NULL; + + /* + * For /proc/#/{mountinfo,mount} we read all file to memory and use it + * as memory stream. For more details see mnt_read_procfs_file(). + */ + if ((fd = fileno(f)) >= 0 + && (tb->fmt == MNT_FMT_GUESS || + tb->fmt == MNT_FMT_MOUNTINFO || + tb->fmt == MNT_FMT_MTAB) + && is_procfs_fd(fd) + && (memf = mnt_get_procfs_memstream(fd, &membuf))) { + + rc = __table_parse_stream(tb, memf, filename); + fclose(memf); + free(membuf); + } else + rc = __table_parse_stream(tb, f, filename); + + return rc; +} + +/** + * mnt_table_parse_file: + * @tb: tab pointer + * @filename: file + * + * Parses the whole table (e.g. /etc/fstab) and appends new records to the @tab. + * + * The libmount parser ignores broken (syntax error) lines, these lines are + * reported to the caller by the errcb() function (see mnt_table_set_parser_errcb()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_file(struct libmnt_table *tb, const char *filename) +{ + FILE *f; + int rc, fd = -1; + + if (!filename || !tb) + return -EINVAL; + + /* + * Try to use read()+poll() to realiably read all + * /proc/#/{mount,mountinfo} file to memory + */ + if (tb->fmt != MNT_FMT_SWAPS + && strncmp(filename, "/proc/", 6) == 0) { + + FILE *memf; + char *membuf = NULL; + + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + rc = -errno; + goto done; + } + memf = mnt_get_procfs_memstream(fd, &membuf); + if (memf) { + rc = __table_parse_stream(tb, memf, filename); + + fclose(memf); + free(membuf); + close(fd); + goto done; + } + /* else fallback to fopen/fdopen() */ + } + + if (fd >= 0) + f = fdopen(fd, "r" UL_CLOEXECSTR); + else + f = fopen(filename, "r" UL_CLOEXECSTR); + + if (f) { + rc = __table_parse_stream(tb, f, filename); + fclose(f); + } else + rc = -errno; +done: + DBG(TAB, ul_debugobj(tb, "parsing done [filename=%s, rc=%d]", filename, rc)); + return rc; +} + +static int mnt_table_parse_dir_filter(const struct dirent *d) +{ + size_t namesz; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && + d->d_type != DT_LNK) + return 0; +#endif + if (*d->d_name == '.') + return 0; + +#define MNT_MNTTABDIR_EXTSIZ (sizeof(MNT_MNTTABDIR_EXT) - 1) + + namesz = strlen(d->d_name); + if (!namesz || namesz < MNT_MNTTABDIR_EXTSIZ + 1 || + strcmp(d->d_name + (namesz - MNT_MNTTABDIR_EXTSIZ), + MNT_MNTTABDIR_EXT) != 0) + return 0; + + /* Accept this */ + return 1; +} + +#ifdef HAVE_SCANDIRAT +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i; + int dd; + struct dirent **namelist = NULL; + + dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dd < 0) + return -errno; + + n = scandirat(dd, ".", &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) { + close(dd); + return 0; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dd, d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + __table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + close(dd); + return 0; +} +#else +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i, r = 0; + DIR *dir = NULL; + struct dirent **namelist = NULL; + + n = scandir(dirname, &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) + return 0; + + /* let's use "at" functions rather than playing crazy games with paths... */ + dir = opendir(dirname); + if (!dir) { + r = -errno; + goto out; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dirfd(dir), d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dirfd(dir), d->d_name, + O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + __table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + +out: + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + if (dir) + closedir(dir); + return r; +} +#endif + +/** + * mnt_table_parse_dir: + * @tb: mount table + * @dirname: directory + * + * The directory: + * - files are sorted by strverscmp(3) + * - files that start with "." are ignored (e.g. ".10foo.fstab") + * - files without the ".fstab" extension are ignored + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + return __mnt_table_parse_dir(tb, dirname); +} + +struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent) +{ + struct libmnt_table *tb; + struct stat st; + + if (!filename) + return NULL; + if (stat(filename, &st)) + return empty_for_enoent ? mnt_new_table() : NULL; + + tb = mnt_new_table(); + if (tb) { + DBG(TAB, ul_debugobj(tb, "new tab for file: %s", filename)); + tb->fmt = fmt; + if (mnt_table_parse_file(tb, filename) != 0) { + mnt_unref_table(tb); + tb = NULL; + } + } + return tb; +} + +/** + * mnt_new_table_from_file: + * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path + * + * Same as mnt_new_table() + mnt_table_parse_file(). Use this function for private + * files only. This function does not allow using the error callback, so you + * cannot provide any feedback to end-users about broken records in files (e.g. + * fstab). + * + * Returns: newly allocated tab on success and NULL in case of error. + */ +struct libmnt_table *mnt_new_table_from_file(const char *filename) +{ + if (!filename) + return NULL; + + return __mnt_new_table_from_file(filename, MNT_FMT_GUESS, 0); +} + +/** + * mnt_new_table_from_dir + * @dirname: directory with *.fstab files + * + * Returns: newly allocated tab on success and NULL in case of error. + */ +struct libmnt_table *mnt_new_table_from_dir(const char *dirname) +{ + struct libmnt_table *tb; + + if (!dirname) + return NULL; + tb = mnt_new_table(); + if (tb && mnt_table_parse_dir(tb, dirname) != 0) { + mnt_unref_table(tb); + tb = NULL; + } + return tb; +} + +/** + * mnt_table_set_parser_errcb: + * @tb: pointer to table + * @cb: pointer to callback function + * + * The error callback function is called by table parser (mnt_table_parse_file()) + * in case of a syntax error. The callback function could be used for error + * evaluation, libmount will continue/stop parsing according to callback return + * codes: + * + * <0 : fatal error (abort parsing) + * 0 : success (parsing continues) + * >0 : recoverable error (the line is ignored, parsing continues). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_parser_errcb(struct libmnt_table *tb, + int (*cb)(struct libmnt_table *tb, const char *filename, int line)) +{ + if (!tb) + return -EINVAL; + tb->errcb = cb; + return 0; +} + +/* + * Filter out entries during tab file parsing. If @cb returns 1, then the entry + * is ignored. + */ +int mnt_table_set_parser_fltrcb(struct libmnt_table *tb, + int (*cb)(struct libmnt_fs *, void *), + void *data) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "%s table parser filter", cb ? "set" : "unset")); + tb->fltrcb = cb; + tb->fltrcb_data = data; + return 0; +} + +/** + * mnt_table_parse_swaps: + * @tb: table + * @filename: overwrites default (/proc/swaps or $LIBMOUNT_SWAPS) or NULL + * + * This function parses /proc/swaps and appends new lines to the @tab. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_swaps(struct libmnt_table *tb, const char *filename) +{ + if (!tb) + return -EINVAL; + if (!filename) { + filename = mnt_get_swaps_path(); + if (!filename) + return -EINVAL; + } + + tb->fmt = MNT_FMT_SWAPS; + + return mnt_table_parse_file(tb, filename); +} + +/** + * mnt_table_parse_fstab: + * @tb: table + * @filename: overwrites default (/etc/fstab or $LIBMOUNT_FSTAB) or NULL + * + * This function parses /etc/fstab and appends new lines to the @tab. If the + * @filename is a directory, then mnt_table_parse_dir() is called. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_fstab(struct libmnt_table *tb, const char *filename) +{ + struct stat st; + int rc = 0; + + if (!tb) + return -EINVAL; + if (!filename) + filename = mnt_get_fstab_path(); + if (!filename) + return -EINVAL; + if (stat(filename, &st) != 0) + return -errno; + + tb->fmt = MNT_FMT_FSTAB; + + if (S_ISREG(st.st_mode)) + rc = mnt_table_parse_file(tb, filename); + else if (S_ISDIR(st.st_mode)) + rc = mnt_table_parse_dir(tb, filename); + else + rc = -EINVAL; + + return rc; +} + +/* + * This function uses @uf to find a corresponding record in @tb, then the record + * from @tb is updated (user specific mount options are added). + * + * Note that @uf must contain only user specific mount options instead of + * VFS options (note that FS options are ignored). + * + * Returns modified filesystem (from @tb) or NULL. + */ +static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct libmnt_fs *uf) +{ + struct libmnt_fs *fs; + struct libmnt_iter itr; + const char *optstr, *src, *target, *root, *attrs; + + if (!tb || !uf) + return NULL; + + DBG(TAB, ul_debugobj(tb, "merging user fs")); + + src = mnt_fs_get_srcpath(uf); + target = mnt_fs_get_target(uf); + optstr = mnt_fs_get_user_options(uf); + attrs = mnt_fs_get_attributes(uf); + root = mnt_fs_get_root(uf); + + if (!src || !target || !root || (!attrs && !optstr)) + return NULL; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *r = mnt_fs_get_root(fs); + + if (fs->flags & MNT_FS_MERGED) + continue; + + if (r && strcmp(r, root) == 0 + && mnt_fs_streq_target(fs, target) + && mnt_fs_streq_srcpath(fs, src)) + break; + } + + if (fs) { + DBG(TAB, ul_debugobj(tb, "found fs -- appending user optstr")); + mnt_fs_append_options(fs, optstr); + mnt_fs_append_attributes(fs, attrs); + mnt_fs_set_bindsrc(fs, mnt_fs_get_bindsrc(uf)); + fs->flags |= MNT_FS_MERGED; + + DBG(TAB, ul_debugobj(tb, "found fs:")); + DBG(TAB, mnt_fs_print_debug(fs, stderr)); + } + return fs; +} + +/* default filename is /proc/self/mountinfo + */ +int __mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename, + struct libmnt_table *u_tb) +{ + int rc = 0, priv_utab = 0; + int explicit_file = filename ? 1 : 0; + + assert(tb); + + if (filename) + DBG(TAB, ul_debugobj(tb, "%s requested as mtab", filename)); + +#ifdef USE_LIBMOUNT_SUPPORT_MTAB + if (mnt_has_regular_mtab(&filename, NULL)) { + + DBG(TAB, ul_debugobj(tb, "force mtab usage [filename=%s]", filename)); + + rc = mnt_table_parse_file(tb, filename); + + /* + * If @filename forces us to read from /proc then also read + * utab file to merge userspace mount options. + */ + if (rc == 0 && is_mountinfo(tb)) + goto read_utab; + + if (!rc) + return 0; + filename = NULL; /* failed */ + } else + filename = NULL; /* mtab useless */ +#endif + + if (!filename || strcmp(filename, _PATH_PROC_MOUNTINFO) == 0) { + filename = _PATH_PROC_MOUNTINFO; + tb->fmt = MNT_FMT_MOUNTINFO; + DBG(TAB, ul_debugobj(tb, "mtab parse: #1 read mountinfo")); + } else + tb->fmt = MNT_FMT_GUESS; + + rc = mnt_table_parse_file(tb, filename); + if (rc) { + if (explicit_file) + return rc; + + /* hmm, old kernel? ...try /proc/mounts */ + tb->fmt = MNT_FMT_MTAB; + return mnt_table_parse_file(tb, _PATH_PROC_MOUNTS); + } + + if (!is_mountinfo(tb)) + return 0; +#ifdef USE_LIBMOUNT_SUPPORT_MTAB +read_utab: +#endif + DBG(TAB, ul_debugobj(tb, "mtab parse: #2 read utab")); + + if (mnt_table_get_nents(tb) == 0) + return 0; /* empty, ignore utab */ + /* + * try to read the user specific information from /run/mount/utabs + */ + if (!u_tb) { + const char *utab = mnt_get_utab_path(); + + if (!utab || is_file_empty(utab)) + return 0; + + u_tb = mnt_new_table(); + if (!u_tb) + return -ENOMEM; + + u_tb->fmt = MNT_FMT_UTAB; + mnt_table_set_parser_fltrcb(u_tb, tb->fltrcb, tb->fltrcb_data); + + rc = mnt_table_parse_file(u_tb, utab); + priv_utab = 1; + } + + DBG(TAB, ul_debugobj(tb, "mtab parse: #3 merge utab")); + + if (rc == 0) { + struct libmnt_fs *u_fs; + struct libmnt_iter itr; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + /* merge user options into mountinfo from the kernel */ + while(mnt_table_next_fs(u_tb, &itr, &u_fs) == 0) + mnt_table_merge_user_fs(tb, u_fs); + } + + + if (priv_utab) + mnt_unref_table(u_tb); + return 0; +} +/** + * mnt_table_parse_mtab: + * @tb: table + * @filename: overwrites default or NULL + * + * The default filename is /proc/self/mountinfo. If the mount table is a + * mountinfo file then /run/mount/utabs is parsed too and both files are merged + * to the one libmnt_table. + * + * If libmount is compiled with classic mtab file support, and the /etc/mtab is + * a regular file then this file is parsed. + * + * It's strongly recommended to use NULL as a @filename to keep code portable. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename) +{ + return __mnt_table_parse_mtab(tb, filename, NULL); +} |