summaryrefslogtreecommitdiff
path: root/util-linux/swaponoff.c
blob: dc8dd9df6ad0d0ec8ccc61ead0253548b9728a34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/* vi: set sw=4 ts=4: */
/*
 * Mini swapon/swapoff implementation for busybox
 *
 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config SWAPON
//config:	bool "swapon (4.9 kb)"
//config:	default y
//config:	select PLATFORM_LINUX
//config:	help
//config:	  Once you have created some swap space using 'mkswap', you also need
//config:	  to enable your swap space with the 'swapon' utility. The 'swapoff'
//config:	  utility is used, typically at system shutdown, to disable any swap
//config:	  space. If you are not using any swap space, you can leave this
//config:	  option disabled.
//config:
//config:config FEATURE_SWAPON_DISCARD
//config:	bool "Support discard option -d"
//config:	default y
//config:	depends on SWAPON
//config:	help
//config:	  Enable support for discarding swap area blocks at swapon and/or as
//config:	  the kernel frees them. This option enables both the -d option on
//config:	  'swapon' and the 'discard' option for swap entries in /etc/fstab.
//config:
//config:config FEATURE_SWAPON_PRI
//config:	bool "Support priority option -p"
//config:	default y
//config:	depends on SWAPON
//config:	help
//config:	  Enable support for setting swap device priority in swapon.
//config:
//config:config SWAPOFF
//config:	bool "swapoff (4.3 kb)"
//config:	default y
//config:	select PLATFORM_LINUX

//                  APPLET_ODDNAME:name     main         location     suid_type     help
//applet:IF_SWAPON( APPLET_ODDNAME(swapon,  swap_on_off, BB_DIR_SBIN, BB_SUID_DROP, swapon))
//applet:IF_SWAPOFF(APPLET_ODDNAME(swapoff, swap_on_off, BB_DIR_SBIN, BB_SUID_DROP, swapoff))

//kbuild:lib-$(CONFIG_SWAPON) += swaponoff.o
//kbuild:lib-$(CONFIG_SWAPOFF) += swaponoff.o

//usage:#define swapon_trivial_usage
//usage:       "[-a] [-e]" IF_FEATURE_SWAPON_DISCARD(" [-d[POL]]") IF_FEATURE_SWAPON_PRI(" [-p PRI]") " [DEVICE]"
//usage:#define swapon_full_usage "\n\n"
//usage:       "Start swapping on DEVICE\n"
//usage:     "\n	-a	Start swapping on all swap devices"
//usage:	IF_FEATURE_SWAPON_DISCARD(
//usage:     "\n	-d[POL]	Discard blocks at swapon (POL=once),"
//usage:     "\n		as freed (POL=pages), or both (POL omitted)"
//usage:	)
//usage:     "\n	-e	Silently skip devices that do not exist"
//usage:	IF_FEATURE_SWAPON_PRI(
//usage:     "\n	-p PRI	Set swap device priority"
//usage:	)
//usage:
//usage:#define swapoff_trivial_usage
//usage:       "[-a] [DEVICE]"
//usage:#define swapoff_full_usage "\n\n"
//usage:       "Stop swapping on DEVICE\n"
//usage:     "\n	-a	Stop swapping on all swap devices"

#include "libbb.h"
#include "common_bufsiz.h"
#include <mntent.h>
#ifndef __BIONIC__
# include <sys/swap.h>
#endif

#if ENABLE_FEATURE_MOUNT_LABEL
# include "volume_id.h"
#else
# define resolve_mount_spec(fsname) ((void)0)
#endif

#ifndef MNTTYPE_SWAP
# define MNTTYPE_SWAP "swap"
#endif

#if ENABLE_FEATURE_SWAPON_DISCARD
#ifndef SWAP_FLAG_DISCARD
#define SWAP_FLAG_DISCARD 0x10000
#endif
#ifndef SWAP_FLAG_DISCARD_ONCE
#define SWAP_FLAG_DISCARD_ONCE 0x20000
#endif
#ifndef SWAP_FLAG_DISCARD_PAGES
#define SWAP_FLAG_DISCARD_PAGES 0x40000
#endif
#define SWAP_FLAG_DISCARD_MASK \
	(SWAP_FLAG_DISCARD | SWAP_FLAG_DISCARD_ONCE | SWAP_FLAG_DISCARD_PAGES)
#endif


#if ENABLE_FEATURE_SWAPON_DISCARD || ENABLE_FEATURE_SWAPON_PRI
struct globals {
	int flags;
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
#define g_flags (G.flags)
#define save_g_flags()    int save_g_flags = g_flags
#define restore_g_flags() g_flags = save_g_flags
#else
#define g_flags 0
#define save_g_flags()    ((void)0)
#define restore_g_flags() ((void)0)
#endif
#define INIT_G() do { setup_common_bufsiz(); } while (0)

#if ENABLE_SWAPOFF
# if ENABLE_SWAPON
#  define do_swapoff (applet_name[5] == 'f')
# else
#  define do_swapoff 1
# endif
#else
#  define do_swapoff 0
#endif

/* Command line options */
enum {
	OPTBIT_a,                              /* -a all      */
	OPTBIT_e,                              /* -e ifexists */
	IF_FEATURE_SWAPON_DISCARD( OPTBIT_d ,) /* -d discard  */
	IF_FEATURE_SWAPON_PRI    ( OPTBIT_p ,) /* -p priority */
	OPT_a = 1 << OPTBIT_a,
	OPT_e = 1 << OPTBIT_e,
	OPT_d = IF_FEATURE_SWAPON_DISCARD((1 << OPTBIT_d)) + 0,
	OPT_p = IF_FEATURE_SWAPON_PRI    ((1 << OPTBIT_p)) + 0,
};

