/* * Copyright (C) 2009 Karel Zak * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ #include #include #include #include #include #include #include #include #include #include "nls.h" #include "at.h" #include "mangle.h" #include "mountP.h" #include "pathnames.h" static inline char *skip_spaces(char *s) { assert(s); while (*s == ' ' || *s == '\t') s++; return s; } static int next_number(char **s, int *num) { char *end = NULL; assert(num); assert(s); *s = skip_spaces(*s); if (!**s) return -1; *num = strtol(*s, &end, 10); if (end == NULL || *s == end) return -1; *s = end; /* valid end of number is space or terminator */ if (*end == ' ' || *end == '\t' || *end == '\0') return 0; return -1; } /* * Parses one line from {fs,m}tab */ static int mnt_parse_tab_line(mnt_fs *fs, char *s) { int rc, n = 0; char *src, *fstype, *optstr; rc = sscanf(s, "%ms " /* (1) source */ "%ms " /* (2) target */ "%ms " /* (3) FS type */ "%ms " /* (4) options */ "%n", /* byte count */ &src, &fs->target, &fstype, &optstr, &n); if (rc == 4) { unmangle_string(src); unmangle_string(fs->target); unmangle_string(fstype); unmangle_string(optstr); rc = __mnt_fs_set_source_ptr(fs, src); if (!rc) rc = __mnt_fs_set_fstype_ptr(fs, fstype); if (!rc) rc = mnt_fs_set_options(fs, optstr); free(optstr); } else { DBG(TAB, mnt_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); rc = -EINVAL; } if (rc) return rc; /* error */ fs->passno = fs->freq = 0; s = skip_spaces(s + n); if (*s) { if (next_number(&s, &fs->freq) != 0) { if (*s) { DBG(TAB, mnt_debug("tab parse error: [freq]")); rc = -EINVAL; } } else if (next_number(&s, &fs->passno) != 0 && *s) { DBG(TAB, mnt_debug("tab parse error: [passno]")); rc = -EINVAL; } } return rc; } /* * Parses one line from mountinfo file */ static int mnt_parse_mountinfo_line(mnt_fs *fs, char *s) { int rc; unsigned int maj, min; char *fstype, *src; rc = sscanf(s, "%u " /* (1) id */ "%u " /* (2) parent */ "%u:%u " /* (3) maj:min */ "%ms " /* (4) mountroot */ "%ms " /* (5) target */ "%ms" /* (6) vfs options (fs-independent) */ "%*[^-]" /* (7) optional fields */ "- " /* (8) separator */ "%ms " /* (9) FS type */ "%ms " /* (10) source */ "%ms", /* (11) fs options (fs specific) */ &fs->id, &fs->parent, &maj, &min, &fs->root, &fs->target, &fs->vfs_optstr, &fstype, &src, &fs->fs_optstr); if (rc == 10) { fs->devno = makedev(maj, min); unmangle_string(fs->root); unmangle_string(fs->target); unmangle_string(fs->vfs_optstr); unmangle_string(fstype); if (!strcmp(src, "none")) { free(src); src = NULL; } else unmangle_string(src); if (!strcmp(fs->fs_optstr, "none")) { free(fs->fs_optstr); fs->fs_optstr = NULL; } else unmangle_string(fs->fs_optstr); rc = __mnt_fs_set_fstype_ptr(fs, fstype); if (!rc) rc = __mnt_fs_set_source_ptr(fs, src); } else { DBG(TAB, mnt_debug( "mountinfo parse error [sscanf rc=%d]: '%s'", rc, s)); rc = -EINVAL; } return rc; } /* * Parses one line from utab file */ static int mnt_parse_utab_line(mnt_fs *fs, const char *s) { const char *p = s; assert(fs); assert(s); assert(!fs->source); assert(!fs->target); while (p && *p) { 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 (strcmp(v, "none")) __mnt_fs_set_source_ptr(fs, 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, mnt_debug("utab parse error: ENOMEM")); return -ENOMEM; } /* * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) * * Note that we aren't tring to guess utab file format, because this file has * to be always parsed by private libmount routines with explicitly defined * format. * * mountinfo: " ... " */ static int guess_tab_format(char *line) { unsigned int a, b; if (sscanf(line, "%u %u", &a, &b) == 2) return MNT_FMT_MOUNTINFO; return MNT_FMT_FSTAB; } /* * Read and parse the next line from {fs,m}tab or mountinfo */ static int mnt_tab_parse_next(mnt_tab *tb, FILE *f, mnt_fs *fs, const char *filename, int *nlines) { char buf[BUFSIZ]; char *s; assert(tb); assert(f); assert(fs); /* read the next non-blank non-comment line */ do { if (fgets(buf, sizeof(buf), f) == NULL) return -EINVAL; ++*nlines; s = index (buf, '\n'); if (!s) { /* Missing final newline? Otherwise extremely */ /* long line - assume file was corrupted */ if (feof(f)) { DBG(TAB, mnt_debug_h(tb, "%s: no final newline", filename)); s = index (buf, '\0'); } else { DBG(TAB, mnt_debug_h(tb, "%s:%d: missing newline at line", filename, *nlines)); goto err; } } *s = '\0'; if (--s >= buf && *s == '\r') *s = '\0'; s = skip_spaces(buf); } while (*s == '\0' || *s == '#'); if (tb->fmt == MNT_FMT_GUESS) tb->fmt = guess_tab_format(s); if (tb->fmt == MNT_FMT_FSTAB) { if (mnt_parse_tab_line(fs, s) != 0) goto err; } else if (tb->fmt == MNT_FMT_MOUNTINFO) { if (mnt_parse_mountinfo_line(fs, s) != 0) goto err; } else if (tb->fmt == MNT_FMT_UTAB) { if (mnt_parse_utab_line(fs, s) != 0) goto err; } /*DBG(TAB, mnt_fs_print_debug(fs, stderr));*/ return 0; err: DBG(TAB, mnt_debug_h(tb, "%s:%d: %s parse error", filename, *nlines, tb->fmt == MNT_FMT_MOUNTINFO ? "mountinfo" : tb->fmt == MNT_FMT_FSTAB ? "fstab" : "utab")); /* by default all errors are recoverable, otherwise behavior depends on * errcb() function. See mnt_tab_set_parser_errcb(). */ return tb->errcb ? tb->errcb(tb, filename, *nlines) : 1; } /** * mnt_tab_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_tab_parse_stream(mnt_tab *tb, FILE *f, const char *filename) { int nlines = 0; int rc = -1; assert(tb); assert(f); assert(filename); DBG(TAB, mnt_debug_h(tb, "%s: start parsing", filename)); while (!feof(f)) { mnt_fs *fs = mnt_new_fs(); if (!fs) goto err; rc = mnt_tab_parse_next(tb, f, fs, filename, &nlines); if (!rc) rc = mnt_tab_add_fs(tb, fs); if (rc) { mnt_free_fs(fs); if (rc == 1) continue; /* recoverable error */ if (feof(f)) break; goto err; /* fatal error */ } } DBG(TAB, mnt_debug_h(tb, "%s: stop parsing", filename)); return 0; err: DBG(TAB, mnt_debug_h(tb, "%s: parse error (rc=%d)", filename, rc)); return rc; } /** * mnt_tab_parse_file: * @tb: tab pointer * @filename: file * * Parses whole table (e.g. /etc/mtab) and appends new records to the @tab. * * The libmount parser ignores broken (syntax error) lines, these lines are * reported to caller by errcb() function (see mnt_tab_set_parser_errcb()). * * Returns: 0 on success, negative number in case of error. */ int mnt_tab_parse_file(mnt_tab *tb, const char *filename) { FILE *f; int rc; assert(tb); assert(filename); if (!filename || !tb) return -EINVAL; f = fopen(filename, "r"); if (f) { rc = mnt_tab_parse_stream(tb, f, filename); fclose(f); } else return -errno; return rc; } static int mnt_tab_parse_dir(mnt_tab *tb, const char *dirname) { int n = 0, i; DIR *dir = NULL; struct dirent **namelist = NULL; /* TODO: it would be nice to have a scandir() implementaion that * is able to use already opened directory */ n = scandir(dirname, &namelist, NULL, versionsort); if (n <= 0) return 0; /* let use "at" functions rather than play crazy games with paths... */ dir = opendir(dirname); if (!dir) return -errno; for (i = 0; i < n; i++) { struct dirent *d = namelist[i]; struct stat st; size_t namesz; FILE *f; #ifdef _DIRENT_HAVE_D_TYPE if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && d->d_type != DT_LNK) continue; #endif if (*d->d_name == '.') continue; #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)) continue; if (fstat_at(dirfd(dir), _PATH_MNTTAB_DIR, d->d_name, &st, 0) || !S_ISREG(st.st_mode)) continue; f = fopen_at(dirfd(dir), _PATH_MNTTAB_DIR, d->d_name, O_RDONLY, "r"); if (f) { mnt_tab_parse_stream(tb, f, d->d_name); fclose(f); } } for (i = 0; i < n; i++) free(namelist[i]); free(namelist); if (dir) closedir(dir); return 0; } mnt_tab *__mnt_new_tab_from_file(const char *filename, int fmt) { mnt_tab *tb; struct stat st; assert(filename); if (!filename) return NULL; if (stat(filename, &st) || st.st_size == 0) return NULL; tb = mnt_new_tab(); if (tb) { tb->fmt = fmt; if (mnt_tab_parse_file(tb, filename) != 0) { mnt_free_tab(tb); tb = NULL; } } return tb; } /** * mnt_new_tab_from_file: * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path * * Same as mnt_new_tab() + mnt_tab_parse_file(). Use this function for private * files only. This function does not allow to use 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. */ mnt_tab *mnt_new_tab_from_file(const char *filename) { return __mnt_new_tab_from_file(filename, MNT_FMT_GUESS); } /** * mnt_new_tab_from_dir * @dirname: for example /etc/fstab.d * * Returns: newly allocated tab on success and NULL in case of error. */ mnt_tab *mnt_new_tab_from_dir(const char *dirname) { mnt_tab *tb; assert(dirname); if (!dirname) return NULL; tb = mnt_new_tab(); if (tb && mnt_tab_parse_dir(tb, dirname) != 0) { mnt_free_tab(tb); tb = NULL; } return tb; } /** * mnt_tab_set_parser_errcb: * @tab: pointer to table * @cb: pointer to callback function * * The error callback function is called by table parser (mnt_tab_parse_file()) * in case of syntax error. The callback function could be used for errors * evaluation, libmount will continue/stop parsing according to callback return * codes: * * <0 : fatal error (abort parsing) * 0 : success (parsing continue) * >0 : recoverable error (the line is ignored, parsing continue). * * Returns: 0 on success or negative number in case of error. */ int mnt_tab_set_parser_errcb(mnt_tab *tb, int (*cb)(mnt_tab *tb, const char *filename, int line)) { assert(tb); tb->errcb = cb; return 0; } /** * mnt_tab_parse_fstab: * @tb: table * @filename: overwrites default (/etc/fstab or $LIBMOUNT_FSTAB) or NULL * * This function parses /etc/fstab or /etc/fstab.d and appends new lines to the * @tab. If the system contains classic fstab file and also fstab.d directory * then the fstab file is parsed before the fstab.d directory. * * The fstab.d directory: * - files are sorted by strverscmp(3) * - files that starts with "." are ignored (e.g. ".10foo.fstab") * - files without the ".fstab" extension are ignored * * See also mnt_tab_set_parser_errcb(). * * Returns: 0 on success or negative number in case of error. */ int mnt_tab_parse_fstab(mnt_tab *tb, const char *filename) { FILE *f; assert(tb); if (!tb) return -EINVAL; if (!filename) filename = mnt_get_fstab_path(); tb->fmt = MNT_FMT_FSTAB; f = fopen(filename, "r"); if (f) { int rc = mnt_tab_parse_stream(tb, f, filename); fclose(f); if (rc) return rc; if (strcmp(filename, _PATH_MNTTAB)) /* /etc/fstab.d sould be used together with /etc/fstab only */ return 0; } if (!access(_PATH_MNTTAB_DIR, R_OK)) return mnt_tab_parse_dir(tb, _PATH_MNTTAB_DIR); return 0; } /* * This function uses @uf to found corresponding record in @tb, then the record * from @tb is updated (userspace specific mount options are added). * * Note that @uf must contain only userspace specific mount options instead of * VFS options (note that FS options are ignored). * * Returns modified filesystem (from @tb) or NULL. */ static mnt_fs *mnt_tab_merge_userspace_fs(mnt_tab *tb, mnt_fs *uf) { mnt_fs *fs; mnt_iter itr; const char *optstr, *src, *target, *root, *attrs; assert(tb); assert(uf); if (!tb || !uf) return NULL; DBG(TAB, mnt_debug_h(tb, "merging userspace fs")); src = mnt_fs_get_srcpath(uf); target = mnt_fs_get_target(uf); optstr = mnt_fs_get_userspace_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_tab_next_fs(tb, &itr, &fs) == 0) { const char *s = mnt_fs_get_srcpath(fs), *t = mnt_fs_get_target(fs), *r = mnt_fs_get_root(fs); if (s && t && r && !strcmp(t, target) && !strcmp(s, src) && !strcmp(r, root)) break; } if (fs) { DBG(TAB, mnt_debug_h(tb, "found fs -- appending userspace optstr")); mnt_fs_append_userspace_options(fs, optstr); mnt_fs_append_attributes(fs, attrs); mnt_fs_set_bindsrc(fs, mnt_fs_get_bindsrc(uf)); DBG(TAB, mnt_debug_h(tb, "found fs:")); DBG(TAB, mnt_fs_print_debug(fs, stderr)); } return fs; } /** * mnt_tab_parse_mtab: * @tb: table * @filename: overwrites default (/etc/mtab or $LIBMOUNT_MTAB) or NULL * * This function parses /etc/mtab or /proc/self/mountinfo + * /dev/.mount/utabs or /proc/mounts. * * See also mnt_tab_set_parser_errcb(). * * Returns: 0 on success or negative number in case of error. */ int mnt_tab_parse_mtab(mnt_tab *tb, const char *filename) { int rc; const char *utab = NULL; if (mnt_has_regular_mtab(&filename, NULL)) { DBG(TAB, mnt_debug_h(tb, "force %s usage", filename)); rc = mnt_tab_parse_file(tb, filename); if (!rc) return 0; filename = NULL; /* failed */ } /* * useless /etc/mtab * -- read kernel information from /proc/self/mountinfo */ tb->fmt = MNT_FMT_MOUNTINFO; rc = mnt_tab_parse_file(tb, _PATH_PROC_MOUNTINFO); if (rc) { /* hmm, old kernel? ...try /proc/mounts */ tb->fmt = MNT_FMT_MTAB; return mnt_tab_parse_file(tb, _PATH_PROC_MOUNTS); } /* * try to read userspace specific information from /dev/.mount/utabs */ utab = mnt_get_utab_path(); if (utab) { mnt_tab *u_tb = __mnt_new_tab_from_file(utab, MNT_FMT_UTAB); if (u_tb) { mnt_fs *u_fs; mnt_iter itr; mnt_reset_iter(&itr, MNT_ITER_BACKWARD); /* merge userspace options into mountinfo from kernel */ while(mnt_tab_next_fs(u_tb, &itr, &u_fs) == 0) mnt_tab_merge_userspace_fs(tb, u_fs); mnt_free_tab(u_tb); } } return 0; }