/** * mnt_optstr_deduplicate_option: * @optstr: string with a comma separated list of options * @name: requested option name * * Removes all instances of @name except the last one. * * Returns: 0 on success, 1 when not found the @name or negative number in case * of error. */ int mnt_optstr_deduplicate_option(char **optstr, const char *name) { int rc; char *begin = NULL, *end = NULL, *opt; assert(optstr); assert(name); opt = *optstr; do { struct libmnt_optloc ol; mnt_init_optloc(&ol); rc = mnt_optstr_locate_option(opt, name, &ol); if (!rc) { if (begin) { /* remove the previous instance */ size_t shift = strlen(*optstr); mnt_optstr_remove_option_at(optstr, begin, end); /* now all the offsets are not valid anymore - recount */ shift -= strlen(*optstr); ol.begin -= shift; ol.end -= shift; } begin = ol.begin; end = ol.end; opt = end && *end ? end + 1 : NULL; } } while (rc == 0 && opt && *opt); return rc < 0 ? rc : begin ? 0 : 1; }
/** * mnt_optstr_set_option: * @optstr: string with a comma separated list of options * @name: requested option * @value: new value or NULL * * Set or unset the option @value. * * Returns: 0 on success, 1 when not found the @name or negative number in case * of error. */ int mnt_optstr_set_option(char **optstr, const char *name, const char *value) { struct libmnt_optloc ol; char *nameend; int rc = 1; assert(optstr); assert(name); if (!optstr) return -EINVAL; mnt_init_optloc(&ol); if (*optstr) rc = mnt_optstr_locate_option(*optstr, name, &ol); if (rc < 0) return rc; /* parse error */ if (rc == 1) return mnt_optstr_append_option(optstr, name, value); /* not found */ nameend = ol.begin + ol.namesz; if (value == NULL && ol.value && ol.valsz) /* remove unwanted "=value" */ mnt_optstr_remove_option_at(optstr, nameend, ol.end); else if (value && ol.value == NULL) /* insert "=value" */ rc = insert_value(optstr, nameend, value, NULL); else if (value && ol.value && strlen(value) == ol.valsz) /* simply replace =value */ memcpy(ol.value, value, ol.valsz); else if (value && ol.value) { mnt_optstr_remove_option_at(optstr, nameend, ol.end); rc = insert_value(optstr, nameend, value, NULL); } return rc; }
/** * mnt_optstr_remove_option: * @optstr: string with comma separated list of options * @name: requested option name * * Returns: 0 on success, 1 when not found the @name or negative number in case * of error. */ int mnt_optstr_remove_option(char **optstr, const char *name) { struct libmnt_optloc ol; int rc; mnt_init_optloc(&ol); rc = mnt_optstr_locate_option(*optstr, name, &ol); if (rc != 0) return rc; mnt_optstr_remove_option_at(optstr, ol.begin, ol.end); return 0; }
/* * Converts already evalulated and fixed options to the form that is compatible * with /sbin/mount.type helpers. */ static int generate_helper_optstr(struct libmnt_context *cxt, char **optstr) { struct libmnt_optmap const *maps[1]; char *next, *name, *val; size_t namesz, valsz; int rc = 0; assert(cxt); assert(cxt->fs); assert(optstr); DBG(CXT, mnt_debug_h(cxt, "mount: generate heper mount options")); *optstr = mnt_fs_strdup_options(cxt->fs); if (!*optstr) return -ENOMEM; if (cxt->flags & MNT_FL_SAVED_USER) rc = mnt_optstr_set_option(optstr, "user", cxt->orig_user); if (rc) goto err; /* remove userspace options with MNT_NOHLPS flag */ maps[0] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP); next = *optstr; while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) { const struct libmnt_optmap *ent; mnt_optmap_get_entry(maps, 1, name, namesz, &ent); if (ent && ent->id && (ent->mask & MNT_NOHLPS)) { next = name; rc = mnt_optstr_remove_option_at(optstr, name, val ? val + valsz : name + namesz); if (rc) goto err; } } return rc; err: free(*optstr); *optstr = NULL; return rc; }
/* * this has to be called after mnt_context_evaluate_permissions() */ static int fix_optstr(struct libmnt_context *cxt) { int rc = 0; char *next; char *name, *val; size_t namesz, valsz; struct libmnt_fs *fs; #ifdef HAVE_LIBSELINUX int se_fix = 0, se_rem = 0; #endif assert(cxt); assert(cxt->fs); assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); if (!cxt) return -EINVAL; if (!cxt->fs || (cxt->flags & MNT_FL_MOUNTOPTS_FIXED)) return 0; DBG(CXT, mnt_debug_h(cxt, "mount: fixing optstr")); fs = cxt->fs; /* The propagation flags should not be used together with any other * flags (except MS_REC and MS_SILENT) */ if (cxt->mountflags & MS_PROPAGATION) cxt->mountflags &= (MS_PROPAGATION | MS_REC | MS_SILENT); if (!mnt_optstr_get_option(fs->user_optstr, "user", &val, &valsz)) { if (val) { cxt->orig_user = strndup(val, valsz); if (!cxt->orig_user) { rc = -ENOMEM; goto done; } } cxt->flags |= MNT_FL_SAVED_USER; } /* * Sync mount options with mount flags */ rc = mnt_optstr_apply_flags(&fs->vfs_optstr, cxt->mountflags, mnt_get_builtin_optmap(MNT_LINUX_MAP)); if (rc) goto done; rc = mnt_optstr_apply_flags(&fs->user_optstr, cxt->user_mountflags, mnt_get_builtin_optmap(MNT_USERSPACE_MAP)); if (rc) goto done; next = fs->fs_optstr; #ifdef HAVE_LIBSELINUX if (!is_selinux_enabled()) /* Always remove SELinux garbage if SELinux disabled */ se_rem = 1; else if (cxt->mountflags & MS_REMOUNT) /* * Linux kernel < 2.6.39 does not allow to remount with any * selinux specific mount options. * * Kernel 2.6.39 commits: ff36fe2c845cab2102e4826c1ffa0a6ebf487c65 * 026eb167ae77244458fa4b4b9fc171209c079ba7 * fix this odd behavior, so we don't have to care about it in * userspace. */ se_rem = get_linux_version() < KERNEL_VERSION(2, 6, 39); else /* For normal mount we have translate the contexts */ se_fix = 1; #endif while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) { if (namesz == 3 && !strncmp(name, "uid", 3)) rc = mnt_optstr_fix_uid(&fs->fs_optstr, val, valsz, &next); else if (namesz == 3 && !strncmp(name, "gid", 3)) rc = mnt_optstr_fix_gid(&fs->fs_optstr, val, valsz, &next); #ifdef HAVE_LIBSELINUX else if ((se_rem || se_fix) && namesz >= 7 && (!strncmp(name, "context", 7) || !strncmp(name, "fscontext", 9) || !strncmp(name, "defcontext", 10) || !strncmp(name, "rootcontext", 11) || !strncmp(name, "seclabel", 8))) { if (se_rem) { /* remove context= option */ next = name; rc = mnt_optstr_remove_option_at(&fs->fs_optstr, name, val ? val + valsz : name + namesz); } else if (se_fix && val && valsz) /* translate selinux contexts */ rc = mnt_optstr_fix_secontext(&fs->fs_optstr, val, valsz, &next); } #endif if (rc) goto done; } if (!rc && cxt->user_mountflags & MNT_MS_USER) rc = mnt_optstr_fix_user(&fs->user_optstr); /* refresh merged optstr */ free(fs->optstr); fs->optstr = NULL; fs->optstr = mnt_fs_strdup_options(fs); done: cxt->flags |= MNT_FL_MOUNTOPTS_FIXED; DBG(CXT, mnt_debug_h(cxt, "fixed options [rc=%d]: " "vfs: '%s' fs: '%s' user: '******', optstr: '%s'", rc, fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr)); if (rc) rc = -MNT_ERR_MOUNTOPT; 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; }