#define OPT_ALL      (option_mask32 & OPT_a)
#define OPT_DISCARD  (option_mask32 & OPT_d)
#define OPT_IFEXISTS (option_mask32 & OPT_e)
#define OPT_PRIO     (option_mask32 & OPT_p)

static int swap_enable_disable(char *device)
{
	int err = 0;
	int quiet = 0;

	resolve_mount_spec(&device);

	if (do_swapoff) {
		err = swapoff(device);
		/* Don't complain on OPT_ALL if not a swap device or if it doesn't exist */
		quiet = (OPT_ALL && (errno == EINVAL || errno == ENOENT));
	} else {
		/* swapon */
		struct stat st;
		err = stat(device, &st);
		if (!err) {
			if (ENABLE_DESKTOP && S_ISREG(st.st_mode)) {
				if (st.st_blocks * (off_t)512 < st.st_size) {
					bb_error_msg("%s: file has holes", device);
					return 1;
				}
			}
			err = swapon(device, g_flags);
			/* Don't complain on swapon -a if device is already in use */
			quiet = (OPT_ALL && errno == EBUSY);
		}
		/* Don't complain if file does not exist with -e option */
		if (err && OPT_IFEXISTS && errno == ENOENT)
			err = 0;
	}

	if (err && !quiet) {
		bb_simple_perror_msg(device);
		return 1;
	}
	return 0;
}

#if ENABLE_FEATURE_SWAPON_DISCARD
static void set_discard_flag(char *s)
{
	/* Unset the flag first to allow fstab options to override */
	/* options set on the command line */
	g_flags = (g_flags & ~SWAP_FLAG_DISCARD_MASK) | SWAP_FLAG_DISCARD;

	if (!s) /* No optional policy value on the commandline */
		return;
	/* Skip prepended '=' */
	if (*s == '=')
		s++;
	/* For fstab parsing: remove other appended options */
	*strchrnul(s, ',') = '\0';

	if (strcmp(s, "once") == 0)
		g_flags |= SWAP_FLAG_DISCARD_ONCE;
	if  (strcmp(s, "pages") == 0)
		g_flags |= SWAP_FLAG_DISCARD_PAGES;
}
#else
#define set_discard_flag(s) ((void)0)
#endif

