swap.c revision ba64af90ecf48f7653a04bf3af1291385c9a69b8
/*-*- 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 <unistd.h>
#include "libudev.h"
#include "alloc-util.h"
#include "dbus-swap.h"
#include "escape.h"
#include "exit-status.h"
#include "fd-util.h"
#include "formats-util.h"
#include "fstab-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "special.h"
#include "string-table.h"
#include "string-util.h"
#include "swap.h"
#include "udev-util.h"
#include "unit-name.h"
#include "unit.h"
#include "virt.h"
[SWAP_DEAD] = UNIT_INACTIVE,
[SWAP_ACTIVE] = UNIT_ACTIVE,
};
static void swap_unset_proc_swaps(Swap *s) {
assert(s);
if (!s->from_proc_swaps)
return;
s->from_proc_swaps = false;
}
int r;
assert(s);
if (r < 0)
return r;
if (s->devnode) {
if (first)
else
}
if (devnode) {
if (!s->devnode)
return -ENOMEM;
}
return 0;
}
assert(s);
u->ignore_on_isolate = true;
}
static void swap_unwatch_control_pid(Swap *s) {
assert(s);
if (s->control_pid <= 0)
return;
s->control_pid = 0;
}
assert(s);
swap_set_devnode(s, NULL);
s->control_command = NULL;
}
static int swap_arm_timer(Swap *s) {
int r;
assert(s);
if (s->timeout_usec <= 0) {
return 0;
}
if (s->timer_event_source) {
if (r < 0)
return r;
}
r = sd_event_add_time(
&s->timer_event_source,
swap_dispatch_timer, s);
if (r < 0)
return r;
return 0;
}
static int swap_add_device_links(Swap *s) {
assert(s);
if (!s->what)
return 0;
if (!s->from_fragment)
return 0;
if (is_device_path(s->what))
else
/* File based swap devices need to be ordered after
* systemd-remount-fs.service, since they might need a
* writable file system. */
}
static int swap_add_default_dependencies(Swap *s) {
assert(s);
return 0;
if (detect_container() > 0)
return 0;
return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true);
}
static int swap_verify(Swap *s) {
_cleanup_free_ char *e = NULL;
int r;
return 0;
if (r < 0)
if (!unit_has_name(UNIT(s), e)) {
return -EINVAL;
}
log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load.");
return -EINVAL;
}
return 0;
}
static int swap_load_devnode(Swap *s) {
const char *p;
assert(s);
return 0;
if (!d)
return 0;
p = udev_device_get_devnode(d);
if (!p)
return 0;
return swap_set_devnode(s, p);
}
int r;
assert(s);
/* Load a .swap file */
if (r < 0)
return r;
if (u->load_state == UNIT_LOADED) {
if (UNIT(s)->fragment_path)
s->from_fragment = true;
if (!s->what) {
if (s->parameters_fragment.what)
else if (s->parameters_proc_swaps.what)
else {
if (r < 0)
return r;
}
if (!s->what)
return -ENOMEM;
}
path_kill_slashes(s->what);
if (!UNIT(s)->description) {
r = unit_set_description(u, s->what);
if (r < 0)
return r;
}
if (r < 0)
return r;
r = swap_add_device_links(s);
if (r < 0)
return r;
r = swap_load_devnode(s);
if (r < 0)
return r;
r = unit_patch_contexts(u);
if (r < 0)
return r;
r = unit_add_exec_dependencies(u, &s->exec_context);
if (r < 0)
return r;
r = unit_set_default_slice(u);
if (r < 0)
return r;
if (UNIT(s)->default_dependencies) {
r = swap_add_default_dependencies(s);
if (r < 0)
return r;
}
}
return swap_verify(s);
}
static int swap_setup_unit(
Manager *m,
const char *what,
const char *what_proc_swaps,
int priority,
bool set_flags) {
_cleanup_free_ char *e = NULL;
bool delete = false;
int r;
SwapParameters *p;
assert(m);
if (r < 0)
return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m");
u = manager_get_unit(m, e);
if (u &&
SWAP(u)->from_proc_swaps &&
log_error("Swap %s appeared twice with different device paths %s and %s", e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps);
return -EEXIST;
}
if (!u) {
delete = true;
if (!u)
return log_oom();
r = unit_add_name(u, e);
if (r < 0)
goto fail;
r = -ENOMEM;
goto fail;
}
} else
delete = false;
p = &SWAP(u)->parameters_proc_swaps;
if (!p->what) {
if (!p->what) {
r = -ENOMEM;
goto fail;
}
}
if (set_flags) {
}
SWAP(u)->from_proc_swaps = true;
return 0;
fail:
log_unit_warning_errno(u, r, "Failed to load swap unit: %m");
if (delete && u)
unit_free(u);
return r;
}
const char *dn;
int r;
assert(m);
if (r < 0)
return r;
/* If this is a block device, then let's add duplicates for
* all other names of this block device */
return 0;
if (!d)
return 0;
/* Add the main device node */
dn = udev_device_get_devnode(d);
/* Add additional units for all symlinks */
const char *p;
p = udev_list_entry_get_name(item);
continue;
if (path_startswith(p, "/dev/block/"))
continue;
continue;
}
return r;
}
assert(s);
if (state != SWAP_ACTIVATING &&
state != SWAP_ACTIVATING_SIGTERM &&
state != SWAP_ACTIVATING_SIGKILL &&
state != SWAP_ACTIVATING_DONE &&
state != SWAP_DEACTIVATING &&
s->control_command = NULL;
}
log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state));
/* If there other units for the same device node have a job
queued it might be worth checking again if it is runnable
now. This is necessary, since swap_start() refuses
operation with EAGAIN if there's already another job for
the same device node queued. */
}
static int swap_coldplug(Unit *u) {
int r;
assert(s);
if (s->deserialized_state != s->state)
new_state = s->deserialized_state;
else if (s->from_proc_swaps)
return 0;
if (s->control_pid > 0 &&
pid_is_unwaited(s->control_pid) &&
if (r < 0)
return r;
r = swap_arm_timer(s);
if (r < 0)
return r;
}
swap_set_state(s, new_state);
return 0;
}
SwapParameters *p;
assert(s);
assert(f);
if (s->from_proc_swaps)
p = &s->parameters_proc_swaps;
else if (s->from_fragment)
p = &s->parameters_fragment;
else
p = NULL;
fprintf(f,
"%sSwap State: %s\n"
"%sResult: %s\n"
"%sWhat: %s\n"
"%sFrom fragment: %s\n",
if (s->devnode)
if (p)
fprintf(f,
"%sPriority: %i\n"
"%sOptions: %s\n",
if (s->control_pid > 0)
fprintf(f,
prefix, s->control_pid);
}
int r;
.apply_permissions = true,
.apply_chroot = true,
.apply_tty_stdin = true,
.bus_endpoint_fd = -1,
.stdin_fd = -1,
.stdout_fd = -1,
.stderr_fd = -1,
};
assert(s);
assert(c);
(void) unit_realize_cgroup(UNIT(s));
if (s->reset_cpu_usage) {
(void) unit_reset_cpu_usage(UNIT(s));
s->reset_cpu_usage = false;
}
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
goto fail;
r = swap_arm_timer(s);
if (r < 0)
goto fail;
r = exec_spawn(UNIT(s),
c,
&s->exec_context,
s->exec_runtime,
&pid);
if (r < 0)
goto fail;
if (r < 0)
/* FIXME: we need to do something here */
goto fail;
return 0;
fail:
return r;
}
assert(s);
if (f != SWAP_SUCCESS)
s->result = f;
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
}
assert(s);
if (f != SWAP_SUCCESS)
s->result = f;
}
int r;
assert(s);
if (f != SWAP_SUCCESS)
s->result = f;
r = unit_kill_context(
UNIT(s),
&s->kill_context,
-1,
s->control_pid,
false);
if (r < 0)
goto fail;
if (r > 0) {
r = swap_arm_timer(s);
if (r < 0)
goto fail;
swap_set_state(s, state);
} else if (state == SWAP_ACTIVATING_SIGTERM)
else if (state == SWAP_DEACTIVATING_SIGTERM)
else
return;
fail:
}
static void swap_enter_activating(Swap *s) {
int r;
assert(s);
if (s->from_fragment) {
int priority = -1;
if (r < 0)
log_warning_errno(r, "Failed to parse swap priority \"%s\", ignoring: %m", s->parameters_fragment.options);
log_warning("Duplicate swap priority configuration by Priority and Options fields.");
if (r <= 0 && s->parameters_fragment.priority >= 0) {
if (s->parameters_fragment.options)
else
if (r < 0)
goto fail;
}
}
if (r < 0)
goto fail;
if (r < 0)
goto fail;
}
if (r < 0)
goto fail;
if (r < 0)
goto fail;
return;
fail:
}
static void swap_enter_deactivating(Swap *s) {
int r;
assert(s);
r = exec_command_set(s->control_command,
s->what,
NULL);
if (r < 0)
goto fail;
if (r < 0)
goto fail;
return;
fail:
}
static int swap_start(Unit *u) {
assert(s);
/* We cannot fulfill this request right now, try again later
* please! */
if (s->state == SWAP_DEACTIVATING ||
s->state == SWAP_DEACTIVATING_SIGTERM ||
s->state == SWAP_DEACTIVATING_SIGKILL ||
s->state == SWAP_ACTIVATING_SIGTERM ||
s->state == SWAP_ACTIVATING_SIGKILL)
return -EAGAIN;
if (s->state == SWAP_ACTIVATING)
return 0;
if (detect_container() > 0)
return -EPERM;
/* If there's a job for another swap unit for the same node
* running, then let's not dispatch this one for now, and wait
* until that other job has finished. */
return -EAGAIN;
s->result = SWAP_SUCCESS;
s->reset_cpu_usage = true;
return 1;
}
assert(s);
if (s->state == SWAP_DEACTIVATING ||
s->state == SWAP_DEACTIVATING_SIGTERM ||
s->state == SWAP_DEACTIVATING_SIGKILL ||
s->state == SWAP_ACTIVATING_SIGTERM ||
s->state == SWAP_ACTIVATING_SIGKILL)
return 0;
s->state == SWAP_ACTIVATING_DONE ||
s->state == SWAP_ACTIVE);
if (detect_container() > 0)
return -EPERM;
return 1;
}
assert(s);
assert(f);
if (s->control_pid > 0)
if (s->control_command_id >= 0)
return 0;
}
assert(s);
if (state < 0)
else
s->deserialized_state = state;
SwapResult f;
f = swap_result_from_string(value);
if (f < 0)
else if (f != SWAP_SUCCESS)
s->result = f;
else
s->control_pid = pid;
if (id < 0)
else {
s->control_command_id = id;
}
} else
return 0;
}
assert(u);
}
assert(u);
}
assert(s);
return s->from_proc_swaps;
}
SwapResult f;
assert(s);
if (pid != s->control_pid)
return;
s->control_pid = 0;
f = SWAP_SUCCESS;
else if (code == CLD_EXITED)
else if (code == CLD_KILLED)
f = SWAP_FAILURE_SIGNAL;
else if (code == CLD_DUMPED)
else
assert_not_reached("Unknown code");
if (f != SWAP_SUCCESS)
s->result = f;
if (s->control_command) {
s->control_command = NULL;
}
switch (s->state) {
case SWAP_ACTIVATING:
case SWAP_ACTIVATING_DONE:
case SWAP_ACTIVATING_SIGTERM:
case SWAP_ACTIVATING_SIGKILL:
if (f == SWAP_SUCCESS)
swap_enter_active(s, f);
else
swap_enter_dead(s, f);
break;
case SWAP_DEACTIVATING:
swap_enter_dead(s, f);
break;
default:
assert_not_reached("Uh, control process died at wrong time.");
}
/* Notify clients about changed exit status */
}
assert(s);
switch (s->state) {
case SWAP_ACTIVATING:
case SWAP_ACTIVATING_DONE:
break;
case SWAP_DEACTIVATING:
break;
case SWAP_ACTIVATING_SIGTERM:
if (s->kill_context.send_sigkill) {
} else {
}
break;
if (s->kill_context.send_sigkill) {
} else {
}
break;
case SWAP_ACTIVATING_SIGKILL:
break;
default:
assert_not_reached("Timeout at wrong time.");
}
return 0;
}
unsigned i;
int r = 0;
assert(m);
rewind(m->proc_swaps);
for (i = 1;; i++) {
int prio = 0, k;
k = fscanf(m->proc_swaps,
"%*s " /* type of swap */
"%*s " /* swap size */
"%*s " /* used */
"%i\n", /* priority */
if (k != 2) {
if (k == EOF)
break;
log_warning("Failed to parse /proc/swaps:%u.", i);
continue;
}
return log_oom();
if (k < 0)
r = k;
}
return r;
}
Unit *u;
int r;
assert(m);
r = swap_load_proc_swaps(m, true);
if (r < 0) {
log_error_errno(r, "Failed to reread /proc/swaps: %m");
/* Reset flags, just in case, for late calls */
}
return 0;
}
/* This has just been deactivated */
case SWAP_ACTIVE:
break;
default:
/* Fire again */
break;
}
} else if (swap->just_activated) {
/* New swap entry */
case SWAP_DEAD:
case SWAP_FAILED:
break;
case SWAP_ACTIVATING:
break;
default:
/* Nothing really changed, but let's
* issue an notification call
* nonetheless, in case somebody is
* waiting for this. */
break;
}
}
/* Reset the flags for later calls */
}
return 1;
}
assert(s);
* a device unit, follow that. */
if (s->from_fragment)
return NULL;
if (other->from_fragment)
/* Otherwise, make everybody follow the unit that's named after
* the swap device in the kernel */
return NULL;
}
/* Fall back to the first on the list */
}
int r;
assert(s);
if (LIST_JUST_US(same_devnode, s)) {
return 0;
}
if (!set)
return -ENOMEM;
if (r < 0)
goto fail;
}
return 1;
fail:
return r;
}
static void swap_shutdown(Manager *m) {
assert(m);
}
static void swap_enumerate(Manager *m) {
int r;
assert(m);
if (!m->proc_swaps) {
if (!m->proc_swaps) {
log_debug("Not swap enabled, skipping enumeration");
else
return;
}
r = sd_event_add_io(m->event, &m->swap_event_source, fileno(m->proc_swaps), EPOLLPRI, swap_dispatch_io, m);
if (r < 0) {
log_error_errno(r, "Failed to watch /proc/swaps: %m");
goto fail;
}
/* Dispatch this before we dispatch SIGCHLD, so that
if (r < 0) {
log_error_errno(r, "Failed to change /proc/swaps priority: %m");
goto fail;
}
}
r = swap_load_proc_swaps(m, false);
if (r < 0)
goto fail;
return;
fail:
swap_shutdown(m);
}
_cleanup_free_ char *e = NULL;
const char *dn;
Swap *s;
int r = 0;
assert(m);
if (!dn)
return 0;
if (r < 0)
return r;
s = hashmap_get(m->units, e);
if (s)
r = swap_set_devnode(s, dn);
_cleanup_free_ char *n = NULL;
int q;
if (q < 0)
return q;
s = hashmap_get(m->units, n);
if (s) {
q = swap_set_devnode(s, dn);
if (q < 0)
r = q;
}
}
return r;
}
const char *dn;
int r = 0;
Swap *s;
if (!dn)
return 0;
int q;
q = swap_set_devnode(s, NULL);
if (q < 0)
r = q;
}
return r;
}
static void swap_reset_failed(Unit *u) {
assert(s);
if (s->state == SWAP_FAILED)
swap_set_state(s, SWAP_DEAD);
s->result = SWAP_SUCCESS;
}
}
int r;
if (!s->timer_event_source)
return 0;
if (r < 0)
return r;
return 1;
}
static bool swap_supported(void) {
static int supported = -1;
/* If swap support is not available in the kernel, or we are
* running in a container we don't support swap units, and any
* attempts to starting one should fail immediately. */
if (supported < 0)
detect_container() <= 0;
return supported;
}
static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = {
[SWAP_EXEC_ACTIVATE] = "ExecActivate",
[SWAP_EXEC_DEACTIVATE] = "ExecDeactivate",
};
static const char* const swap_result_table[_SWAP_RESULT_MAX] = {
[SWAP_SUCCESS] = "success",
[SWAP_FAILURE_RESOURCES] = "resources",
[SWAP_FAILURE_TIMEOUT] = "timeout",
[SWAP_FAILURE_EXIT_CODE] = "exit-code",
[SWAP_FAILURE_SIGNAL] = "signal",
[SWAP_FAILURE_CORE_DUMP] = "core-dump"
};
const UnitVTable swap_vtable = {
.object_size = sizeof(Swap),
.sections =
"Unit\0"
"Swap\0"
"Install\0",
.private_section = "Swap",
.no_alias = true,
.no_instances = true,
.start = swap_start,
.starting_stopping = {
[0] = "Activating swap %s...",
[1] = "Deactivating swap %s...",
},
.finished_start_job = {
[JOB_DONE] = "Activated swap %s.",
[JOB_FAILED] = "Failed to activate swap %s.",
[JOB_TIMEOUT] = "Timed out activating swap %s.",
},
.finished_stop_job = {
[JOB_DONE] = "Deactivated swap %s.",
[JOB_FAILED] = "Failed deactivating swap %s.",
[JOB_TIMEOUT] = "Timed out deactivating swap %s.",
},
},
};