mount.c revision 257f1d8ec49046c7b481801acfdd5bf57c9efa5c
/*-*- 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 Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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 "path-util.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;
if (unit_has_name(u, "-.mount")) {
UNIT(m)->refuse_manual_start = true;
UNIT(m)->refuse_manual_stop = true;
} else {
* dep loop, don't use kmsg logging for -.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;
return NULL;
}
assert(m);
if (m->from_proc_self_mountinfo)
return &m->parameters_proc_self_mountinfo;
return get_mount_parameters_fragment(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;
}
static int mount_add_requires_mounts_links(Mount *m) {
int r;
assert(m);
r = unit_add_one_mount_link(other, m);
if (r < 0)
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_device_links(Mount *m) {
MountParameters *p;
int r;
assert(m);
p = get_mount_parameters_fragment(m);
if (!p)
return 0;
if (!p->what)
return 0;
if (!mount_is_bind(p) &&
if (r < 0)
return r;
}
if (p->passno > 0 &&
!mount_is_bind(p) &&
char *name;
/* Let's add in the fsck service */
/* aka SPECIAL_FSCK_SERVICE */
if (!name)
return -ENOMEM;
if (r < 0) {
return r;
}
if (r < 0)
return r;
}
return 0;
}
static int mount_add_quota_links(Mount *m) {
int r;
MountParameters *p;
assert(m);
return 0;
p = get_mount_parameters_fragment(m);
if (!p)
return 0;
if (!needs_quota(p))
return 0;
r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true);
if (r < 0)
return r;
r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true);
if (r < 0)
return r;
return 0;
}
static int mount_add_default_dependencies(Mount *m) {
int r;
MountParameters *p;
const char *after;
assert(m);
return 0;
p = get_mount_parameters_fragment(m);
if (!p)
return 0;
return 0;
if (mount_is_network(p))
else
if (r < 0)
return r;
r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true);
if (r < 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);
p = get_mount_parameters_fragment(m);
if (!p)
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;
if (!m->from_fragment && !m->from_proc_self_mountinfo)
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_add_extras(Mount *m) {
int r;
r = unit_add_exec_dependencies(u, &m->exec_context);
if (r < 0)
return r;
if (UNIT(m)->fragment_path)
m->from_fragment = true;
if (!m->where) {
if (!m->where)
return -ENOMEM;
}
path_kill_slashes(m->where);
if (!UNIT(m)->description) {
r = unit_set_description(u, m->where);
if (r < 0)
return r;
}
r = mount_add_device_links(m);
if (r < 0)
return r;
r = mount_add_mount_links(m);
if (r < 0)
return r;
r = mount_add_socket_links(m);
if (r < 0)
return r;
r = mount_add_swap_links(m);
if (r < 0)
return r;
r = mount_add_path_links(m);
if (r < 0)
return r;
r = mount_add_requires_mounts_links(m);
if (r < 0)
return r;
r = mount_add_automount_links(m);
if (r < 0)
return r;
r = mount_add_quota_links(m);
if (r < 0)
return r;
if (UNIT(m)->default_dependencies) {
r = mount_add_default_dependencies(m);
if (r < 0)
return r;
}
r = unit_add_default_cgroups(u);
if (r < 0)
return r;
r = mount_fix_timeouts(m);
if (r < 0)
return r;
return 0;
}
static int mount_load(Unit *u) {
int r;
assert(u);
if (m->from_proc_self_mountinfo)
else
r = unit_load_fragment_and_dropin(u);
if (r < 0)
return r;
/* This is a new unit? Then let's add in some extras */
if (u->load_state == UNIT_LOADED) {
r = mount_add_extras(m);
if (r < 0)
return r;
r = unit_exec_context_defaults(u, &m->exec_context);
if (r < 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,
NULL,
NULL,
&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;
if (r < 0) {
} 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);
if (dir_is_empty(m->where) <= 0)
log_notice("%s: Directory %s to mount over is not empty, ignoring. (To see the over-mounted files, please manually mount the underlying file system to a secondary location.)", m->meta.id, m->where);
/* Create the source directory for bind-mounts if needed */
p = get_mount_parameters_fragment(m);
if (p && mount_is_bind(p))
if (m->from_fragment)
r = exec_command_set(
m->control_command,
m->where,
NULL);
else
r = -ENOENT;
if (r < 0)
goto fail;
if (r < 0)
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
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_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->kill_context.send_sigkill) {
} else {
if (m->from_proc_self_mountinfo)
else
}
break;
case MOUNT_REMOUNTING_SIGTERM:
if (m->kill_context.send_sigkill) {
} else {
if (m->from_proc_self_mountinfo)
else
}
break;
case MOUNT_UNMOUNTING_SIGTERM:
if (m->kill_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 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);
if (u->load_state == UNIT_ERROR) {
u->load_state = UNIT_LOADED;
u->load_error = 0;
r = mount_add_extras(MOUNT(u));
if (r < 0)
goto fail;
}
}
r = -ENOMEM;
goto fail;
}
p = &MOUNT(u)->parameters_proc_self_mountinfo;
if (set_flags) {
MOUNT(u)->is_mounted = true;
}
MOUNT(u)->from_proc_self_mountinfo = 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;
}
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;
}
if (!o) {
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_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;
if (!pid_set)
return -ENOMEM;
/* Exclude the control pid from being killed via the cgroup */
if (m->control_pid > 0) {
if (q < 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 = {
.object_size = sizeof(Mount),
.sections =
"Unit\0"
"Mount\0"
"Install\0",
.no_alias = true,
.no_instances = 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",
.starting_stopping = {
[0] = "Mounting %s...",
[1] = "Unmounting %s...",
},
.finished_start_job = {
[JOB_DONE] = "Mounted %s.",
[JOB_FAILED] = "Failed to mount %s.",
[JOB_DEPENDENCY] = "Dependency failed for %s.",
[JOB_TIMEOUT] = "Timed out mounting %s.",
},
.finished_stop_job = {
[JOB_DONE] = "Unmounted %s.",
[JOB_FAILED] = "Failed unmounting %s.",
[JOB_TIMEOUT] = "Timed out unmounting %s.",
},
},
};