#if ENABLE_FEATURE_SWAPON_PRI
static void set_priority_flag(char *s)
{
	unsigned prio;

	/* For fstab parsing: remove other appended options */
	*strchrnul(s, ',') = '\0';
	/* Max allowed 32767 (== SWAP_FLAG_PRIO_MASK) */
	prio = bb_strtou(s, NULL, 10);
	if (!errno) {
		/* Unset the flag first to allow fstab options to override */
		/* options set on the command line */
		g_flags = (g_flags & ~SWAP_FLAG_PRIO_MASK) | SWAP_FLAG_PREFER |
					MIN(prio, SWAP_FLAG_PRIO_MASK);
	}
}
#else
#define set_priority_flag(s) ((void)0)
#endif

static int do_em_all_in_fstab(void)
{
	struct mntent *m;
	int err = 0;
	FILE *f = xfopen_for_read("/etc/fstab");

	while ((m = getmntent(f)) != NULL) {
		if (strcmp(m->mnt_type, MNTTYPE_SWAP) == 0) {
			/* swapon -a should ignore entries with noauto,
			 * but swapoff -a should process them
			 */
			if (do_swapoff || hasmntopt(m, MNTOPT_NOAUTO) == NULL) {
				/* each swap space might have different flags */
				/* save global flags for the next round */
				save_g_flags();
				if (ENABLE_FEATURE_SWAPON_DISCARD) {
					char *p = hasmntopt(m, "discard");
					if (p) {
						/* move to '=' or to end of string */
						p += 7;
						set_discard_flag(p);
					}
				}
				if (ENABLE_FEATURE_SWAPON_PRI) {
					char *p = hasmntopt(m, "pri");
					if (p) {
						set_priority_flag(p + 4);
					}
				}
				err |= swap_enable_disable(m->mnt_fsname);
				restore_g_flags();
			}
		}
	}

	if (ENABLE_FEATURE_CLEAN_UP)
		endmntent(f);

	return err;
}

static int do_all_in_proc_swaps(void)
{
	char *line;
	int err = 0;
	FILE *f = fopen_for_read("/proc/swaps");
	/* Don't complain if missing */
	if (f) {
		while ((line = xmalloc_fgetline(f)) != NULL) {
			if (line[0] == '/') {
				*strchrnul(line, ' ') = '\0';
				err |= swap_enable_disable(line);
			}
			free(line);
		}
		if (ENABLE_FEATURE_CLEAN_UP)
			fclose(f);
	}

	return err;
}

#define OPTSTR_SWAPON "ae" \
	IF_FEATURE_SWAPON_DISCARD("d::") \
	IF_FEATURE_SWAPON_PRI("p:")

int swap_on_off_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int swap_on_off_main(int argc UNUSED_PARAM, char **argv)
{
	IF_FEATURE_SWAPON_PRI(char *prio;)
	IF_FEATURE_SWAPON_DISCARD(char *discard = NULL;)
	int ret = 0;

	INIT_G();

	getopt32(argv, do_swapoff ? "ae" : OPTSTR_SWAPON
			IF_FEATURE_SWAPON_DISCARD(, &discard)
			IF_FEATURE_SWAPON_PRI(, &prio)
	);

	argv += optind;

	if (OPT_DISCARD) {
		set_discard_flag(discard);
	}
	if (OPT_PRIO) {
		set_priority_flag(prio);
	}

	if (OPT_ALL) {
		/* swapoff -a does also /proc/swaps */
		if (do_swapoff)
			ret = do_all_in_proc_swaps();
		ret |= do_em_all_in_fstab();
	} else if (!*argv) {
		/* if not -a we need at least one arg */
		bb_show_usage();
	}
	/* Unset -a now to allow for more messages in swap_enable_disable */
	option_mask32 = option_mask32 & ~OPT_a;
	/* Now process devices on the commandline if any */
	while (*argv) {
		ret |= swap_enable_disable(*argv++);
	}
	return ret;
}