blob: e4092d2f758e4f56c5a9a4cfcd300b7faa186a79 [file] [log] [blame]
/* ----------------------------------------------------------------------- *
*
* mounts.c - module for mount utilities.
*
* Copyright 2002-2005 Ian Kent <raven@themaw.net> - All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
* USA; either version 2 of the License, or (at your option) any later
* version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/vfs.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include "automount.h"
#define MAX_OPTIONS_LEN 80
#define MAX_MNT_NAME_LEN 30
#define MAX_ENV_NAME 15
#define EBUFSIZ 1024
const unsigned int t_indirect = AUTOFS_TYPE_INDIRECT;
const unsigned int t_direct = AUTOFS_TYPE_DIRECT;
const unsigned int t_offset = AUTOFS_TYPE_OFFSET;
const unsigned int type_count = 3;
static const char options_template[] = "fd=%d,pgrp=%u,minproto=5,maxproto=%d";
static const char options_template_extra[] = "fd=%d,pgrp=%u,minproto=5,maxproto=%d,%s";
static const char mnt_name_template[] = "automount(pid%u)";
static struct kernel_mod_version kver = {0, 0};
static const char kver_options_template[] = "fd=%d,pgrp=%u,minproto=3,maxproto=5";
extern size_t detached_thread_stack_size;
static size_t maxgrpbuf = 0;
#define EXT_MOUNTS_HASH_SIZE 50
struct ext_mount {
char *mountpoint;
unsigned int umount;
struct list_head mount;
struct list_head mounts;
};
static struct list_head ext_mounts_hash[EXT_MOUNTS_HASH_SIZE];
static unsigned int ext_mounts_hash_init_done = 0;
static pthread_mutex_t ext_mount_hash_mutex = PTHREAD_MUTEX_INITIALIZER;
unsigned int linux_version_code(void)
{
struct utsname my_utsname;
unsigned int p, q, r;
char *tmp, *save;
if (uname(&my_utsname))
return 0;
p = q = r = 0;
tmp = strtok_r(my_utsname.release, ".", &save);
if (!tmp)
return 0;
p = (unsigned int ) atoi(tmp);
tmp = strtok_r(NULL, ".", &save);
if (!tmp)
return KERNEL_VERSION(p, 0, 0);
q = (unsigned int) atoi(tmp);
tmp = strtok_r(NULL, ".", &save);
if (!tmp)
return KERNEL_VERSION(p, q, 0);
r = (unsigned int) atoi(tmp);
return KERNEL_VERSION(p, q, r);
}
unsigned int query_kproto_ver(void)
{
struct ioctl_ops *ops;
char dir[] = "/tmp/autoXXXXXX", *t_dir;
char options[MAX_OPTIONS_LEN + 1];
pid_t pgrp = getpgrp();
int pipefd[2], ioctlfd, len;
struct stat st;
t_dir = mkdtemp(dir);
if (!t_dir)
return 0;
if (pipe(pipefd) == -1) {
rmdir(t_dir);
return 0;
}
len = snprintf(options, MAX_OPTIONS_LEN,
kver_options_template, pipefd[1], (unsigned) pgrp);
if (len < 0) {
close(pipefd[0]);
close(pipefd[1]);
rmdir(t_dir);
return 0;
}
if (mount("automount", t_dir, "autofs", MS_MGC_VAL, options)) {
close(pipefd[0]);
close(pipefd[1]);
rmdir(t_dir);
return 0;
}
close(pipefd[1]);
if (stat(t_dir, &st) == -1) {
umount(t_dir);
close(pipefd[0]);
rmdir(t_dir);
return 0;
}
ops = get_ioctl_ops();
if (!ops) {
umount(t_dir);
close(pipefd[0]);
rmdir(t_dir);
return 0;
}
ops->open(LOGOPT_NONE, &ioctlfd, st.st_dev, t_dir);
if (ioctlfd == -1) {
umount(t_dir);
close(pipefd[0]);
close_ioctl_ctl();
rmdir(t_dir);
return 0;
}
ops->catatonic(LOGOPT_NONE, ioctlfd);
/* If this ioctl() doesn't work, it is kernel version 2 */
if (ops->protover(LOGOPT_NONE, ioctlfd, &kver.major)) {
ops->close(LOGOPT_NONE, ioctlfd);
umount(t_dir);
close(pipefd[0]);
close_ioctl_ctl();
rmdir(t_dir);
return 0;
}
/* If this ioctl() doesn't work, version is 4 or less */
if (ops->protosubver(LOGOPT_NONE, ioctlfd, &kver.minor)) {
ops->close(LOGOPT_NONE, ioctlfd);
umount(t_dir);
close(pipefd[0]);
close_ioctl_ctl();
rmdir(t_dir);
return 0;
}
ops->close(LOGOPT_NONE, ioctlfd);
umount(t_dir);
close(pipefd[0]);
close_ioctl_ctl();
rmdir(t_dir);
return 1;
}
unsigned int get_kver_major(void)
{
return kver.major;
}
unsigned int get_kver_minor(void)
{
return kver.minor;
}
#ifdef HAVE_MOUNT_NFS
static int extract_version(char *start, struct nfs_mount_vers *vers)
{
char *s_ver = strchr(start, ' ');
if (!s_ver)
return 0;
while (*s_ver && !isdigit(*s_ver)) {
s_ver++;
if (!*s_ver)
return 0;
break;
}
vers->major = atoi(strtok(s_ver, "."));
vers->minor = (unsigned int) atoi(strtok(NULL, "."));
vers->fix = (unsigned int) atoi(strtok(NULL, "."));
return 1;
}
int check_nfs_mount_version(struct nfs_mount_vers *vers,
struct nfs_mount_vers *check)
{
pid_t f;
int ret, status, pipefd[2];
char errbuf[EBUFSIZ + 1], *p, *sp;
int errp, errn;
sigset_t allsigs, tmpsig, oldsig;
char *s_ver;
int cancel_state;
if (open_pipe(pipefd))
return -1;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
sigfillset(&allsigs);
pthread_sigmask(SIG_BLOCK, &allsigs, &oldsig);
open_mutex_lock();
f = fork();
if (f == 0) {
reset_signals();
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
execl(PATH_MOUNT_NFS, PATH_MOUNT_NFS, "-V", (char *) NULL);
_exit(255); /* execv() failed */
}
ret = 0;
tmpsig = oldsig;
sigaddset(&tmpsig, SIGCHLD);
pthread_sigmask(SIG_SETMASK, &tmpsig, NULL);
open_mutex_unlock();
close(pipefd[1]);
if (f < 0) {
close(pipefd[0]);
pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
pthread_setcancelstate(cancel_state, NULL);
return -1;
}
errp = 0;
do {
while (1) {
errn = read(pipefd[0], errbuf + errp, EBUFSIZ - errp);
if (errn == -1 && errno == EINTR)
continue;
break;
}
if (errn > 0) {
errp += errn;
sp = errbuf;
while (errp && (p = memchr(sp, '\n', errp))) {
*p++ = '\0';
errp -= (p - sp);
sp = p;
}
if (errp && sp != errbuf)
memmove(errbuf, sp, errp);
if (errp >= EBUFSIZ) {
/* Line too long, split */
errbuf[errp] = '\0';
if ((s_ver = strstr(errbuf, "nfs-utils"))) {
if (extract_version(s_ver, vers))
ret = 1;
}
errp = 0;
}
if ((s_ver = strstr(errbuf, "nfs-utils"))) {
if (extract_version(s_ver, vers))
ret = 1;
}
}
} while (errn > 0);
close(pipefd[0]);
if (errp > 0) {
/* End of file without \n */
errbuf[errp] = '\0';
if ((s_ver = strstr(errbuf, "nfs-utils"))) {
if (extract_version(s_ver, vers))
ret = 1;
}
}
if (ret) {
if ((vers->major < check->major) ||
((vers->major == check->major) && (vers->minor < check->minor)) ||
((vers->major == check->major) && (vers->minor == check->minor) &&
(vers->fix < check->fix)))
ret = 0;
}
if (waitpid(f, &status, 0) != f)
debug(LOGOPT_NONE, "no process found to wait for");
pthread_sigmask(SIG_SETMASK, &oldsig, NULL);
pthread_setcancelstate(cancel_state, NULL);
return ret;
}
#else
int check_nfs_mount_version(struct nfs_mount_vers *vers,
struct nfs_mount_vers *check)
{
return 0;
}
#endif
static char *set_env_name(const char *prefix, const char *name, char *buf)
{
size_t len;
len = strlen(name);
if (prefix)
len += strlen(prefix);
len++;
if (len > MAX_ENV_NAME)
return NULL;
if (!prefix)
strcpy(buf, name);
else {
strcpy(buf, prefix);
strcat(buf, name);
}
return buf;
}
static struct substvar *do_macro_addvar(struct substvar *list,
const char *prefix,
const char *name,
const char *val)
{
char buf[MAX_ENV_NAME + 1];
char *new;
size_t len;
new = set_env_name(prefix, name, buf);
if (new) {
len = strlen(new);
list = macro_addvar(list, new, len, val);
}
return list;
}
static struct substvar *do_macro_removevar(struct substvar *list,
const char *prefix,
const char *name)
{
char buf[MAX_ENV_NAME + 1];
char *new;
size_t len;
new = set_env_name(prefix, name, buf);
if (new) {
len = strlen(new);
list = macro_removevar(list, new, len);
}
return list;
}
struct substvar *addstdenv(struct substvar *sv, const char *prefix)
{
struct substvar *list = sv;
struct thread_stdenv_vars *tsv;
char numbuf[16];
tsv = pthread_getspecific(key_thread_stdenv_vars);
if (tsv) {
const struct substvar *mv;
int ret;
long num;
num = (long) tsv->uid;
ret = sprintf(numbuf, "%ld", num);
if (ret > 0)
list = do_macro_addvar(list, prefix, "UID", numbuf);
num = (long) tsv->gid;
ret = sprintf(numbuf, "%ld", num);
if (ret > 0)
list = do_macro_addvar(list, prefix, "GID", numbuf);
list = do_macro_addvar(list, prefix, "USER", tsv->user);
list = do_macro_addvar(list, prefix, "GROUP", tsv->group);
list = do_macro_addvar(list, prefix, "HOME", tsv->home);
mv = macro_findvar(list, "HOST", 4);
if (mv) {
char *shost = strdup(mv->val);
if (shost) {
char *dot = strchr(shost, '.');
if (dot)
*dot = '\0';
list = do_macro_addvar(list,
prefix, "SHOST", shost);
free(shost);
}
}
}
return list;
}
struct substvar *removestdenv(struct substvar *sv, const char *prefix)
{
struct substvar *list = sv;
list = do_macro_removevar(list, prefix, "UID");
list = do_macro_removevar(list, prefix, "USER");
list = do_macro_removevar(list, prefix, "HOME");
list = do_macro_removevar(list, prefix, "GID");
list = do_macro_removevar(list, prefix, "GROUP");
list = do_macro_removevar(list, prefix, "SHOST");
return list;
}
void add_std_amd_vars(struct substvar *sv)
{
char *tmp;
tmp = conf_amd_get_arch();
if (tmp) {
macro_global_addvar("arch", 4, tmp);
free(tmp);
}
tmp = conf_amd_get_karch();
if (tmp) {
macro_global_addvar("karch", 5, tmp);
free(tmp);
}
tmp = conf_amd_get_os();
if (tmp) {
macro_global_addvar("os", 2, tmp);
free(tmp);
}
tmp = conf_amd_get_full_os();
if (tmp) {
macro_global_addvar("full_os", 7, tmp);
free(tmp);
}
tmp = conf_amd_get_os_ver();
if (tmp) {
macro_global_addvar("osver", 5, tmp);
free(tmp);
}
tmp = conf_amd_get_vendor();
if (tmp) {
macro_global_addvar("vendor", 6, tmp);
free(tmp);
}
/* Umm ... HP_UX cluster name, probably not used */
tmp = conf_amd_get_cluster();
if (tmp) {
macro_global_addvar("cluster", 7, tmp);
free(tmp);
} else {
const struct substvar *v = macro_findvar(sv, "domain", 4);
if (v && *v->val) {
tmp = strdup(v->val);
if (tmp) {
macro_global_addvar("cluster", 7, tmp);
free(tmp);
}
}
}
tmp = conf_amd_get_auto_dir();
if (tmp) {
macro_global_addvar("autodir", 7, tmp);
free(tmp);
}
return;
}
void remove_std_amd_vars(void)
{
macro_global_removevar("autodir", 7);
macro_global_removevar("cluster", 7);
macro_global_removevar("vendor", 6);
macro_global_removevar("osver", 5);
macro_global_removevar("full_os", 7);
macro_global_removevar("os", 2);
macro_global_removevar("karch", 5);
macro_global_removevar("arch", 4);
return;
}
struct amd_entry *new_amd_entry(const struct substvar *sv)
{
struct amd_entry *new;
const struct substvar *v;
char *path;
v = macro_findvar(sv, "path", 4);
if (!v)
return NULL;
path = strdup(v->val);
if (!path)
return NULL;
new = malloc(sizeof(struct amd_entry));
if (!new) {
free(path);
return NULL;
}
memset(new, 0, sizeof(*new));
new->path = path;
INIT_LIST_HEAD(&new->list);
INIT_LIST_HEAD(&new->entries);
INIT_LIST_HEAD(&new->ext_mount);
return new;
}
void clear_amd_entry(struct amd_entry *entry)
{
if (!entry)
return;
if (entry->path)
free(entry->path);
if (entry->map_type)
free(entry->map_type);
if (entry->pref)
free(entry->pref);
if (entry->fs)
free(entry->fs);
if (entry->rhost)
free(entry->rhost);
if (entry->rfs)
free(entry->rfs);
if (entry->opts)
free(entry->opts);
if (entry->addopts)
free(entry->addopts);
if (entry->remopts)
free(entry->remopts);
if (entry->sublink)
free(entry->sublink);
if (entry->selector)
free_selector(entry->selector);
return;
}
void free_amd_entry(struct amd_entry *entry)
{
clear_amd_entry(entry);
free(entry);
return;
}
void free_amd_entry_list(struct list_head *entries)
{
if (!list_empty(entries)) {
struct list_head *head = entries;
struct amd_entry *this;
struct list_head *p;
p = head->next;
while (p != head) {
this = list_entry(p, struct amd_entry, list);
p = p->next;
free_amd_entry(this);
}
}
}
/*
* Make common autofs mount options string
*/
char *make_options_string(char *path, int pipefd, const char *extra)
{
char *options;
int len;
options = malloc(MAX_OPTIONS_LEN + 1);
if (!options) {
logerr("can't malloc options string");
return NULL;
}
if (extra)
len = snprintf(options, MAX_OPTIONS_LEN,
options_template_extra,
pipefd, (unsigned) getpgrp(),
AUTOFS_MAX_PROTO_VERSION, extra);
else
len = snprintf(options, MAX_OPTIONS_LEN, options_template,
pipefd, (unsigned) getpgrp(),
AUTOFS_MAX_PROTO_VERSION);
if (len >= MAX_OPTIONS_LEN) {
logerr("buffer to small for options - truncated");
len = MAX_OPTIONS_LEN - 1;
}
if (len < 0) {
logerr("failed to malloc autofs mount options for %s", path);
free(options);
return NULL;
}
options[len] = '\0';
return options;
}
char *make_mnt_name_string(char *path)
{
char *mnt_name;
int len;
mnt_name = malloc(MAX_MNT_NAME_LEN + 1);
if (!mnt_name) {
logerr("can't malloc mnt_name string");
return NULL;
}
len = snprintf(mnt_name, MAX_MNT_NAME_LEN,
mnt_name_template, (unsigned) getpid());
if (len >= MAX_MNT_NAME_LEN) {
logerr("buffer to small for mnt_name - truncated");
len = MAX_MNT_NAME_LEN - 1;
}
if (len < 0) {
logerr("failed setting up mnt_name for autofs path %s", path);
free(mnt_name);
return NULL;
}
mnt_name[len] = '\0';
return mnt_name;
}
static void ext_mounts_hash_init(void)
{
int i;
for (i = 0; i < EXT_MOUNTS_HASH_SIZE; i++)
INIT_LIST_HEAD(&ext_mounts_hash[i]);
ext_mounts_hash_init_done = 1;
}
static struct ext_mount *ext_mount_lookup(const char *mountpoint)
{
u_int32_t hval = hash(mountpoint, EXT_MOUNTS_HASH_SIZE);
struct list_head *p, *head;
if (!ext_mounts_hash_init_done)
ext_mounts_hash_init();
if (list_empty(&ext_mounts_hash[hval]))
return NULL;
head = &ext_mounts_hash[hval];
list_for_each(p, head) {
struct ext_mount *this = list_entry(p, struct ext_mount, mount);
if (!strcmp(this->mountpoint, mountpoint))
return this;
}
return NULL;
}
int ext_mount_add(struct list_head *entry, const char *path, unsigned int umount)
{
struct ext_mount *em;
u_int32_t hval;
int ret = 0;
pthread_mutex_lock(&ext_mount_hash_mutex);
em = ext_mount_lookup(path);
if (em) {
struct list_head *p, *head;
head = &em->mounts;
list_for_each(p, head) {
if (p == entry)
goto done;
}
list_add_tail(entry, &em->mounts);
ret = 1;
goto done;
}
em = malloc(sizeof(struct ext_mount));
if (!em)
goto done;
em->mountpoint = strdup(path);
if (!em->mountpoint) {
free(em);
goto done;
}
em->umount = umount;
INIT_LIST_HEAD(&em->mount);
INIT_LIST_HEAD(&em->mounts);
hval = hash(path, EXT_MOUNTS_HASH_SIZE);
list_add_tail(&em->mount, &ext_mounts_hash[hval]);
list_add_tail(entry, &em->mounts);
ret = 1;
done:
pthread_mutex_unlock(&ext_mount_hash_mutex);
return ret;
}
int ext_mount_remove(struct list_head *entry, const char *path)
{
struct ext_mount *em;
int ret = 0;
pthread_mutex_lock(&ext_mount_hash_mutex);
em = ext_mount_lookup(path);
if (!em)
goto done;
list_del_init(entry);
if (!list_empty(&em->mounts))
goto done;
else {
list_del_init(&em->mount);
if (em->umount)
ret = 1;
if (list_empty(&em->mount)) {
free(em->mountpoint);
free(em);
}
}
done:
pthread_mutex_unlock(&ext_mount_hash_mutex);
return ret;
}
int ext_mount_inuse(const char *path)
{
struct ext_mount *em;
int ret = 0;
pthread_mutex_lock(&ext_mount_hash_mutex);
em = ext_mount_lookup(path);
if (!em)
goto done;
if (!list_empty(&em->mounts))
ret = 1;
done:
pthread_mutex_unlock(&ext_mount_hash_mutex);
return ret;
}
/*
* Get list of mounts under path in longest->shortest order
*/
struct mnt_list *get_mnt_list(const char *table, const char *path, int include)
{
FILE *tab;
size_t pathlen = strlen(path);
struct mntent mnt_wrk;
char buf[PATH_MAX * 3];
struct mntent *mnt;
struct mnt_list *ent, *mptr, *last;
struct mnt_list *list = NULL;
char *pgrp;
size_t len;
if (!path || !pathlen || pathlen > PATH_MAX)
return NULL;
tab = open_setmntent_r(table);
if (!tab) {
char *estr = strerror_r(errno, buf, PATH_MAX - 1);
logerr("setmntent: %s", estr);
return NULL;
}
while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) {
len = strlen(mnt->mnt_dir);
if ((!include && len <= pathlen) ||
strncmp(mnt->mnt_dir, path, pathlen) != 0)
continue;
/* Not a subdirectory of requested path ? */
/* pathlen == 1 => everything is subdir */
if (pathlen > 1 && len > pathlen &&
mnt->mnt_dir[pathlen] != '/')
continue;
ent = malloc(sizeof(*ent));
if (!ent) {
endmntent(tab);
free_mnt_list(list);
return NULL;
}
memset(ent, 0, sizeof(*ent));
mptr = list;
last = NULL;
while (mptr) {
if (len >= strlen(mptr->path))
break;
last = mptr;
mptr = mptr->next;
}
if (mptr == list)
list = ent;
else
last->next = ent;
ent->next = mptr;
ent->path = malloc(len + 1);
if (!ent->path) {
endmntent(tab);
free_mnt_list(list);
return NULL;
}
strcpy(ent->path, mnt->mnt_dir);
ent->fs_name = malloc(strlen(mnt->mnt_fsname) + 1);
if (!ent->fs_name) {
endmntent(tab);
free_mnt_list(list);
return NULL;
}
strcpy(ent->fs_name, mnt->mnt_fsname);
ent->fs_type = malloc(strlen(mnt->mnt_type) + 1);
if (!ent->fs_type) {
endmntent(tab);
free_mnt_list(list);
return NULL;
}
strcpy(ent->fs_type, mnt->mnt_type);
ent->opts = malloc(strlen(mnt->mnt_opts) + 1);
if (!ent->opts) {
endmntent(tab);
free_mnt_list(list);
return NULL;
}
strcpy(ent->opts, mnt->mnt_opts);
ent->owner = 0;
pgrp = strstr(mnt->mnt_opts, "pgrp=");
if (pgrp) {
char *end = strchr(pgrp, ',');
if (end)
*end = '\0';
sscanf(pgrp, "pgrp=%d", &ent->owner);
}
}
endmntent(tab);
return list;
}
/*
* Reverse a list of mounts
*/
struct mnt_list *reverse_mnt_list(struct mnt_list *list)
{
struct mnt_list *next, *last;
if (!list)
return NULL;
next = list;
last = NULL;
while (next) {
struct mnt_list *this = next;
next = this->next;
this->next = last;
last = this;
}
return last;
}
void free_mnt_list(struct mnt_list *list)
{
struct mnt_list *next;
if (!list)
return;
next = list;
while (next) {
struct mnt_list *this = next;
next = this->next;
if (this->path)
free(this->path);
if (this->fs_name)
free(this->fs_name);
if (this->fs_type)
free(this->fs_type);
if (this->opts)
free(this->opts);
free(this);
}
}
static int table_is_mounted(const char *table, const char *path, unsigned int type)
{
struct mntent *mnt;
struct mntent mnt_wrk;
char buf[PATH_MAX * 3];
size_t pathlen = strlen(path);
FILE *tab;
int ret = 0;
if (!path || !pathlen || pathlen >= PATH_MAX)
return 0;
tab = open_setmntent_r(table);
if (!tab) {
char *estr = strerror_r(errno, buf, PATH_MAX - 1);
logerr("setmntent: %s", estr);
return 0;
}
while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) {
size_t len = strlen(mnt->mnt_dir);
if (type) {
unsigned int autofs_fs;
autofs_fs = !strcmp(mnt->mnt_type, "autofs");
if (type & MNTS_REAL)
if (autofs_fs)
continue;
if (type & MNTS_AUTOFS)
if (!autofs_fs)
continue;
}
if (pathlen == len && !strncmp(path, mnt->mnt_dir, pathlen)) {
ret = 1;
break;
}
}
endmntent(tab);
return ret;
}
static int ioctl_is_mounted(const char *table, const char *path, unsigned int type)
{
struct ioctl_ops *ops = get_ioctl_ops();
unsigned int mounted;
int ret;
/* If the ioctl fails fall back to the potentially resource
* intensive mount table check.
*/
ret = ops->ismountpoint(LOGOPT_NONE, -1, path, &mounted);
if (ret == -1)
return table_is_mounted(table, path, type);
if (mounted) {
switch (type) {
case MNTS_ALL:
return 1;
case MNTS_AUTOFS:
return (mounted & DEV_IOCTL_IS_AUTOFS);
case MNTS_REAL:
return (mounted & DEV_IOCTL_IS_OTHER);
}
}
return 0;
}
int is_mounted(const char *table, const char *path, unsigned int type)
{
struct ioctl_ops *ops = get_ioctl_ops();
if (ops->ismountpoint)
return ioctl_is_mounted(table, path, type);
else
return table_is_mounted(table, path, type);
}
int has_fstab_option(const char *opt)
{
struct mntent *mnt;
struct mntent mnt_wrk;
char buf[PATH_MAX * 3];
FILE *tab;
int ret = 0;
if (!opt)
return 0;
tab = open_setmntent_r(_PATH_MNTTAB);
if (!tab) {
char *estr = strerror_r(errno, buf, PATH_MAX - 1);
logerr("setmntent: %s", estr);
return 0;
}
while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) {
if (hasmntopt(mnt, opt)) {
ret = 1;
break;
}
}
endmntent(tab);
return ret;
}
/*
* Since we have to look at the entire mount tree for direct
* mounts (all mounts under "/") and we may have a large number
* of entries to traverse again and again we need to
* use a more efficient method than the routines above.
*
* Thre tree_... routines allow us to read the mount tree
* once and pass it to subsequent functions for use. Since
* it's a tree structure searching should be a low overhead
* operation.
*/
void tree_free_mnt_tree(struct mnt_list *tree)
{
struct list_head *head, *p;
if (!tree)
return;
tree_free_mnt_tree(tree->left);
tree_free_mnt_tree(tree->right);
head = &tree->self;
p = head->next;
while (p != head) {
struct mnt_list *this;
this = list_entry(p, struct mnt_list, self);
p = p->next;
list_del(&this->self);
free(this->path);
free(this->fs_name);
free(this->fs_type);
if (this->opts)
free(this->opts);
free(this);
}
free(tree->path);
free(tree->fs_name);
free(tree->fs_type);
if (tree->opts)
free(tree->opts);
free(tree);
}
/*
* Make tree of system mounts in /proc/mounts.
*/
struct mnt_list *tree_make_mnt_tree(const char *table, const char *path)
{
FILE *tab;
struct mntent mnt_wrk;
char buf[PATH_MAX * 3];
struct mntent *mnt;
struct mnt_list *ent, *mptr;
struct mnt_list *tree = NULL;
char *pgrp;
size_t plen;
int eq;
tab = open_setmntent_r(table);
if (!tab) {
char *estr = strerror_r(errno, buf, PATH_MAX - 1);
logerr("setmntent: %s", estr);
return NULL;
}
plen = strlen(path);
while ((mnt = getmntent_r(tab, &mnt_wrk, buf, PATH_MAX * 3))) {
size_t len = strlen(mnt->mnt_dir);
/* Not matching path */
if (strncmp(mnt->mnt_dir, path, plen))
continue;
/* Not a subdirectory of requested path */
if (plen > 1 && len > plen && mnt->mnt_dir[plen] != '/')
continue;
ent = malloc(sizeof(*ent));
if (!ent) {
endmntent(tab);
tree_free_mnt_tree(tree);
return NULL;
}
memset(ent, 0, sizeof(*ent));
INIT_LIST_HEAD(&ent->self);
INIT_LIST_HEAD(&ent->list);
INIT_LIST_HEAD(&ent->entries);
INIT_LIST_HEAD(&ent->sublist);
ent->path = malloc(len + 1);
if (!ent->path) {
endmntent(tab);
free(ent);
tree_free_mnt_tree(tree);
return NULL;
}
strcpy(ent->path, mnt->mnt_dir);
ent->fs_name = malloc(strlen(mnt->mnt_fsname) + 1);
if (!ent->fs_name) {
free(ent->path);
free(ent);
endmntent(tab);
tree_free_mnt_tree(tree);
return NULL;
}
strcpy(ent->fs_name, mnt->mnt_fsname);
ent->fs_type = malloc(strlen(mnt->mnt_type) + 1);
if (!ent->fs_type) {
free(ent->fs_name);
free(ent->path);
free(ent);
endmntent(tab);
tree_free_mnt_tree(tree);
return NULL;
}
strcpy(ent->fs_type, mnt->mnt_type);
ent->opts = malloc(strlen(mnt->mnt_opts) + 1);
if (!ent->opts) {
free(ent->fs_type);
free(ent->fs_name);
free(ent->path);
free(ent);
endmntent(tab);
tree_free_mnt_tree(tree);
return NULL;
}
strcpy(ent->opts, mnt->mnt_opts);
ent->owner = 0;
pgrp = strstr(mnt->mnt_opts, "pgrp=");
if (pgrp) {
char *end = strchr(pgrp, ',');
if (end)
*end = '\0';
sscanf(pgrp, "pgrp=%d", &ent->owner);
}
mptr = tree;
while (mptr) {
int elen = strlen(ent->path);
int mlen = strlen(mptr->path);
if (elen < mlen) {
if (mptr->left) {
mptr = mptr->left;
continue;
} else {
mptr->left = ent;
break;
}
} else if (elen > mlen) {
if (mptr->right) {
mptr = mptr->right;
continue;
} else {
mptr->right = ent;
break;
}
}
eq = strcmp(ent->path, mptr->path);
if (eq < 0) {
if (mptr->left)
mptr = mptr->left;
else {
mptr->left = ent;
break;
}
} else if (eq > 0) {
if (mptr->right)
mptr = mptr->right;
else {
mptr->right = ent;
break;
}
} else {
list_add_tail(&ent->self, &mptr->self);
break;
}
}
if (!tree)
tree = ent;
}
endmntent(tab);
return tree;
}
/*
* Get list of mounts under "path" in longest->shortest order
*/
int tree_get_mnt_list(struct mnt_list *mnts, struct list_head *list, const char *path, int include)
{
size_t mlen, plen;
if (!mnts)
return 0;
plen = strlen(path);
mlen = strlen(mnts->path);
if (mlen < plen)
return tree_get_mnt_list(mnts->right, list, path, include);
else {
struct list_head *self, *p;
tree_get_mnt_list(mnts->left, list, path, include);
if ((!include && mlen <= plen) ||
strncmp(mnts->path, path, plen))
goto skip;
if (plen > 1 && mlen > plen && mnts->path[plen] != '/')
goto skip;
INIT_LIST_HEAD(&mnts->list);
list_add(&mnts->list, list);
self = &mnts->self;
list_for_each(p, self) {
struct mnt_list *this;
this = list_entry(p, struct mnt_list, self);
INIT_LIST_HEAD(&this->list);
list_add(&this->list, list);
}
skip:
tree_get_mnt_list(mnts->right, list, path, include);
}
if (list_empty(list))
return 0;
return 1;
}
/*
* Get list of mounts under "path" in longest->shortest order
*/
int tree_get_mnt_sublist(struct mnt_list *mnts, struct list_head *list, const char *path, int include)
{
size_t mlen, plen;
if (!mnts)
return 0;
plen = strlen(path);
mlen = strlen(mnts->path);
if (mlen < plen)
return tree_get_mnt_sublist(mnts->right, list, path, include);
else {
struct list_head *self, *p;
tree_get_mnt_sublist(mnts->left, list, path, include);
if ((!include && mlen <= plen) ||
strncmp(mnts->path, path, plen))
goto skip;
if (plen > 1 && mlen > plen && mnts->path[plen] != '/')
goto skip;
INIT_LIST_HEAD(&mnts->sublist);
list_add(&mnts->sublist, list);
self = &mnts->self;
list_for_each(p, self) {
struct mnt_list *this;
this = list_entry(p, struct mnt_list, self);
INIT_LIST_HEAD(&this->sublist);
list_add(&this->sublist, list);
}
skip:
tree_get_mnt_sublist(mnts->right, list, path, include);
}
if (list_empty(list))
return 0;
return 1;
}
int tree_find_mnt_ents(struct mnt_list *mnts, struct list_head *list, const char *path)
{
int mlen, plen;
if (!mnts)
return 0;
plen = strlen(path);
mlen = strlen(mnts->path);
if (mlen < plen)
return tree_find_mnt_ents(mnts->right, list, path);
else if (mlen > plen)
return tree_find_mnt_ents(mnts->left, list, path);
else {
struct list_head *self, *p;
tree_find_mnt_ents(mnts->left, list, path);
if (!strcmp(mnts->path, path)) {
INIT_LIST_HEAD(&mnts->entries);
list_add(&mnts->entries, list);
}
self = &mnts->self;
list_for_each(p, self) {
struct mnt_list *this;
this = list_entry(p, struct mnt_list, self);
if (!strcmp(this->path, path)) {
INIT_LIST_HEAD(&this->entries);
list_add(&this->entries, list);
}
}
tree_find_mnt_ents(mnts->right, list, path);
if (!list_empty(list))
return 1;
}
return 0;
}
int tree_is_mounted(struct mnt_list *mnts, const char *path, unsigned int type)
{
struct ioctl_ops *ops = get_ioctl_ops();
struct list_head *p;
struct list_head list;
int mounted = 0;
if (ops->ismountpoint)
return ioctl_is_mounted(_PROC_MOUNTS, path, type);
INIT_LIST_HEAD(&list);
if (!tree_find_mnt_ents(mnts, &list, path))
return 0;
list_for_each(p, &list) {
struct mnt_list *mptr;
mptr = list_entry(p, struct mnt_list, entries);
if (type) {
unsigned int autofs_fs;
autofs_fs = !strcmp(mptr->fs_type, "autofs");
if (type & MNTS_REAL) {
if (!autofs_fs) {
mounted = 1;
break;
}
} else if (type & MNTS_AUTOFS) {
if (autofs_fs) {
mounted = 1;
break;
}
} else {
mounted = 1;
break;
}
}
}
return mounted;
}
void set_tsd_user_vars(unsigned int logopt, uid_t uid, gid_t gid)
{
struct thread_stdenv_vars *tsv;
struct passwd pw;
struct passwd *ppw = &pw;
struct passwd **pppw = &ppw;
struct group gr;
struct group *pgr;
struct group **ppgr;
char *pw_tmp, *gr_tmp;
int status, tmplen, grplen;
/*
* Setup thread specific data values for macro
* substution in map entries during the mount.
* Best effort only as it must go ahead.
*/
tsv = malloc(sizeof(struct thread_stdenv_vars));
if (!tsv) {
error(logopt, "failed alloc tsv storage");
return;
}
memset(tsv, 0, sizeof(struct thread_stdenv_vars));
tsv->uid = uid;
tsv->gid = gid;
/* Try to get passwd info */
tmplen = sysconf(_SC_GETPW_R_SIZE_MAX);
if (tmplen < 0) {
error(logopt, "failed to get buffer size for getpwuid_r");
goto free_tsv;
}
pw_tmp = malloc(tmplen + 1);
if (!pw_tmp) {
error(logopt, "failed to malloc buffer for getpwuid_r");
goto free_tsv;
}
status = getpwuid_r(uid, ppw, pw_tmp, tmplen, pppw);
if (status || !ppw) {
error(logopt, "failed to get passwd info from getpwuid_r");
free(pw_tmp);
goto free_tsv;
}
tsv->user = strdup(pw.pw_name);
if (!tsv->user) {
error(logopt, "failed to malloc buffer for user");
free(pw_tmp);
goto free_tsv;
}
tsv->home = strdup(pw.pw_dir);
if (!tsv->home) {
error(logopt, "failed to malloc buffer for home");
free(pw_tmp);
goto free_tsv_user;
}
free(pw_tmp);
/* Try to get group info */
grplen = sysconf(_SC_GETGR_R_SIZE_MAX);
if (grplen < 0) {
error(logopt, "failed to get buffer size for getgrgid_r");
goto free_tsv_home;
}
gr_tmp = NULL;
status = ERANGE;
#ifdef ENABLE_LIMIT_GETGRGID_SIZE
if (!maxgrpbuf)
maxgrpbuf = detached_thread_stack_size * 0.9;
#endif
/* If getting the group name fails go on without it. It's
* used to set an environment variable for program maps
* which may or may not use it so it isn't critical to
* operation.
*/
tmplen = grplen;
while (1) {
char *tmp = realloc(gr_tmp, tmplen + 1);
if (!tmp) {
error(logopt, "failed to malloc buffer for getgrgid_r");
goto no_group;
}
gr_tmp = tmp;
pgr = &gr;
ppgr = &pgr;
status = getgrgid_r(gid, pgr, gr_tmp, tmplen, ppgr);
if (status != ERANGE)
break;
tmplen += grplen;
/* Don't tempt glibc to alloca() larger than is (likely)
* available on the stack if limit-getgrgid-size is enabled.
*/
if (!maxgrpbuf || (tmplen < maxgrpbuf))
continue;
/* Add a message so we know this happened */
debug(logopt, "group buffer allocation would be too large");
break;
}
no_group:
if (status || !pgr)
error(logopt, "failed to get group info from getgrgid_r");
else {
tsv->group = strdup(gr.gr_name);
if (!tsv->group)
error(logopt, "failed to malloc buffer for group");
}
if (gr_tmp)
free(gr_tmp);
status = pthread_setspecific(key_thread_stdenv_vars, tsv);
if (status) {
error(logopt, "failed to set stdenv thread var");
goto free_tsv_group;
}
return;
free_tsv_group:
if (tsv->group)
free(tsv->group);
free_tsv_home:
free(tsv->home);
free_tsv_user:
free(tsv->user);
free_tsv:
free(tsv);
return;
}
const char *mount_type_str(const unsigned int type)
{
static const char *str_type[] = {
"indirect",
"direct",
"offset"
};
unsigned int pos, i;
for (pos = 0, i = type; pos < type_count; i >>= 1, pos++)
if (i & 0x1)
break;
return (pos == type_count ? NULL : str_type[pos]);
}
void set_exp_timeout(struct autofs_point *ap,
struct map_source *source, time_t timeout)
{
ap->exp_timeout = timeout;
if (source)
source->exp_timeout = timeout;
}
time_t get_exp_timeout(struct autofs_point *ap, struct map_source *source)
{
time_t timeout = ap->exp_timeout;
if (source && ap->type == LKP_DIRECT)
timeout = source->exp_timeout;
return timeout;
}
void notify_mount_result(struct autofs_point *ap,
const char *path, time_t timeout, const char *type)
{
if (timeout)
info(ap->logopt,
"mounted %s on %s with timeout %u, freq %u seconds",
type, path, (unsigned int) timeout,
(unsigned int) ap->exp_runfreq);
else
info(ap->logopt,
"mounted %s on %s with timeouts disabled",
type, path);
return;
}
static int do_remount_direct(struct autofs_point *ap, int fd, const char *path)
{
struct ioctl_ops *ops = get_ioctl_ops();
int status = REMOUNT_SUCCESS;
uid_t uid;
gid_t gid;
int ret;
ops->requester(ap->logopt, fd, path, &uid, &gid);
if (uid != -1 && gid != -1)
set_tsd_user_vars(ap->logopt, uid, gid);
ret = lookup_nss_mount(ap, NULL, path, strlen(path));
if (ret)
info(ap->logopt, "re-connected to %s", path);
else {
status = REMOUNT_FAIL;
info(ap->logopt, "failed to re-connect %s", path);
}
return status;
}
static int do_remount_indirect(struct autofs_point *ap, int fd, const char *path)
{
struct ioctl_ops *ops = get_ioctl_ops();
int status = REMOUNT_SUCCESS;
struct dirent **de;
char buf[PATH_MAX + 1];
uid_t uid;
gid_t gid;
unsigned int mounted;
int n, size;
n = scandir(path, &de, 0, alphasort);
if (n < 0)
return -1;
size = sizeof(buf);
while (n--) {
int ret, len;
if (strcmp(de[n]->d_name, ".") == 0 ||
strcmp(de[n]->d_name, "..") == 0) {
free(de[n]);
continue;
}
ret = cat_path(buf, size, path, de[n]->d_name);
if (!ret) {
do {
free(de[n]);
} while (n--);
free(de);
return -1;
}
ops->ismountpoint(ap->logopt, -1, buf, &mounted);
if (!mounted) {
struct dirent **de2;
int i, j;
i = j = scandir(buf, &de2, 0, alphasort);
if (i < 0) {
free(de[n]);
continue;
}
while (i--)
free(de2[i]);
free(de2);
if (j <= 2) {
free(de[n]);
continue;
}
}
ops->requester(ap->logopt, fd, buf, &uid, &gid);
if (uid != -1 && gid != -1)
set_tsd_user_vars(ap->logopt, uid, gid);
len = strlen(de[n]->d_name);
ret = lookup_nss_mount(ap, NULL, de[n]->d_name, len);
if (ret)
info(ap->logopt, "re-connected to %s", buf);
else {
status = REMOUNT_FAIL;
info(ap->logopt, "failed to re-connect %s", buf);
}
free(de[n]);
}
free(de);
return status;
}
static int remount_active_mount(struct autofs_point *ap,
struct mapent *me, const char *path, dev_t devid,
const unsigned int type, int *ioctlfd)
{
struct ioctl_ops *ops = get_ioctl_ops();
const char *str_type = mount_type_str(type);
char buf[MAX_ERR_BUF];
unsigned int mounted;
time_t timeout;
struct stat st;
int fd;
*ioctlfd = -1;
/* Open failed, no mount present */
ops->open(ap->logopt, &fd, devid, path);
if (fd == -1)
return REMOUNT_OPEN_FAIL;
if (!me)
timeout = get_exp_timeout(ap, NULL);
else
timeout = get_exp_timeout(ap, me->source);
/* Re-reading the map, set timeout and return */
if (ap->state == ST_READMAP) {
debug(ap->logopt, "already mounted, update timeout");
ops->timeout(ap->logopt, fd, timeout);
ops->close(ap->logopt, fd);
return REMOUNT_READ_MAP;
}
debug(ap->logopt, "trying to re-connect to mount %s", path);
/* Mounted so set pipefd and timeout etc. */
if (ops->catatonic(ap->logopt, fd) == -1) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "set catatonic failed: %s", estr);
debug(ap->logopt, "couldn't re-connect to mount %s", path);
ops->close(ap->logopt, fd);
return REMOUNT_OPEN_FAIL;
}
if (ops->setpipefd(ap->logopt, fd, ap->kpipefd) == -1) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "set pipefd failed: %s", estr);
debug(ap->logopt, "couldn't re-connect to mount %s", path);
ops->close(ap->logopt, fd);
return REMOUNT_OPEN_FAIL;
}
ops->timeout(ap->logopt, fd, timeout);
if (fstat(fd, &st) == -1) {
error(ap->logopt,
"failed to stat %s mount %s", str_type, path);
debug(ap->logopt, "couldn't re-connect to mount %s", path);
ops->close(ap->logopt, fd);
return REMOUNT_STAT_FAIL;
}
if (type != t_indirect)
cache_set_ino_index(me->mc, path, st.st_dev, st.st_ino);
else
ap->dev = st.st_dev;
notify_mount_result(ap, path, timeout, str_type);
*ioctlfd = fd;
/* Any mounts on or below? */
if (ops->ismountpoint(ap->logopt, fd, path, &mounted) == -1) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "ismountpoint %s failed: %s", path, estr);
debug(ap->logopt, "couldn't re-connect to mount %s", path);
ops->close(ap->logopt, fd);
return REMOUNT_FAIL;
}
if (!mounted) {
/*
* If we're an indirect mount we pass back the fd.
* But if were a direct or offset mount with no active
* mount we don't retain an open file descriptor.
*/
if (type != t_indirect) {
ops->close(ap->logopt, fd);
*ioctlfd = -1;
}
} else {
/*
* What can I do if we can't remount the existing
* mount(s) (possibly a partial failure), everything
* following will be broken?
*/
if (type == t_indirect)
do_remount_indirect(ap, fd, path);
else
do_remount_direct(ap, fd, path);
}
debug(ap->logopt, "re-connected to mount %s", path);
return REMOUNT_SUCCESS;
}
int try_remount(struct autofs_point *ap, struct mapent *me, unsigned int type)
{
struct ioctl_ops *ops = get_ioctl_ops();
const char *path;
int ret, fd;
dev_t devid;
if (type == t_indirect)
path = ap->path;
else
path = me->key;
ret = ops->mount_device(ap->logopt, path, type, &devid);
if (ret == -1 || ret == 0)
return -1;
ret = remount_active_mount(ap, me, path, devid, type, &fd);
/*
* The directory must exist since we found a device
* number for the mount but we can't know if we created
* it or not. However, if this is an indirect mount with
* the nobrowse option we need to remove the mount point
* directory at umount anyway.
*/
if (type == t_indirect) {
if (ap->flags & MOUNT_FLAG_GHOST)
ap->flags &= ~MOUNT_FLAG_DIR_CREATED;
else
ap->flags |= MOUNT_FLAG_DIR_CREATED;
} else
me->flags &= ~MOUNT_FLAG_DIR_CREATED;
/*
* Either we opened the mount or we're re-reading the map.
* If we opened the mount and ioctlfd is not -1 we have
* a descriptor for the indirect mount so we need to
* record that in the mount point struct. Otherwise we're
* re-reading the map.
*/
if (ret == REMOUNT_READ_MAP)
return 1;
else if (ret == REMOUNT_SUCCESS) {
if (fd != -1) {
if (type == t_indirect)
ap->ioctlfd = fd;
else
me->ioctlfd = fd;
return 1;
}
/* Indirect mount requires a valid fd */
if (type != t_indirect)
return 1;
}
/*
* Since we got the device number above a mount exists so
* any other failure warrants a failure return here.
*/
return 0;
}
/*
* When exiting mounts need be set catatonic, regardless of whether they
* are busy on not, to avoid a hang on access once the daemon has gone
* away.
*/
static int set_mount_catatonic(struct autofs_point *ap, struct mapent *me, int ioctlfd)
{
struct ioctl_ops *ops = get_ioctl_ops();
unsigned int opened = 0;
char buf[MAX_ERR_BUF];
char *path;
int fd = -1;
int error;
dev_t dev;
path = ap->path;
dev = ap->dev;
if (me && (ap->type == LKP_DIRECT || *me->key == '/')) {
path = me->key;
dev = me->dev;
}
if (ioctlfd >= 0)
fd = ioctlfd;
else if (me && me->ioctlfd >= 0)
fd = me->ioctlfd;
else {
error = ops->open(ap->logopt, &fd, dev, path);
if (error == -1) {
int err = errno;
char *estr;
if (errno == ENOENT)
return 0;
estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt,
"failed to open ioctlfd for %s, error: %s",
path, estr);
return err;
}
opened = 1;
}
if (fd >= 0) {
error = ops->catatonic(ap->logopt, fd);
if (error == -1) {
int err = errno;
char *estr;
estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt,
"failed to set %s catatonic, error: %s",
path, estr);
if (opened)
ops->close(ap->logopt, fd);
return err;
}
if (opened)
ops->close(ap->logopt, fd);
}
debug(ap->logopt, "set %s catatonic", path);
return 0;
}
static void set_multi_mount_tree_catatonic(struct autofs_point *ap, struct mapent *me)
{
if (!list_empty(&me->multi_list)) {
struct list_head *head = &me->multi_list;
struct list_head *p;
list_for_each(p, head) {
struct mapent *this;
this = list_entry(p, struct mapent, multi_list);
set_mount_catatonic(ap, this, this->ioctlfd);
}
}
}
void set_indirect_mount_tree_catatonic(struct autofs_point *ap)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent_cache *mc;
struct mapent *me;
if (!is_mounted(_PROC_MOUNTS, ap->path, MNTS_AUTOFS))
return;
map = entry->maps;
while (map) {
mc = map->mc;
cache_readlock(mc);
me = cache_enumerate(mc, NULL);
while (me) {
/* Skip negative map entries and wildcard entries */
if (!me->mapent)
goto next;
if (!strcmp(me->key, "*"))
goto next;
/* Only need to set offset mounts catatonic */
if (me->multi && me->multi == me)
set_multi_mount_tree_catatonic(ap, me);
next:
me = cache_enumerate(mc, me);
}
cache_unlock(mc);
map = map->next;
}
/* By the time this function is called ap->ioctlfd will have
* been closed so don't try and use it.
*/
set_mount_catatonic(ap, NULL, -1);
return;
}
void set_direct_mount_tree_catatonic(struct autofs_point *ap, struct mapent *me)
{
/* Set offset mounts catatonic for this mapent */
if (me->multi && me->multi == me)
set_multi_mount_tree_catatonic(ap, me);
set_mount_catatonic(ap, me, me->ioctlfd);
}
int umount_ent(struct autofs_point *ap, const char *path)
{
int rv;
if (ap->state != ST_SHUTDOWN_FORCE)
rv = spawn_umount(ap->logopt, path, NULL);
else {
/* We are doing a forced shutdown so unlink busy
* mounts */
info(ap->logopt, "forcing umount of %s", path);
rv = spawn_umount(ap->logopt, "-l", path, NULL);
}
if (rv && (ap->state == ST_SHUTDOWN_FORCE || ap->state == ST_SHUTDOWN)) {
/*
* Verify that we actually unmounted the thing. This is a
* belt and suspenders approach to not eating user data.
* We have seen cases where umount succeeds, but there is
* still a file system mounted on the mount point. How
* this happens has not yet been determined, but we want to
* make sure to return failure here, if that is the case,
* so that we do not try to call rmdir_path on the
* directory.
*/
if (!rv && is_mounted(_PATH_MOUNTED, path, MNTS_REAL)) {
crit(ap->logopt,
"the umount binary reported that %s was "
"unmounted, but there is still something "
"mounted on this path.", path);
rv = -1;
}
}
return rv;
}
int umount_amd_ext_mount(struct autofs_point *ap, struct amd_entry *entry)
{
int rv = 1;
if (entry->umount) {
char *prog, *str;
char **argv;
int argc = -1;
str = strdup(entry->umount);
if (!str)
goto out;
prog = NULL;
argv = NULL;
argc = construct_argv(str, &prog, &argv);
if (argc == -1) {
free(str);
goto out;
}
if (!ext_mount_remove(&entry->ext_mount, entry->fs)) {
rv =0;
goto out_free;
}
rv = spawnv(ap->logopt, prog, (const char * const *) argv);
if (rv == -1 || (WIFEXITED(rv) && WEXITSTATUS(rv)))
error(ap->logopt,
"failed to umount program mount at %s", entry->fs);
else {
rv = 0;
debug(ap->logopt,
"umounted program mount at %s", entry->fs);
rmdir_path(ap, entry->fs, ap->dev);
}
out_free:
free_argv(argc, (const char **) argv);
free(str);
goto out;
}
if (ext_mount_remove(&entry->ext_mount, entry->fs)) {
rv = umount_ent(ap, entry->fs);
if (rv)
error(ap->logopt,
"failed to umount external mount %s", entry->fs);
else
debug(ap->logopt,
"umounted external mount %s", entry->fs);
}
out:
return rv;
}
static int do_mount_autofs_offset(struct autofs_point *ap,
struct mapent *oe, const char *root,
char *offset)
{
int mounted = 0;
int ret;
debug(ap->logopt, "mount offset %s at %s", oe->key, root);
ret = mount_autofs_offset(ap, oe, root, offset);
if (ret >= MOUNT_OFFSET_OK)
mounted++;
else {
if (ret != MOUNT_OFFSET_IGNORE)
warn(ap->logopt, "failed to mount offset");
else {
debug(ap->logopt, "ignoring \"nohide\" trigger %s",
oe->key);
free(oe->mapent);
oe->mapent = NULL;
}
}
return mounted;
}
int mount_multi_triggers(struct autofs_point *ap, struct mapent *me,
const char *root, unsigned int start, const char *base)
{
char path[PATH_MAX + 1];
char *offset = path;
struct mapent *oe;
struct list_head *pos = NULL;
unsigned int fs_path_len;
int mounted;
fs_path_len = start + strlen(base);
if (fs_path_len > PATH_MAX)
return -1;
mounted = 0;
offset = cache_get_offset(base, offset, start, &me->multi_list, &pos);
while (offset) {
int plen = fs_path_len + strlen(offset);
if (plen > PATH_MAX) {
warn(ap->logopt, "path loo long");
goto cont;
}
oe = cache_lookup_offset(base, offset, start, &me->multi_list);
if (!oe || !oe->mapent)
goto cont;
mounted += do_mount_autofs_offset(ap, oe, root, offset);
/*
* If re-constructing a multi-mount it's necessary to walk
* into nested mounts, unlike the usual "mount only what's
* needed as you go" behavior.
*/
if (ap->state == ST_READMAP && ap->flags & MOUNT_FLAG_REMOUNT) {
if (oe->ioctlfd != -1 ||
is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) {
char oe_root[PATH_MAX + 1];
strcpy(oe_root, root);
strcat(oe_root, offset);
mount_multi_triggers(ap, oe, oe_root, strlen(oe_root), base);
}
}
cont:
offset = cache_get_offset(base,
offset, start, &me->multi_list, &pos);
}
return mounted;
}
static int rmdir_path_offset(struct autofs_point *ap, struct mapent *oe)
{
char *dir, *path;
unsigned int split;
int ret;
if (ap->type == LKP_DIRECT)
return rmdir_path(ap, oe->key, oe->multi->dev);
dir = strdup(oe->key);
if (ap->flags & MOUNT_FLAG_GHOST)
split = strlen(ap->path) + strlen(oe->multi->key) + 1;
else
split = strlen(ap->path);
dir[split] = '\0';
path = &dir[split + 1];
if (chdir(dir) == -1) {
error(ap->logopt, "failed to chdir to %s", dir);
free(dir);
return -1;
}
ret = rmdir_path(ap, path, ap->dev);
free(dir);
if (chdir("/") == -1)
error(ap->logopt, "failed to chdir to /");
return ret;
}
int umount_multi_triggers(struct autofs_point *ap, struct mapent *me, char *root, const char *base)
{
char path[PATH_MAX + 1];
char *offset;
struct mapent *oe;
struct list_head *mm_root, *pos;
const char o_root[] = "/";
const char *mm_base;
int left, start;
left = 0;
start = strlen(root);
mm_root = &me->multi->multi_list;
if (!base)
mm_base = o_root;
else
mm_base = base;
pos = NULL;
offset = path;
while ((offset = cache_get_offset(mm_base, offset, start, mm_root, &pos))) {
char *oe_base;
oe = cache_lookup_offset(mm_base, offset, start, &me->multi_list);
/* root offset is a special case */
if (!oe || (strlen(oe->key) - start) == 1)
continue;
/*
* Check for and umount subtree offsets resulting from
* nonstrict mount fail.
*/
oe_base = oe->key + strlen(root);
left += umount_multi_triggers(ap, oe, root, oe_base);
if (oe->ioctlfd != -1 ||
is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) {
left++;
continue;
}
debug(ap->logopt, "umount offset %s", oe->key);
if (umount_autofs_offset(ap, oe)) {
warn(ap->logopt, "failed to umount offset");
left++;
} else {
struct stat st;
int ret;
if (!(oe->flags & MOUNT_FLAG_DIR_CREATED))
continue;
/*
* An error due to partial directory removal is
* ok so only try and remount the offset if the
* actual mount point still exists.
*/
ret = rmdir_path_offset(ap, oe);
if (ret == -1 && !stat(oe->key, &st)) {
ret = do_mount_autofs_offset(ap, oe, root, offset);
if (ret)
left++;
/* But we did origianlly create this */
oe->flags |= MOUNT_FLAG_DIR_CREATED;
}
}
}
if (!left && me->multi == me) {
struct mapent_cache *mc = me->mc;
int status;
/*
* Special case.
* If we can't umount the root container then we can't
* delete the offsets from the cache and we need to put
* the offset triggers back.
*/
if (is_mounted(_PATH_MOUNTED, root, MNTS_REAL)) {
info(ap->logopt, "unmounting dir = %s", root);
if (umount_ent(ap, root) &&
is_mounted(_PATH_MOUNTED, root, MNTS_REAL)) {
if (mount_multi_triggers(ap, me, root, strlen(root), "/") < 0)
warn(ap->logopt,
"failed to remount offset triggers");
return ++left;
}
}
/* We're done - clean out the offsets */
status = cache_delete_offset_list(mc, me->key);
if (status != CHE_OK)
warn(ap->logopt, "couldn't delete offset list");
}
return left;
}
int clean_stale_multi_triggers(struct autofs_point *ap,
struct mapent *me, char *top, const char *base)
{
char *root;
char mm_top[PATH_MAX + 1];
char path[PATH_MAX + 1];
char buf[MAX_ERR_BUF];
char *offset;
struct mapent *oe;
struct list_head *mm_root, *pos;
const char o_root[] = "/";
const char *mm_base;
int left, start;
time_t age;
if (top)
root = top;
else {
if (!strchr(me->multi->key, '/'))
/* Indirect multi-mount root */
/* sprintf okay - if it's mounted, it's
* PATH_MAX or less bytes */
sprintf(mm_top, "%s/%s", ap->path, me->multi->key);
else
strcpy(mm_top, me->multi->key);
root = mm_top;
}
left = 0;
start = strlen(root);
mm_root = &me->multi->multi_list;
if (!base)
mm_base = o_root;
else
mm_base = base;
pos = NULL;
offset = path;
age = me->multi->age;
while ((offset = cache_get_offset(mm_base, offset, start, mm_root, &pos))) {
char *oe_base;
char *key;
int ret;
oe = cache_lookup_offset(mm_base, offset, start, &me->multi_list);
/* root offset is a special case */
if (!oe || (strlen(oe->key) - start) == 1)
continue;
/* Check for and umount stale subtree offsets */
oe_base = oe->key + strlen(root);
ret = clean_stale_multi_triggers(ap, oe, root, oe_base);
left += ret;
if (ret)
continue;
if (oe->age == age)
continue;
/*
* If an offset that has an active mount has been removed
* from the multi-mount we don't want to attempt to trigger
* mounts for it. Obviously this is because it has been
* removed, but less obvious is the potential strange
* behaviour that can result if we do try and mount it
* again after it's been expired. For example, if an NFS
* file system is no longer exported and is later umounted
* it can be mounted again without any error message but
* shows as an empty directory. That's going to confuse
* people for sure.
*
* If the mount cannot be umounted (the process is now
* using a stale mount) the offset needs to be invalidated
* so no further mounts will be attempted but the offset
* cache entry must remain so expires can continue to
* attempt to umount it. If the mount can be umounted and
* the offset is removed, at least for NFS we will get
* ESTALE errors when attempting list the directory.
*/
if (oe->ioctlfd != -1 ||
is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) {
if (umount_ent(ap, oe->key) &&
is_mounted(_PROC_MOUNTS, oe->key, MNTS_REAL)) {
debug(ap->logopt,
"offset %s has active mount, invalidate",
oe->key);
if (oe->mapent) {
free(oe->mapent);
oe->mapent = NULL;
}
left++;
continue;
}
}
key = strdup(oe->key);
if (!key) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
error(ap->logopt, "malloc: %s", estr);
left++;
continue;
}
debug(ap->logopt, "umount offset %s", oe->key);
if (umount_autofs_offset(ap, oe)) {
warn(ap->logopt, "failed to umount offset %s", key);
left++;
} else {
struct stat st;
/* Mount point not ours to delete ? */
if (!(oe->flags & MOUNT_FLAG_DIR_CREATED)) {
debug(ap->logopt, "delete offset key %s", key);
if (cache_delete_offset(oe->mc, key) == CHE_FAIL)
error(ap->logopt,
"failed to delete offset key %s", key);
free(key);
continue;
}
/*
* An error due to partial directory removal is
* ok so only try and remount the offset if the
* actual mount point still exists.
*/
ret = rmdir_path_offset(ap, oe);
if (ret == -1 && !stat(oe->key, &st)) {
ret = do_mount_autofs_offset(ap, oe, root, offset);
if (ret) {
left++;
/* But we did origianlly create this */
oe->flags |= MOUNT_FLAG_DIR_CREATED;
free(key);
continue;
}
/*
* Fall through if the trigger can't be mounted
* again, since there is no offset there can't
* be any mount requests so remove the map
* entry from the cache. There's now a dead
* offset mount, but what else can we do ....
*/
}
debug(ap->logopt, "delete offset key %s", key);
if (cache_delete_offset(oe->mc, key) == CHE_FAIL)
error(ap->logopt,
"failed to delete offset key %s", key);
}
free(key);
}
return left;
}