mount.c revision b30e2f4c18ad81b04e4314fd191a5d458553773c
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <stdio.h>
#include <mntent.h>
#include <signal.h>
#include "unit.h"
#include "mount.h"
#include "load-fragment.h"
#include "load-dropin.h"
#include "log.h"
#include "strv.h"
#include "mkdir.h"
#include "mount-setup.h"
#include "unit-name.h"
#include "dbus-mount.h"
#include "special.h"
#include "bus-errors.h"
#include "exit-status.h"
#include "def.h"
[MOUNT_DEAD] = UNIT_INACTIVE,
[MOUNT_MOUNTED] = UNIT_ACTIVE,
};
static void mount_init(Unit *u) {
assert(u);
m->directory_mode = 0755;
* dep loop, don't use kmsg logging for -.mount */
if (!unit_has_name(u, "-.mount")) {
}
* the same process group as us, so that the autofs kernel
* side doesn't send us another mount request while we are
* already trying to comply its last one. */
m->exec_context.same_pgrp = true;
UNIT(m)->ignore_on_isolate = true;
}
static void mount_unwatch_control_pid(Mount *m) {
assert(m);
if (m->control_pid <= 0)
return;
m->control_pid = 0;
}
static void mount_parameters_done(MountParameters *p) {
assert(p);
}
static void mount_done(Unit *u) {
assert(m);
m->control_command = NULL;
unit_unwatch_timer(u, &m->timer_watch);
}
assert(m);
if (m->from_fragment)
return &m->parameters_fragment;
else if (m->from_etc_fstab)
return &m->parameters_etc_fstab;
return NULL;
}
assert(m);
if (m->from_proc_self_mountinfo)
return &m->parameters_proc_self_mountinfo;
return get_mount_parameters_configured(m);
}
static int mount_add_mount_links(Mount *m) {
int r;
assert(m);
/* Adds in links to other mount points that might lie below or
* above us in the hierarchy */
if (n == m)
continue;
continue;
return r;
if (pn)
return r;
return r;
if (pm)
return r;
return r;
return r;
return r;
return r;
}
}
return 0;
}
static int mount_add_swap_links(Mount *m) {
int r;
assert(m);
return r;
return 0;
}
static int mount_add_path_links(Mount *m) {
int r;
assert(m);
return r;
return 0;
}
static int mount_add_automount_links(Mount *m) {
int r;
assert(m);
return r;
return 0;
}
static int mount_add_socket_links(Mount *m) {
int r;
assert(m);
return r;
return 0;
}
/* Like glibc's hasmntopt(), but works on a string, not a
* struct mntent */
if (!haystack)
return NULL;
}
static bool mount_is_network(MountParameters *p) {
assert(p);
return true;
return true;
return false;
}
static bool mount_is_bind(MountParameters *p) {
assert(p);
return true;
return true;
return false;
}
static bool needs_quota(MountParameters *p) {
assert(p);
if (mount_is_network(p))
return false;
if (mount_is_bind(p))
return false;
}
static int mount_add_fstab_links(Mount *m) {
MountParameters *p;
int r;
assert(m);
return 0;
if (!(p = get_mount_parameters_configured(m)))
return 0;
if (p != &m->parameters_etc_fstab)
return 0;
handle =
automount ||
if (mount_is_network(p)) {
} else {
}
if (r < 0)
return r;
if (tu_wants) {
if (r < 0)
return r;
}
if (after) {
if (r < 0)
return r;
}
if (automount) {
return r;
/* If auto is configured as well also pull in the
* mount right-away, but don't rely on it. */
if (!noauto) /* automount + auto */
return r;
/* Install automount unit */
if (!nofail) /* automount + fail */
else /* automount + nofail */
/* Automatically add mount points that aren't natively
* configured to local-fs.target */
if (!nofail) /* auto + fail */
else /* auto + nofail */
}
return 0;
}
static int mount_add_device_links(Mount *m) {
MountParameters *p;
int r;
assert(m);
if (!(p = get_mount_parameters_configured(m)))
return 0;
if (!p->what)
return 0;
if (!mount_is_bind(p) &&
p == &m->parameters_etc_fstab) {
return r;
}
if (p->passno > 0 &&
!mount_is_bind(p) &&
char *name;
/* Let's add in the fsck service */
/* aka SPECIAL_FSCK_SERVICE */
return -ENOMEM;
return r;
}
return r;
}
return 0;
}
static int mount_add_default_dependencies(Mount *m) {
int r;
MountParameters *p;
assert(m);
return 0;
p = get_mount_parameters_configured(m);
if (p && needs_quota(p)) {
if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true)) < 0 ||
(r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true)) < 0)
return r;
}
if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0)
return r;
return 0;
}
static int mount_fix_timeouts(Mount *m) {
MountParameters *p;
Iterator i;
usec_t u;
char *t;
int r;
assert(m);
if (!(p = get_mount_parameters_configured(m)))
return 0;
/* Allow configuration how long we wait for a device that
* backs a mount point to show up. This is useful to support
* endless device timeouts for devices that show up only after
* user input, like crypto devices. */
timeout += 31;
timeout += 25;
else
return 0;
if (!t)
return -ENOMEM;
r = parse_usec(t, &u);
free(t);
if (r < 0) {
return r;
}
continue;
other->job_timeout = u;
}
return 0;
}
static int mount_verify(Mount *m) {
bool b;
char *e;
assert(m);
return 0;
return -ENOENT;
return -ENOMEM;
b = unit_has_name(UNIT(m), e);
free(e);
if (!b) {
return -EINVAL;
}
return -EINVAL;
}
return -EBADMSG;
}
return -EINVAL;
}
return 0;
}
static int mount_load(Unit *u) {
int r;
assert(u);
if ((r = unit_load_fragment_and_dropin_optional(u)) < 0)
return r;
/* This is a new unit? Then let's add in some extras */
if (u->load_state == UNIT_LOADED) {
if ((r = unit_add_exec_dependencies(u, &m->exec_context)) < 0)
return r;
if (UNIT(m)->fragment_path)
m->from_fragment = true;
else if (m->from_etc_fstab)
/* We always add several default dependencies to fstab mounts,
* but we do not want the implicit complementing of Wants= with After=
* in the target unit that this mount unit will be hooked into. */
UNIT(m)->default_dependencies = false;
if (!m->where)
return -ENOMEM;
path_kill_slashes(m->where);
if (!UNIT(m)->description)
if ((r = unit_set_description(u, m->where)) < 0)
return r;
if ((r = mount_add_device_links(m)) < 0)
return r;
if ((r = mount_add_mount_links(m)) < 0)
return r;
if ((r = mount_add_socket_links(m)) < 0)
return r;
if ((r = mount_add_swap_links(m)) < 0)
return r;
if ((r = mount_add_path_links(m)) < 0)
return r;
if ((r = mount_add_automount_links(m)) < 0)
return r;
if ((r = mount_add_fstab_links(m)) < 0)
return r;
if ((r = mount_add_default_dependencies(m)) < 0)
return r;
if ((r = unit_add_default_cgroups(u)) < 0)
return r;
}
return mount_verify(m);
}
Unit *p;
int r;
Iterator i;
assert(m);
if (p->type == UNIT_AUTOMOUNT) {
if (r < 0)
return r;
}
return 0;
}
assert(m);
if (state != MOUNT_MOUNTING &&
state != MOUNT_MOUNTING_DONE &&
state != MOUNT_REMOUNTING &&
state != MOUNT_UNMOUNTING &&
state != MOUNT_MOUNTING_SIGTERM &&
state != MOUNT_MOUNTING_SIGKILL &&
m->control_command = NULL;
}
if (state == MOUNT_MOUNTED ||
mount_notify_automount(m, 0);
else if (state == MOUNT_DEAD ||
state == MOUNT_UNMOUNTING ||
state == MOUNT_MOUNTING_SIGTERM ||
state == MOUNT_MOUNTING_SIGKILL ||
state == MOUNT_FAILED)
mount_notify_automount(m, -ENODEV);
log_debug("%s changed %s -> %s",
unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS);
m->reload_result = MOUNT_SUCCESS;
}
static int mount_coldplug(Unit *u) {
int r;
assert(m);
if (m->deserialized_state != m->state)
new_state = m->deserialized_state;
else if (m->from_proc_self_mountinfo)
if (new_state == MOUNT_MOUNTING ||
new_state == MOUNT_MOUNTING_DONE ||
new_state == MOUNT_REMOUNTING ||
new_state == MOUNT_UNMOUNTING ||
if (m->control_pid <= 0)
return -EBADMSG;
return r;
return r;
}
mount_set_state(m, new_state);
}
return 0;
}
MountParameters *p;
assert(m);
assert(f);
p = get_mount_parameters(m);
fprintf(f,
"%sMount State: %s\n"
"%sResult: %s\n"
"%sWhere: %s\n"
"%sWhat: %s\n"
"%sFile System Type: %s\n"
"%sOptions: %s\n"
"%sFrom fragment: %s\n"
"%sDirectoryMode: %04o\n",
prefix, m->directory_mode);
if (m->control_pid > 0)
fprintf(f,
"%sControl PID: %lu\n",
prefix, (unsigned long) m->control_pid);
}
int r;
assert(m);
assert(c);
goto fail;
if ((r = exec_spawn(c,
NULL,
&m->exec_context,
NULL, 0,
true,
true,
true,
UNIT(m)->cgroup_bondings,
UNIT(m)->cgroup_attributes,
&pid)) < 0)
goto fail;
/* FIXME: we need to do something here */
goto fail;
return 0;
fail:
return r;
}
assert(m);
if (f != MOUNT_SUCCESS)
m->result = f;
}
assert(m);
if (f != MOUNT_SUCCESS)
m->result = f;
}
int r;
bool wait_for_exit = false;
assert(m);
if (f != MOUNT_SUCCESS)
m->result = f;
if (m->control_pid > 0) {
else
wait_for_exit = true;
}
r = -ENOMEM;
goto fail;
}
/* Exclude the control pid from being killed via the cgroup */
if (m->control_pid > 0)
goto fail;
} else if (r > 0)
wait_for_exit = true;
}
}
if (wait_for_exit) {
goto fail;
mount_set_state(m, state);
else
return;
fail:
else
if (pid_set)
}
static void mount_enter_unmounting(Mount *m) {
int r;
assert(m);
if ((r = exec_command_set(
m->control_command,
m->where,
NULL)) < 0)
goto fail;
goto fail;
return;
fail:
}
static void mount_enter_mounting(Mount *m) {
int r;
MountParameters *p;
assert(m);
/* Create the source directory for bind-mounts if needed */
p = get_mount_parameters_configured(m);
if (p && mount_is_bind(p))
if (m->from_fragment)
r = exec_command_set(
m->control_command,
m->where,
NULL);
else if (m->from_etc_fstab)
r = exec_command_set(
m->control_command,
m->where,
NULL);
else
r = -ENOENT;
if (r < 0)
goto fail;
goto fail;
return;
fail:
}
static void mount_enter_mounting_done(Mount *m) {
assert(m);
}
static void mount_enter_remounting(Mount *m) {
int r;
assert(m);
if (m->from_fragment) {
const char *o;
if (m->parameters_fragment.options) {
r = -ENOMEM;
goto fail;
}
o = buf;
} else
o = "remount";
r = exec_command_set(
m->control_command,
m->where,
"-o", o,
NULL);
} else if (m->from_etc_fstab)
r = exec_command_set(
m->control_command,
m->where,
"-o", "remount",
NULL);
else
r = -ENOENT;
if (r < 0)
goto fail;
goto fail;
return;
fail:
}
static int mount_start(Unit *u) {
assert(m);
/* We cannot fulfill this request right now, try again later
* please! */
if (m->state == MOUNT_UNMOUNTING ||
m->state == MOUNT_UNMOUNTING_SIGTERM ||
m->state == MOUNT_UNMOUNTING_SIGKILL ||
m->state == MOUNT_MOUNTING_SIGTERM ||
m->state == MOUNT_MOUNTING_SIGKILL)
return -EAGAIN;
/* Already on it! */
if (m->state == MOUNT_MOUNTING)
return 0;
m->result = MOUNT_SUCCESS;
m->reload_result = MOUNT_SUCCESS;
return 0;
}
static int mount_stop(Unit *u) {
assert(m);
/* Already on it */
if (m->state == MOUNT_UNMOUNTING ||
m->state == MOUNT_UNMOUNTING_SIGKILL ||
m->state == MOUNT_UNMOUNTING_SIGTERM ||
m->state == MOUNT_MOUNTING_SIGTERM ||
m->state == MOUNT_MOUNTING_SIGKILL)
return 0;
m->state == MOUNT_MOUNTING_DONE ||
m->state == MOUNT_MOUNTED ||
m->state == MOUNT_REMOUNTING ||
m->state == MOUNT_REMOUNTING_SIGTERM ||
m->state == MOUNT_REMOUNTING_SIGKILL);
return 0;
}
static int mount_reload(Unit *u) {
assert(m);
if (m->state == MOUNT_MOUNTING_DONE)
return -EAGAIN;
return 0;
}
assert(m);
assert(f);
if (m->control_pid > 0)
if (m->control_command_id >= 0)
return 0;
}
assert(u);
else
m->deserialized_state = state;
MountResult f;
if (f < 0)
else if (f != MOUNT_SUCCESS)
m->result = f;
MountResult f;
if (f < 0)
else if (f != MOUNT_SUCCESS)
m->reload_result = f;
else
m->control_pid = pid;
else {
m->control_command_id = id;
}
} else
return 0;
}
assert(u);
}
static const char *mount_sub_state_to_string(Unit *u) {
assert(u);
}
static bool mount_check_gc(Unit *u) {
assert(m);
return m->from_etc_fstab || m->from_proc_self_mountinfo;
}
MountResult f;
assert(m);
if (pid != m->control_pid)
return;
m->control_pid = 0;
f = MOUNT_SUCCESS;
else if (code == CLD_EXITED)
else if (code == CLD_KILLED)
f = MOUNT_FAILURE_SIGNAL;
else if (code == CLD_DUMPED)
else
assert_not_reached("Unknown code");
if (f != MOUNT_SUCCESS)
m->result = f;
if (m->control_command) {
m->control_command = NULL;
}
/* Note that mount(8) returning and the kernel sending us a
* mount table change event might happen out-of-order. If an
* operation succeed we assume the kernel will follow soon too
* and already change into the resulting state. If it fails
* we check if the kernel still knows about the mount. and
* change state accordingly. */
switch (m->state) {
case MOUNT_MOUNTING:
case MOUNT_MOUNTING_DONE:
case MOUNT_MOUNTING_SIGKILL:
case MOUNT_MOUNTING_SIGTERM:
if (f == MOUNT_SUCCESS)
mount_enter_mounted(m, f);
else if (m->from_proc_self_mountinfo)
mount_enter_mounted(m, f);
else
mount_enter_dead(m, f);
break;
case MOUNT_REMOUNTING:
case MOUNT_REMOUNTING_SIGKILL:
case MOUNT_REMOUNTING_SIGTERM:
m->reload_result = f;
if (m->from_proc_self_mountinfo)
else
break;
case MOUNT_UNMOUNTING:
case MOUNT_UNMOUNTING_SIGKILL:
case MOUNT_UNMOUNTING_SIGTERM:
if (f == MOUNT_SUCCESS)
mount_enter_dead(m, f);
else if (m->from_proc_self_mountinfo)
mount_enter_mounted(m, f);
else
mount_enter_dead(m, f);
break;
default:
assert_not_reached("Uh, control process died at wrong time.");
}
/* Notify clients about changed exit status */
}
assert(m);
assert(w == &m->timer_watch);
switch (m->state) {
case MOUNT_MOUNTING:
case MOUNT_MOUNTING_DONE:
break;
case MOUNT_REMOUNTING:
break;
case MOUNT_UNMOUNTING:
break;
case MOUNT_MOUNTING_SIGTERM:
if (m->exec_context.send_sigkill) {
} else {
if (m->from_proc_self_mountinfo)
else
}
break;
case MOUNT_REMOUNTING_SIGTERM:
if (m->exec_context.send_sigkill) {
} else {
if (m->from_proc_self_mountinfo)
else
}
break;
case MOUNT_UNMOUNTING_SIGTERM:
if (m->exec_context.send_sigkill) {
} else {
if (m->from_proc_self_mountinfo)
else
}
break;
case MOUNT_MOUNTING_SIGKILL:
case MOUNT_REMOUNTING_SIGKILL:
case MOUNT_UNMOUNTING_SIGKILL:
if (m->from_proc_self_mountinfo)
else
break;
default:
assert_not_reached("Timeout at wrong time.");
}
}
static int mount_add_one(
Manager *m,
const char *what,
const char *where,
const char *options,
const char *fstype,
int passno,
bool from_proc_self_mountinfo,
bool set_flags) {
int r;
Unit *u;
bool delete;
MountParameters *p;
assert(m);
/* Ignore API mount points. They should never be referenced in
* dependencies ever. */
return 0;
return 0;
/* probably some kind of swap, ignore */
return 0;
if (!e)
return -ENOMEM;
u = manager_get_unit(m, e);
if (!u) {
delete = true;
if (!u) {
free(e);
return -ENOMEM;
}
r = unit_add_name(u, e);
free(e);
if (r < 0)
goto fail;
r = -ENOMEM;
goto fail;
}
} else {
delete = false;
free(e);
}
r = -ENOMEM;
goto fail;
}
if (from_proc_self_mountinfo) {
p = &MOUNT(u)->parameters_proc_self_mountinfo;
if (set_flags) {
MOUNT(u)->is_mounted = true;
}
MOUNT(u)->from_proc_self_mountinfo = true;
} else {
p = &MOUNT(u)->parameters_etc_fstab;
MOUNT(u)->from_etc_fstab = true;
}
p->what = w;
p->options = o;
p->fstype = f;
return 0;
fail:
free(w);
free(o);
free(f);
if (delete && u)
unit_free(u);
return r;
}
static int mount_find_pri(char *options) {
unsigned long r;
return 0;
pri += 4;
errno = 0;
if (errno != 0)
return -errno;
return -EINVAL;
return (int) r;
}
static int mount_load_etc_fstab(Manager *m) {
FILE *f;
int r = 0;
assert(m);
errno = 0;
return -errno;
int k;
r = -ENOMEM;
goto finish;
}
r = -ENOMEM;
goto finish;
}
if (what[0] == '/')
if (where[0] == '/')
int pri;
k = pri;
else
k = swap_add_one(m,
what,
NULL,
pri,
false);
} else
if (k < 0)
r = k;
}
endmntent(f);
return r;
}
int r = 0;
unsigned i;
assert(m);
for (i = 1;; i++) {
int k;
if ((k = fscanf(m->proc_self_mountinfo,
"%*s " /* (1) mount id */
"%*s " /* (2) parent id */
"%*s " /* (3) major:minor */
"%*s " /* (4) root */
"%ms " /* (5) mount point */
"%ms" /* (6) mount options */
"%*[^-]" /* (7) optional fields */
"- " /* (8) separator */
"%ms " /* (9) file system type */
"%ms" /* (10) mount source */
"%ms" /* (11) mount options 2 */
"%*[^\n]", /* some rubbish at the end */
&path,
&options,
&fstype,
&device,
&options2)) != 5) {
if (k == EOF)
break;
log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
goto clean_up;
}
r = -ENOMEM;
goto finish;
}
r = -ENOMEM;
goto finish;
}
r = k;
free(d);
free(p);
free(o);
}
free(d);
free(p);
free(o);
return r;
}
static void mount_shutdown(Manager *m) {
assert(m);
if (m->proc_self_mountinfo) {
m->proc_self_mountinfo = NULL;
}
}
static int mount_enumerate(Manager *m) {
int r;
struct epoll_event ev;
assert(m);
if (!m->proc_self_mountinfo) {
return -errno;
return -errno;
}
if ((r = mount_load_etc_fstab(m)) < 0)
goto fail;
if ((r = mount_load_proc_self_mountinfo(m, false)) < 0)
goto fail;
return 0;
fail:
mount_shutdown(m);
return r;
}
Unit *u;
int r;
assert(m);
/* The manager calls this for every fd event happening on the
* table changes */
if ((r = mount_load_proc_self_mountinfo(m, true)) < 0) {
/* Reset flags, just in case, for later calls */
}
return;
}
if (!mount->is_mounted) {
/* This has just been unmounted. */
mount->from_proc_self_mountinfo = false;
case MOUNT_MOUNTED:
break;
default:
break;
}
/* New or changed mount entry */
case MOUNT_DEAD:
case MOUNT_FAILED:
break;
case MOUNT_MOUNTING:
break;
default:
/* Nothing really changed, but let's
* issue an notification call
* nonetheless, in case somebody is
* waiting for this. (e.g. file system
break;
}
}
/* Reset the flags for later calls */
}
}
static void mount_reset_failed(Unit *u) {
assert(m);
if (m->state == MOUNT_FAILED)
m->result = MOUNT_SUCCESS;
m->reload_result = MOUNT_SUCCESS;
}
int r = 0;
assert(m);
return -ESRCH;
}
return -ESRCH;
}
if (m->control_pid > 0)
r = -errno;
int q;
return -ENOMEM;
/* Exclude the control pid from being killed via the cgroup */
if (m->control_pid > 0)
r = q;
goto finish;
}
r = q;
}
if (pid_set)
return r;
}
static const char* const mount_state_table[_MOUNT_STATE_MAX] = {
[MOUNT_DEAD] = "dead",
[MOUNT_MOUNTING] = "mounting",
[MOUNT_MOUNTING_DONE] = "mounting-done",
[MOUNT_MOUNTED] = "mounted",
[MOUNT_REMOUNTING] = "remounting",
[MOUNT_UNMOUNTING] = "unmounting",
[MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm",
[MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill",
[MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm",
[MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill",
[MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm",
[MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill",
[MOUNT_FAILED] = "failed"
};
static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = {
[MOUNT_EXEC_MOUNT] = "ExecMount",
[MOUNT_EXEC_UNMOUNT] = "ExecUnmount",
[MOUNT_EXEC_REMOUNT] = "ExecRemount",
};
static const char* const mount_result_table[_MOUNT_RESULT_MAX] = {
[MOUNT_SUCCESS] = "success",
[MOUNT_FAILURE_RESOURCES] = "resources",
[MOUNT_FAILURE_TIMEOUT] = "timeout",
[MOUNT_FAILURE_EXIT_CODE] = "exit-code",
[MOUNT_FAILURE_SIGNAL] = "signal",
[MOUNT_FAILURE_CORE_DUMP] = "core-dump"
};
const UnitVTable mount_vtable = {
.suffix = ".mount",
.object_size = sizeof(Mount),
.sections =
"Unit\0"
"Mount\0"
"Install\0",
.no_alias = true,
.no_instances = true,
.show_status = true,
.init = mount_init,
.load = mount_load,
.done = mount_done,
.dump = mount_dump,
.start = mount_start,
.stop = mount_stop,
.reload = mount_reload,
.kill = mount_kill,
.bus_interface = "org.freedesktop.systemd1.Mount",
};