#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "c.h" #include "nls.h" #include "bitops.h" #include "blkdev.h" #include "pathnames.h" #include "xalloc.h" #include "strutils.h" #include "optutils.h" #include "closestream.h" #include "swapheader.h" #include "swapprober.h" #include "swapon-common.h" #ifdef HAVE_SYS_SWAP_H # include #endif #ifndef SWAP_FLAG_DISCARD # define SWAP_FLAG_DISCARD 0x10000 /* enable discard for swap */ #endif #ifndef SWAP_FLAG_DISCARD_ONCE # define SWAP_FLAG_DISCARD_ONCE 0x20000 /* discard swap area at swapon-time */ #endif #ifndef SWAP_FLAG_DISCARD_PAGES # define SWAP_FLAG_DISCARD_PAGES 0x40000 /* discard page-clusters after use */ #endif #define SWAP_FLAGS_DISCARD_VALID (SWAP_FLAG_DISCARD | SWAP_FLAG_DISCARD_ONCE | \ SWAP_FLAG_DISCARD_PAGES) #ifndef SWAP_FLAG_PREFER # define SWAP_FLAG_PREFER 0x8000 /* set if swap priority specified */ #endif #ifndef SWAP_FLAG_PRIO_MASK # define SWAP_FLAG_PRIO_MASK 0x7fff #endif #ifndef SWAP_FLAG_PRIO_SHIFT # define SWAP_FLAG_PRIO_SHIFT 0 #endif #if !defined(HAVE_SWAPON) && defined(SYS_swapon) # include # define swapon(path, flags) syscall(SYS_swapon, path, flags) #endif #define MAX_PAGESIZE (64 * 1024) #ifndef UUID_STR_LEN # define UUID_STR_LEN 37 #endif enum { SIG_SWAPSPACE = 1, SIG_SWSUSPEND }; /* column names */ struct colinfo { const char *name; /* header */ double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; }; enum { COL_PATH, COL_TYPE, COL_SIZE, COL_USED, COL_PRIO, COL_UUID, COL_LABEL }; static struct colinfo infos[] = { [COL_PATH] = { "NAME", 0.20, 0, N_("device file or partition path") }, [COL_TYPE] = { "TYPE", 0.20, SCOLS_FL_TRUNC, N_("type of the device")}, [COL_SIZE] = { "SIZE", 0.20, SCOLS_FL_RIGHT, N_("size of the swap area")}, [COL_USED] = { "USED", 0.20, SCOLS_FL_RIGHT, N_("bytes in use")}, [COL_PRIO] = { "PRIO", 0.20, SCOLS_FL_RIGHT, N_("swap priority")}, [COL_UUID] = { "UUID", 0.20, 0, N_("swap uuid")}, [COL_LABEL] = { "LABEL", 0.20, 0, N_("swap label")}, }; /* swap area properties */ struct swap_prop { int discard; /* discard policy */ int priority; /* non-prioritized swap by default */ int no_fail; /* skip device if not exist */ }; /* device description */ struct swap_device { const char *path; /* device or file to be turned on */ const char *label; /* swap label */ const char *uuid; /* unique identifier */ unsigned int pagesize; }; /* control struct */ struct swapon_ctl { int columns[ARRAY_SIZE(infos) * 2]; /* --show columns */ int ncolumns; /* number of columns */ struct swap_prop props; /* global settings for all devices */ unsigned int all:1, /* turn on all swap devices */ bytes:1, /* display --show in bytes */ fix_page_size:1, /* reinitialize page size */ no_heading:1, /* toggle --show headers */ raw:1, /* toggle --show alignment */ show:1, /* display --show information */ verbose:1; /* be chatty */ }; static int column_name_to_id(const char *name, size_t namesz) { size_t i; assert(name); for (i = 0; i < ARRAY_SIZE(infos); i++) { const char *cn = infos[i].name; if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) return i; } warnx(_("unknown column: %s"), name); return -1; } static inline int get_column_id(const struct swapon_ctl *ctl, int num) { assert(num < ctl->ncolumns); assert(ctl->columns[num] < (int) ARRAY_SIZE(infos)); return ctl->columns[num]; } static inline struct colinfo *get_column_info(const struct swapon_ctl *ctl, unsigned num) { return &infos[get_column_id(ctl, num)]; } static void add_scols_line(const struct swapon_ctl *ctl, struct libscols_table *table, struct libmnt_fs *fs) { int i; struct libscols_line *line; blkid_probe pr = NULL; const char *data; assert(table); assert(fs); line = scols_table_new_line(table, NULL); if (!line) err(EXIT_FAILURE, _("failed to allocate output line")); data = mnt_fs_get_source(fs); if (access(data, R_OK) == 0) pr = get_swap_prober(data); for (i = 0; i < ctl->ncolumns; i++) { char *str = NULL; off_t size; switch (get_column_id(ctl, i)) { case COL_PATH: xasprintf(&str, "%s", mnt_fs_get_source(fs)); break; case COL_TYPE: xasprintf(&str, "%s", mnt_fs_get_swaptype(fs)); break; case COL_SIZE: size = mnt_fs_get_size(fs); size *= 1024; /* convert to bytes */ if (ctl->bytes) xasprintf(&str, "%jd", size); else str = size_to_human_string(SIZE_SUFFIX_1LETTER, size); break; case COL_USED: size = mnt_fs_get_usedsize(fs); size *= 1024; /* convert to bytes */ if (ctl->bytes) xasprintf(&str, "%jd", size); else str = size_to_human_string(SIZE_SUFFIX_1LETTER, size); break; case COL_PRIO: xasprintf(&str, "%d", mnt_fs_get_priority(fs)); break; case COL_UUID: if (pr && !blkid_probe_lookup_value(pr, "UUID", &data, NULL)) xasprintf(&str, "%s", data); break; case COL_LABEL: if (pr && !blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) xasprintf(&str, "%s", data); break; default: break; } if (str && scols_line_refer_data(line, i, str)) err(EXIT_FAILURE, _("failed to add output data")); } if (pr) blkid_free_probe(pr); return; } static int display_summary(void) { struct libmnt_table *st = get_swaps(); struct libmnt_iter *itr; struct libmnt_fs *fs; if (!st) return -1; if (mnt_table_is_empty(st)) return 0; itr = mnt_new_iter(MNT_ITER_FORWARD); if (!itr) err(EXIT_FAILURE, _("failed to initialize libmount iterator")); printf(_("%s\t\t\t\tType\t\tSize\tUsed\tPriority\n"), _("Filename")); while (mnt_table_next_fs(st, itr, &fs) == 0) { printf("%-39s\t%-8s\t%jd\t%jd\t%d\n", mnt_fs_get_source(fs), mnt_fs_get_swaptype(fs), mnt_fs_get_size(fs), mnt_fs_get_usedsize(fs), mnt_fs_get_priority(fs)); } mnt_free_iter(itr); return 0; } static int show_table(struct swapon_ctl *ctl) { struct libmnt_table *st = get_swaps(); struct libmnt_iter *itr = NULL; struct libmnt_fs *fs; int i; struct libscols_table *table = NULL; if (!st) return -1; itr = mnt_new_iter(MNT_ITER_FORWARD); if (!itr) err(EXIT_FAILURE, _("failed to initialize libmount iterator")); scols_init_debug(0); table = scols_new_table(); if (!table) err(EXIT_FAILURE, _("failed to allocate output table")); scols_table_enable_raw(table, ctl->raw); scols_table_enable_noheadings(table, ctl->no_heading); for (i = 0; i < ctl->ncolumns; i++) { struct colinfo *col = get_column_info(ctl, i); if (!scols_table_new_column(table, col->name, col->whint, col->flags)) err(EXIT_FAILURE, _("failed to allocate output column")); } while (mnt_table_next_fs(st, itr, &fs) == 0) add_scols_line(ctl, table, fs); scols_print_table(table); scols_unref_table(table); mnt_free_iter(itr); return 0; } /* calls mkswap */ static int swap_reinitialize(struct swap_device *dev) { pid_t pid; int status, ret; char const *cmd[7]; int idx=0; assert(dev); assert(dev->path); warnx(_("%s: reinitializing the swap."), dev->path); switch ((pid=fork())) { case -1: /* fork error */ warn(_("fork failed")); return -1; case 0: /* child */ if (geteuid() != getuid()) { /* in case someone uses swapon as setuid binary */ if (setgid(getgid()) < 0) exit(EXIT_FAILURE); if (setuid(getuid()) < 0) exit(EXIT_FAILURE); } cmd[idx++] = "mkswap"; if (dev->label) { cmd[idx++] = "-L"; cmd[idx++] = dev->label; } if (dev->uuid) { cmd[idx++] = "-U"; cmd[idx++] = dev->uuid; } cmd[idx++] = dev->path; cmd[idx++] = NULL; execvp(cmd[0], (char * const *) cmd); errexec(cmd[0]); default: /* parent */ do { ret = waitpid(pid, &status, 0); } while (ret == -1 && errno == EINTR); if (ret < 0) { warn(_("waitpid failed")); return -1; } /* mkswap returns: 0=suss, >0 error */ if (WIFEXITED(status) && WEXITSTATUS(status)==0) return 0; /* ok */ break; } return -1; /* error */ } /* Replaces unwanted SWSUSPEND signature with swap signature */ static int swap_rewrite_signature(const struct swap_device *dev) { int fd, rc = -1; assert(dev); assert(dev->path); assert(dev->pagesize); fd = open(dev->path, O_WRONLY); if (fd == -1) { warn(_("cannot open %s"), dev->path); return -1; } if (lseek(fd, dev->pagesize - SWAP_SIGNATURE_SZ, SEEK_SET) < 0) { warn(_("%s: lseek failed"), dev->path); goto err; } if (write(fd, (void *) SWAP_SIGNATURE, SWAP_SIGNATURE_SZ) != SWAP_SIGNATURE_SZ) { warn(_("%s: write signature failed"), dev->path); goto err; } rc = 0; err: if (close_fd(fd) != 0) { warn(_("write failed: %s"), dev->path); rc = -1; } return rc; } static int swap_detect_signature(const char *buf, int *sig) { assert(buf); assert(sig); if (memcmp(buf, SWAP_SIGNATURE, SWAP_SIGNATURE_SZ) == 0) *sig = SIG_SWAPSPACE; else if (memcmp(buf, "S1SUSPEND", 9) == 0 || memcmp(buf, "S2SUSPEND", 9) == 0 || memcmp(buf, "ULSUSPEND", 9) == 0 || memcmp(buf, "\xed\xc3\x02\xe9\x98\x56\xe5\x0c", 8) == 0 || memcmp(buf, "LINHIB0001", 10) == 0) *sig = SIG_SWSUSPEND; else return 0; return 1; } static char *swap_get_header(int fd, int *sig, unsigned int *pagesize) { char *buf; ssize_t datasz; unsigned int page; assert(sig); assert(pagesize); *pagesize = 0; *sig = 0; buf = xmalloc(MAX_PAGESIZE); datasz = read(fd, buf, MAX_PAGESIZE); if (datasz == (ssize_t) -1) goto err; for (page = 0x1000; page <= MAX_PAGESIZE; page <<= 1) { /* skip 32k pagesize since this does not seem to * be supported */ if (page == 0x8000) continue; /* the smallest swap area is PAGE_SIZE*10, it means * 40k, that's less than MAX_PAGESIZE */ if (datasz < 0 || (size_t) datasz < (page - SWAP_SIGNATURE_SZ)) break; if (swap_detect_signature(buf + page - SWAP_SIGNATURE_SZ, sig)) { *pagesize = page; break; } } if (*pagesize) return buf; err: free(buf); return NULL; } /* returns real size of swap space */ static unsigned long long swap_get_size(const struct swap_device *dev, const char *hdr) { unsigned int last_page = 0; const unsigned int swap_version = SWAP_VERSION; const struct swap_header_v1_2 *s; assert(dev); assert(dev->pagesize > 0); s = (const struct swap_header_v1_2 *) hdr; if (s->version == swap_version) last_page = s->last_page; else if (swab32(s->version) == swap_version) last_page = swab32(s->last_page); return ((unsigned long long) last_page + 1) * dev->pagesize; } static void swap_get_info(struct swap_device *dev, const char *hdr) { const struct swap_header_v1_2 *s = (const struct swap_header_v1_2 *) hdr; assert(dev); if (s && *s->volume_name) dev->label = xstrdup(s->volume_name); if (s && *s->uuid) { const unsigned char *u = s->uuid; char str[UUID_STR_LEN]; snprintf(str, sizeof(str), "%02x%02x%02x%02x-" "%02x%02x-%02x%02x-" "%02x%02x-%02x%02x%02x%02x%02x%02x", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); dev->uuid = xstrdup(str); } } static int swapon_checks(const struct swapon_ctl *ctl, struct swap_device *dev) { struct stat st; int fd, sig; char *hdr = NULL; unsigned long long devsize = 0; int permMask; assert(ctl); assert(dev); assert(dev->path); fd = open(dev->path, O_RDONLY); if (fd == -1) { warn(_("cannot open %s"), dev->path); goto err; } if (fstat(fd, &st) < 0) { warn(_("stat of %s failed"), dev->path); goto err; } permMask = S_ISBLK(st.st_mode) ? 07007 : 07077; if ((st.st_mode & permMask) != 0) warnx(_("%s: insecure permissions %04o, %04o suggested."), dev->path, st.st_mode & 07777, ~permMask & 0666); if (S_ISREG(st.st_mode) && st.st_uid != 0) warnx(_("%s: insecure file owner %d, 0 (root) suggested."), dev->path, st.st_uid); /* test for holes by LBT */ if (S_ISREG(st.st_mode)) { if (st.st_blocks * 512 < st.st_size) { warnx(_("%s: skipping - it appears to have holes."), dev->path); goto err; } devsize = st.st_size; } if (S_ISBLK(st.st_mode) && blkdev_get_size(fd, &devsize)) { warnx(_("%s: get size failed"), dev->path); goto err; } hdr = swap_get_header(fd, &sig, &dev->pagesize); if (!hdr) { warnx(_("%s: read swap header failed"), dev->path); goto err; } if (ctl->verbose) warnx(_("%s: found signature [pagesize=%d, signature=%s]"), dev->path, dev->pagesize, sig == SIG_SWAPSPACE ? "swap" : sig == SIG_SWSUSPEND ? "suspend" : "unknown"); if (sig == SIG_SWAPSPACE && dev->pagesize) { unsigned long long swapsize = swap_get_size(dev, hdr); int syspg = getpagesize(); if (ctl->verbose) warnx(_("%s: pagesize=%d, swapsize=%llu, devsize=%llu"), dev->path, dev->pagesize, swapsize, devsize); if (swapsize > devsize) { if (ctl->verbose) warnx(_("%s: last_page 0x%08llx is larger" " than actual size of swapspace"), dev->path, swapsize); } else if (syspg < 0 || (unsigned int) syspg != dev->pagesize) { if (ctl->fix_page_size) { int rc; swap_get_info(dev, hdr); warnx(_("%s: swap format pagesize does not match."), dev->path); rc = swap_reinitialize(dev); if (rc < 0) goto err; } else warnx(_("%s: swap format pagesize does not match. " "(Use --fixpgsz to reinitialize it.)"), dev->path); } } else if (sig == SIG_SWSUSPEND) { /* We have to reinitialize swap with old (=useless) software suspend * data. The problem is that if we don't do it, then we get data * corruption the next time an attempt at unsuspending is made. */ warnx(_("%s: software suspend data detected. " "Rewriting the swap signature."), dev->path); if (swap_rewrite_signature(dev) < 0) goto err; } free(hdr); close(fd); return 0; err: if (fd != -1) close(fd); free(hdr); return -1; } static int do_swapon(const struct swapon_ctl *ctl, const struct swap_prop *prop, const char *spec, int canonic) { struct swap_device dev = { .path = NULL }; int status; int flags = 0; int priority; assert(ctl); assert(prop); if (!canonic) { dev.path = mnt_resolve_spec(spec, mntcache); if (!dev.path) return cannot_find(spec); } else dev.path = spec; priority = prop->priority; if (swapon_checks(ctl, &dev)) return -1; #ifdef SWAP_FLAG_PREFER if (priority >= 0) { if (priority > SWAP_FLAG_PRIO_MASK) priority = SWAP_FLAG_PRIO_MASK; flags = SWAP_FLAG_PREFER | ((priority & SWAP_FLAG_PRIO_MASK) << SWAP_FLAG_PRIO_SHIFT); } #endif /* * Validate the discard flags passed and set them * accordingly before calling sys_swapon. */ if (prop->discard && !(prop->discard & ~SWAP_FLAGS_DISCARD_VALID)) { /* * If we get here with both discard policy flags set, * we just need to tell the kernel to enable discards * and it will do correctly, just as we expect. */ if ((prop->discard & SWAP_FLAG_DISCARD_ONCE) && (prop->discard & SWAP_FLAG_DISCARD_PAGES)) flags |= SWAP_FLAG_DISCARD; else flags |= prop->discard; } if (ctl->verbose) printf(_("swapon %s\n"), dev.path); status = swapon(dev.path, flags); if (status < 0) warn(_("%s: swapon failed"), dev.path); return status; } static int swapon_by_label(struct swapon_ctl *ctl, const char *label) { char *device = mnt_resolve_tag("LABEL", label, mntcache); return device ? do_swapon(ctl, &ctl->props, device, TRUE) : cannot_find(label); } static int swapon_by_uuid(struct swapon_ctl *ctl, const char *uuid) { char *device = mnt_resolve_tag("UUID", uuid, mntcache); return device ? do_swapon(ctl, &ctl->props, device, TRUE) : cannot_find(uuid); } /* -o or fstab */ static int parse_options(struct swap_prop *props, const char *options) { char *arg = NULL; size_t argsz = 0; assert(props); assert(options); if (mnt_optstr_get_option(options, "nofail", NULL, NULL) == 0) props->no_fail = 1; if (mnt_optstr_get_option(options, "discard", &arg, &argsz) == 0) { props->discard |= SWAP_FLAG_DISCARD; if (arg) { /* only single-time discards are wanted */ if (strncmp(arg, "once", argsz) == 0) props->discard |= SWAP_FLAG_DISCARD_ONCE; /* do discard for every released swap page */ if (strncmp(arg, "pages", argsz) == 0) props->discard |= SWAP_FLAG_DISCARD_PAGES; } } arg = NULL; if (mnt_optstr_get_option(options, "pri", &arg, NULL) == 0 && arg) props->priority = atoi(arg); return 0; } static int swapon_all(struct swapon_ctl *ctl) { struct libmnt_table *tb = get_fstab(); struct libmnt_iter *itr; struct libmnt_fs *fs; int status = 0; if (!tb) err(EXIT_FAILURE, _("failed to parse %s"), mnt_get_fstab_path()); itr = mnt_new_iter(MNT_ITER_FORWARD); if (!itr) err(EXIT_FAILURE, _("failed to initialize libmount iterator")); while (mnt_table_find_next_fs(tb, itr, match_swap, NULL, &fs) == 0) { /* defaults */ const char *opts; const char *device; struct swap_prop prop; /* per device setting */ if (mnt_fs_get_option(fs, "noauto", NULL, NULL) == 0) { if (ctl->verbose) warnx(_("%s: noauto option -- ignored"), mnt_fs_get_source(fs)); continue; } /* default setting */ prop = ctl->props; /* overwrite default by setting from fstab */ opts = mnt_fs_get_options(fs); if (opts) parse_options(&prop, opts); /* convert LABEL=, UUID= etc. from fstab to device name */ device = mnt_resolve_spec(mnt_fs_get_source(fs), mntcache); if (!device) { if (!prop.no_fail) status |= cannot_find(mnt_fs_get_source(fs)); continue; } if (is_active_swap(device)) { if (ctl->verbose) warnx(_("%s: already active -- ignored"), device); continue; } if (prop.no_fail && access(device, R_OK) != 0) { if (ctl->verbose) warnx(_("%s: inaccessible -- ignored"), device); continue; } /* swapon */ status |= do_swapon(ctl, &prop, device, TRUE); } mnt_free_iter(itr); return status; } static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; size_t i; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] []\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Enable devices and files for paging and swapping.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -a, --all enable all swaps from /etc/fstab\n"), out); fputs(_(" -d, --discard[=] enable swap discards, if supported by device\n"), out); fputs(_(" -e, --ifexists silently skip devices that do not exist\n"), out); fputs(_(" -f, --fixpgsz reinitialize the swap space if necessary\n"), out); fputs(_(" -o, --options comma-separated list of swap options\n"), out); fputs(_(" -p, --priority specify the priority of the swap device\n"), out); fputs(_(" -s, --summary display summary about used swap devices (DEPRECATED)\n"), out); fputs(_(" --show[=] display summary in definable table\n"), out); fputs(_(" --noheadings don't print table heading (with --show)\n"), out); fputs(_(" --raw use the raw output format (with --show)\n"), out); fputs(_(" --bytes display swap size in bytes in --show output\n"), out); fputs(_(" -v, --verbose verbose mode\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(26)); fputs(_("\nThe parameter:\n" \ " -L