/* * Note that the @target has to be an absolute path (so at least "/"). The * @filename returns an allocated buffer with the last path component, for example: * * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test" */ int mnt_chdir_to_parent(const char *target, char **filename) { char *buf, *parent, *last = NULL; char cwd[PATH_MAX]; int rc = -EINVAL; if (!target || *target != '/') return -EINVAL; DBG(UTILS, mnt_debug("moving to %s parent", target)); buf = strdup(target); if (!buf) return -ENOMEM; if (*(buf + 1) != '\0') { last = stripoff_last_component(buf); if (!last) goto err; } parent = buf && *buf ? buf : "/"; if (chdir(parent) == -1) { DBG(UTILS, mnt_debug("failed to chdir to %s: %m", parent)); rc = -errno; goto err; } if (!getcwd(cwd, sizeof(cwd))) { DBG(UTILS, mnt_debug("failed to obtain current directory: %m")); rc = -errno; goto err; } if (strcmp(cwd, parent) != 0) { DBG(UTILS, mnt_debug( "unexpected chdir (expected=%s, cwd=%s)", parent, cwd)); goto err; } DBG(CXT, mnt_debug( "current directory moved to %s [last_component='%s']", parent, last)); if (filename) { *filename = buf; if (!last || !*last) memcpy(*filename, ".", 2); else memmove(*filename, last, strlen(last) + 1); } else free(buf); return 0; err: free(buf); return rc; }
/* * Parses one line from {fs,m}tab */ static int mnt_parse_table_line(struct libmnt_fs *fs, char *s) { int rc, n = 0; char *src, *fstype, *optstr; rc = sscanf(s, UL_SCNsA" " /* (1) source */ UL_SCNsA" " /* (2) target */ UL_SCNsA" " /* (3) FS type */ UL_SCNsA" " /* (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; }
/* * Don't export this to libmount API -- utab is private library stuff. * * If the file does not exist and @writable argument is not NULL then it will * try to create the directory (e.g. /run/mount) and the file. * * Returns: 1 if utab is a regular file, and 0 in case of * error (check errno for more details). */ int mnt_has_regular_utab(const char **utab, int *writable) { struct stat st; int rc; const char *filename = utab && *utab ? *utab : mnt_get_utab_path(); if (writable) *writable = 0; if (utab && !*utab) *utab = filename; DBG(UTILS, mnt_debug("utab: %s", filename)); rc = lstat(filename, &st); if (rc == 0) { /* file exist */ if (S_ISREG(st.st_mode)) { if (writable) *writable = !try_write(filename); return 1; } goto done; /* it's not regular file */ } if (writable) { char *dirname = strdup(filename); if (!dirname) goto done; stripoff_last_component(dirname); /* remove filename */ rc = mkdir(dirname, S_IWUSR| S_IRUSR|S_IRGRP|S_IROTH| S_IXUSR|S_IXGRP|S_IXOTH); free(dirname); if (rc && errno != EEXIST) goto done; /* probably EACCES */ *writable = !try_write(filename); if (*writable) return 1; } done: DBG(UTILS, mnt_debug("%s: irregular/non-writable file", filename)); return 0; }
int mnt_get_gid(const char *groupname, gid_t *gid) { int rc = -1; struct group grp; struct group *gr; size_t sz = get_pw_record_size(); char *buf; if (!groupname || !gid) return -EINVAL; buf = malloc(sz); if (!buf) return -ENOMEM; if (!getgrnam_r(groupname, &grp, buf, sz, &gr) && gr) { *gid= gr->gr_gid; rc = 0; } else { DBG(UTILS, mnt_debug( "cannot convert '%s' groupname to GID", groupname)); rc = errno ? -errno : -EINVAL; } free(buf); return rc; }
int mnt_get_uid(const char *username, uid_t *uid) { int rc = -1; struct passwd pwd; struct passwd *pw; size_t sz = get_pw_record_size(); char *buf; if (!username || !uid) return -EINVAL; buf = malloc(sz); if (!buf) return -ENOMEM; if (!getpwnam_r(username, &pwd, buf, sz, &pw) && pw) { *uid= pw->pw_uid; rc = 0; } else { DBG(UTILS, mnt_debug( "cannot convert '%s' username to UID", username)); rc = errno ? -errno : -EINVAL; } free(buf); return rc; }
static int get_filesystems(const char *filename, char ***filesystems, const char *pattern) { int rc = 0; FILE *f; char line[128]; f = fopen(filename, "r"); if (!f) return 1; DBG(UTILS, mnt_debug("reading filesystems list from: %s", filename)); while (fgets(line, sizeof(line), f)) { char name[sizeof(line)]; if (*line == '#' || strncmp(line, "nodev", 5) == 0) continue; if (sscanf(line, " %128[^\n ]\n", name) != 1) continue; if (strcmp(name, "*") == 0) { rc = 1; break; /* end of the /etc/filesystems */ } if (pattern && !mnt_match_fstype(name, pattern)) continue; rc = add_filesystem(filesystems, name); if (rc) break; } fclose(f); return rc; }
/** * mnt_optstr_prepend_option: * @optstr: option string or NULL, returns a reallocated string * @name: value name * @value: value * * Returns: 0 on success or -1 in case of error. After an error the @optstr should * be unmodified. */ int mnt_optstr_prepend_option(char **optstr, const char *name, const char *value) { int rc = 0; char *tmp = *optstr; assert(optstr); if (!name || !*name) return 0; *optstr = NULL; rc = mnt_optstr_append_option(optstr, name, value); if (!rc && tmp && *tmp) rc = mnt_optstr_append_option(optstr, tmp, NULL); if (!rc) { free(tmp); return 0; } free(*optstr); *optstr = tmp; DBG(OPTIONS, mnt_debug("failed to prepend '%s[=%s]' to '%s'", name, value, *optstr)); 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) { 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; __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; }
static int update(const char *target, struct libmnt_fs *fs, unsigned long mountflags) { int rc; struct libmnt_update *upd; DBG(UPDATE, mnt_debug("update test")); upd = mnt_new_update(); if (!upd) return -ENOMEM; rc = mnt_update_set_fs(upd, mountflags, target, fs); if (rc == 1) { /* update is unnecessary */ rc = 0; goto done; } if (rc) { fprintf(stderr, "failed to set FS\n"); goto done; } /* [... here should be mount(2) call ...] */ rc = mnt_update_table(upd, NULL); done: return rc; }
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, mnt_debug("TID for %s is %d", filename, tid)); done: free(path); return tid; }
/* * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) * * Note that we aren't trying to guess utab file format, because this file has * to be always parsed by private libmount routines with explicitly defined * format. * * mountinfo: "<number> <number> ... " */ static int guess_table_format(char *line) { unsigned int a, b; DBG(TAB, mnt_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 */ }
/** * mnt_has_regular_mtab: * @mtab: returns path to mtab * @writable: returns 1 if the file is writable * * If the file does not exist and @writable argument is not NULL then it will * try to create the file * * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check * errno for more details). */ int mnt_has_regular_mtab(const char **mtab, int *writable) { struct stat st; int rc; const char *filename = mtab && *mtab ? *mtab : mnt_get_mtab_path(); if (writable) *writable = 0; if (mtab && !*mtab) *mtab = filename; DBG(UTILS, mnt_debug("mtab: %s", filename)); rc = lstat(filename, &st); if (rc == 0) { /* file exist */ if (S_ISREG(st.st_mode)) { if (writable) *writable = !try_write(filename); return 1; } goto done; } /* try to create the file */ if (writable) { *writable = !try_write(filename); if (*writable) return 1; } done: DBG(UTILS, mnt_debug("%s: irregular/non-writable", filename)); return 0; }
/* * Parses one line from /proc/swaps */ static int mnt_parse_swaps_line(struct libmnt_fs *fs, char *s) { uintmax_t fsz, usz; int rc; char *src = NULL; rc = sscanf(s, UL_SCNsA" " /* (1) source */ UL_SCNsA" " /* (2) type */ "%jd" /* (3) size */ "%jd" /* (4) used */ "%d", /* priority */ &src, &fs->swaptype, &fsz, &usz, &fs->priority); if (rc == 5) { size_t sz; fs->size = fsz; fs->usedsize = usz; unmangle_string(src); /* remove "(deleted)" suffix */ sz = strlen(src); if (sz > PATH_DELETED_SUFFIX_SZ) { char *p = src + (sz - PATH_DELETED_SUFFIX_SZ); if (strcmp(p, PATH_DELETED_SUFFIX) == 0) *p = '\0'; } rc = mnt_fs_set_source(fs, src); if (!rc) mnt_fs_set_fstype(fs, "swap"); free(src); } else { DBG(TAB, mnt_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); rc = -EINVAL; } return rc; }
/* * Sets fs-root and fs-type to @upd->fs according to the @fs template and * @mountfalgs. For MS_BIND mountflag it reads information about source * filesystem from /proc/self/mountinfo. */ static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags) { struct libmnt_fs *src_fs; char *fsroot = NULL; const char *src; int rc = 0; DBG(UPDATE, mnt_debug("setting FS root")); assert(upd); assert(upd->fs); assert(fs); if (mountflags & MS_BIND) { if (!upd->mountinfo) upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); src = mnt_fs_get_srcpath(fs); if (src) { rc = mnt_fs_set_bindsrc(upd->fs, src); if (rc) goto err; } } src_fs = mnt_table_get_fs_root(upd->mountinfo, fs, mountflags, &fsroot); if (src_fs) { src = mnt_fs_get_srcpath(src_fs); rc = mnt_fs_set_source(upd->fs, src); if (rc) goto err; mnt_fs_set_fstype(upd->fs, mnt_fs_get_fstype(src_fs)); } upd->fs->root = fsroot; return 0; err: free(fsroot); return rc; }
/** * mnt_get_mountpoint: * @path: pathname * * This function finds the mountpoint that a given path resides in. @path * should be canonicalized. The returned pointer should be freed by the caller. * * Returns: allocated string with target of the mounted device or NULL on error */ char *mnt_get_mountpoint(const char *path) { char *mnt; struct stat st; dev_t dir, base; assert(path); mnt = strdup(path); if (!mnt) return NULL; if (*mnt == '/' && *(mnt + 1) == '\0') goto done; if (stat(mnt, &st)) goto err; base = st.st_dev; do { char *p = stripoff_last_component(mnt); if (!p) break; if (stat(*mnt ? mnt : "/", &st)) goto err; dir = st.st_dev; if (dir != base) { if (p > mnt) *(p - 1) = '/'; goto done; } base = dir; } while (mnt && *(mnt + 1) != '\0'); memcpy(mnt, "/", 2); done: DBG(UTILS, mnt_debug("%s mountpoint is %s", path, mnt)); return mnt; err: free(mnt); return NULL; }
char *mnt_get_fs_root(const char *path, const char *mnt) { char *m = (char *) mnt, *res; const char *p; size_t sz; if (!m) m = mnt_get_mountpoint(path); if (!m) return NULL; sz = strlen(m); p = sz > 1 ? path + sz : path; if (m != mnt) free(m); res = *p ? strdup(p) : strdup("/"); DBG(UTILS, mnt_debug("%s fs-root is %s", path, res)); return res; }
int mkdir_p(const char *path, mode_t mode) { char *p, *dir; int rc = 0; if (!path || !*path) return -EINVAL; dir = p = strdup(path); if (!dir) return -ENOMEM; if (*p == '/') p++; while (p && *p) { char *e = strchr(p, '/'); if (e) *e = '\0'; if (*p) { rc = mkdir(dir, mode); if (rc && errno != EEXIST) break; rc = 0; } if (!e) break; *e = '/'; p = e + 1; } DBG(UTILS, mnt_debug("%s mkdir %s", path, rc ? "FAILED" : "SUCCESS")); free(dir); return rc; }
/* * Parses one line from mountinfo file */ static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, char *s) { int rc, end = 0; unsigned int maj, min; char *fstype = NULL, *src = NULL, *p; rc = sscanf(s, "%u " /* (1) id */ "%u " /* (2) parent */ "%u:%u " /* (3) maj:min */ UL_SCNsA" " /* (4) mountroot */ UL_SCNsA" " /* (5) target */ UL_SCNsA /* (6) vfs options (fs-independent) */ "%n", /* number of read bytes */ &fs->id, &fs->parent, &maj, &min, &fs->root, &fs->target, &fs->vfs_optstr, &end); if (rc >= 7 && end > 0) s += end; /* (7) optional fields, terminated by " - " */ p = strstr(s, " - "); if (!p) { DBG(TAB, mnt_debug("mountinfo parse error: not found separator")); return -EINVAL; } s = p + 3; rc += sscanf(s, UL_SCNsA" " /* (8) FS type */ UL_SCNsA" " /* (9) source */ UL_SCNsA, /* (10) fs options (fs specific) */ &fstype, &src, &fs->fs_optstr); if (rc >= 10) { fs->flags |= MNT_FS_KERNEL; fs->devno = makedev(maj, min); unmangle_string(fs->root); unmangle_string(fs->target); unmangle_string(fs->vfs_optstr); unmangle_string(fstype); unmangle_string(src); unmangle_string(fs->fs_optstr); rc = __mnt_fs_set_fstype_ptr(fs, fstype); if (!rc) { fstype = NULL; rc = __mnt_fs_set_source_ptr(fs, src); if (!rc) src = NULL; } /* merge VFS and FS options to the one string */ fs->optstr = mnt_fs_strdup_options(fs); if (!fs->optstr) rc = -ENOMEM; } else { free(fstype); free(src); DBG(TAB, mnt_debug( "mountinfo parse error [sscanf rc=%d]: '%s'", rc, s)); rc = -EINVAL; } return rc; }
/** * mnt_optstr_apply_flags: * @optstr: string with comma separated list of options * @flags: returns mount flags * @map: options map * * Removes/adds options to the @optstr according to flags. For example: * * MS_NOATIME and "foo,bar,noexec" --returns-> "foo,bar,noatime" * * Returns: 0 on success or negative number in case of error. */ int mnt_optstr_apply_flags(char **optstr, unsigned long flags, const struct libmnt_optmap *map) { struct libmnt_optmap const *maps[1]; char *name, *next, *val; size_t namesz = 0, valsz = 0; unsigned long fl; int rc = 0; assert(optstr); if (!optstr || !map) return -EINVAL; DBG(CXT, mnt_debug("applying 0x%08lu flags to '%s'", flags, *optstr)); maps[0] = map; next = *optstr; fl = flags; /* * There is a convention that 'rw/ro' flags are always at the beginning of * the string (although the 'rw' is unnecessary). */ if (map == mnt_get_builtin_optmap(MNT_LINUX_MAP)) { const char *o = (fl & MS_RDONLY) ? "ro" : "rw"; if (next && (!strncmp(next, "rw", 2) || !strncmp(next, "ro", 2)) && (*(next + 2) == '\0' || *(next + 2) == ',')) { /* already set, be paranoid and fix it */ memcpy(next, o, 2); } else { rc = mnt_optstr_prepend_option(optstr, o, NULL); if (rc) goto err; next = *optstr; /* because realloc() */ } fl &= ~MS_RDONLY; next += 2; if (*next == ',') next++; } if (next && *next) { /* * scan @optstr and remove options that are missing in * @flags */ while(!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) { const struct libmnt_optmap *ent; if (mnt_optmap_get_entry(maps, 1, name, namesz, &ent)) { /* * remove unwanted option (rw/ro is already set) */ if (!ent || !ent->id) continue; /* ignore name=<value> if options map expects <name> only */ if (valsz && mnt_optmap_entry_novalue(ent)) continue; if (ent->id == MS_RDONLY || (ent->mask & MNT_INVERT) || (fl & ent->id) != (unsigned long) ent->id) { char *end = val ? val + valsz : name + namesz; next = name; rc = mnt_optstr_remove_option_at( optstr, name, end); if (rc) goto err; } if (!(ent->mask & MNT_INVERT)) fl &= ~ent->id; } } } /* add missing options */ if (fl) { const struct libmnt_optmap *ent; char *p; for (ent = map; ent && ent->name; ent++) { if ((ent->mask & MNT_INVERT) || ent->id == 0 || (fl & ent->id) != (unsigned long) ent->id) continue; /* don't add options which require values (e.g. offset=%d) */ p = strchr(ent->name, '='); if (p) { if (p > ent->name && *(p - 1) == '[') p--; /* name[=] */ else continue; /* name= */ p = strndup(ent->name, p - ent->name); if (!p) { rc = -ENOMEM; goto err; } mnt_optstr_append_option(optstr, p, NULL); free(p); } else mnt_optstr_append_option(optstr, ent->name, NULL); } } DBG(CXT, mnt_debug("new optstr '%s'", *optstr)); return rc; err: DBG(CXT, mnt_debug("failed to apply flags [rc=%d]", rc)); return rc; }
/* * Parses the first option from @optstr. The @optstr pointer is set to the beginning * of the next option. * * Returns -EINVAL on parse error, 1 at the end of optstr and 0 on success. */ static int mnt_optstr_parse_next(char **optstr, char **name, size_t *namesz, char **value, size_t *valsz) { int open_quote = 0; char *start = NULL, *stop = NULL, *p, *sep = NULL; char *optstr0; assert(optstr); assert(*optstr); optstr0 = *optstr; if (name) *name = NULL; if (namesz) *namesz = 0; if (value) *value = NULL; if (valsz) *valsz = 0; /* trim leading commas as to not invalidate option * strings with multiple consecutive commas */ while (optstr0 && *optstr0 == ',') optstr0++; for (p = optstr0; p && *p; p++) { if (!start) start = p; /* beginning of the option item */ if (*p == '"') open_quote ^= 1; /* reverse the status */ if (open_quote) continue; /* still in quoted block */ if (!sep && p > start && *p == '=') sep = p; /* name and value separator */ if (*p == ',') stop = p; /* terminate the option item */ else if (*(p + 1) == '\0') stop = p + 1; /* end of optstr */ if (!start || !stop) continue; if (stop <= start) goto error; if (name) *name = start; if (namesz) *namesz = sep ? sep - start : stop - start; *optstr = *stop ? stop + 1 : stop; if (sep) { if (value) *value = sep + 1; if (valsz) *valsz = stop - sep - 1; } return 0; } return 1; /* end of optstr */ error: DBG(OPTIONS, mnt_debug("parse error: \"%s\"", optstr0)); return -EINVAL; }
/* * Parses one line from {fs,m}tab */ static int mnt_parse_table_line(struct libmnt_fs *fs, char *s) { int rc, n = 0, xrc; char *src = NULL, *fstype = NULL, *optstr = NULL; rc = sscanf(s, UL_SCNsA" " /* (1) source */ UL_SCNsA" " /* (2) target */ UL_SCNsA" " /* (3) FS type */ UL_SCNsA" " /* (4) options */ "%n", /* byte count */ &src, &fs->target, &fstype, &optstr, &n); xrc = rc; if (rc == 3 || rc == 4) { /* options are optional */ unmangle_string(src); unmangle_string(fs->target); unmangle_string(fstype); if (optstr && *optstr) unmangle_string(optstr); /* note that __foo functions does not reallocate the string */ rc = __mnt_fs_set_source_ptr(fs, src); if (!rc) { src = NULL; rc = __mnt_fs_set_fstype_ptr(fs, fstype); if (!rc) fstype = NULL; } if (!rc && optstr) rc = mnt_fs_set_options(fs, optstr); free(optstr); optstr = NULL; } else { DBG(TAB, mnt_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); rc = -EINVAL; } if (rc) { free(src); free(fstype); free(optstr); DBG(TAB, mnt_debug("tab parse error: [set vars, rc=%d]\n", rc)); return rc; /* error */ } fs->passno = fs->freq = 0; if (xrc == 4 && n) s = skip_spaces(s + n); if (xrc == 4 && *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; }
/* * Allocates utab entry (upd->fs) for mount/remount. This function should be * called *before* mount(2) syscall. The @fs is used as a read-only template. * * Returns: 0 on success, negative number on error, 1 if utabs update is * unnecessary. */ static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags) { int rc = 0; const char *o = NULL, *a = NULL; char *u = NULL; assert(fs); assert(upd); assert(upd->fs == NULL); assert(!(mountflags & MS_MOVE)); DBG(UPDATE, mnt_debug("prepare utab entry")); o = mnt_fs_get_user_options(fs); a = mnt_fs_get_attributes(fs); upd->fs = NULL; if (o) { /* remove non-mtab options */ rc = mnt_optstr_get_options(o, &u, mnt_get_builtin_optmap(MNT_USERSPACE_MAP), MNT_NOMTAB); if (rc) goto err; } if (!u && !a) { DBG(UPDATE, mnt_debug("utab entry unnecessary (no options)")); return 1; } /* allocate the entry */ upd->fs = mnt_copy_fs(NULL, fs); if (!upd->fs) { rc = -ENOMEM; goto err; } rc = mnt_fs_set_options(upd->fs, u); if (rc) goto err; rc = mnt_fs_set_attributes(upd->fs, a); if (rc) goto err; if (!(mountflags & MS_REMOUNT)) { rc = set_fs_root(upd, fs, mountflags); if (rc) goto err; } free(u); DBG(UPDATE, mnt_debug("utab entry OK")); return 0; err: free(u); mnt_free_fs(upd->fs); upd->fs = NULL; return rc; }