]> err.no Git - util-linux/commitdiff
swapon: add swap format detection and pagesize check
authorMatthias Koenig <mkoenig@suse.de>
Thu, 27 Nov 2008 11:32:56 +0000 (12:32 +0100)
committerKarel Zak <kzak@redhat.com>
Fri, 5 Dec 2008 11:54:12 +0000 (12:54 +0100)
As swap format depends on the pagesize being used, it may happen
that the pagesize of the swap space and the current pagesize differ,
resulting in swapon to fail when trying to enable such a swap space.
In such a case swapon should rather reinitialize the swap space.

[kzak@redhat.com: - add blkdev.c to the global swapon_SOURCES
                  - don't try to detect for huge pages on small swap
                    areas (or when read() returns less than MAX_PAGESIZE)
                  - fix fprintf() format string]

Co-Author: Olaf Hering <olh@suse.de>
Signed-off-by: Matthias Koenig <mkoenig@suse.de>
Signed-off-by: Karel Zak <kzak@redhat.com>
mount/Makefile.am
mount/swapon.c

index 00882af31ba95a2174ee21a44a693c764076d99b..7ff528ca5855bb8192bd3f90c19e94f901b7019a 100644 (file)
@@ -24,7 +24,8 @@ umount_SOURCES = umount.c $(mount_common)
 umount_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
 umount_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
 
-swapon_SOURCES = swapon.c swap_constants.h $(utils_common)
+swapon_SOURCES = swapon.c swap_constants.h $(utils_common) \
+       ../lib/linux_version.c ../lib/blkdev.c
 
 losetup_SOURCES = lomount.c sundries.c xmalloc.c realpath.c \
        loop.h lomount.h xmalloc.h sundries.h realpath.h
@@ -71,7 +72,6 @@ endif
 
 if HAVE_VOLUME_ID
 utils_common += fsprobe_volumeid.c
-swapon_SOURCES += ../lib/linux_version.c ../lib/blkdev.c
 LDADD_common += $(VOLUMEID_LIBS)
 LDADD_common_static += $(VOLUMEID_LIBS_STATIC)
 endif
index eb0eb4aba3c994fa32a9d7a130593b3031532234..0d5067e04818c5dcfd5552f5caf88fda3e2433fe 100644 (file)
@@ -13,6 +13,9 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <fcntl.h>
+#include <stdint.h>
+#include "bitops.h"
+#include "blkdev.h"
 #include "xmalloc.h"
 #include "swap_constants.h"
 #include "nls.h"
@@ -20,6 +23,7 @@
 #include "realpath.h"
 #include "pathnames.h"
 #include "sundries.h"
+#include "swapheader.h"
 
 #define PATH_MKSWAP    "/sbin/mkswap"
 
@@ -39,6 +43,8 @@
 #define QUIET  1
 #define CANONIC        1
 
+#define MAX_PAGESIZE   (64 * 1024)
+
 int all = 0;
 int priority = -1;     /* non-prioritized swap by default */
 
@@ -238,11 +244,120 @@ swap_reinitialize(const char *device) {
        return -1; /* error */
 }
 
+int
+swap_detect_signature(const char *buf)
+{
+       if ((memcmp(buf, "SWAP-SPACE", 10) == 0) ||
+            (memcmp(buf, "SWAPSPACE2", 10) == 0))
+               return 1;
+
+       return 0;
+}
+
+/* return the pagesize the swap format has been built with
+ * as swap metadata depends on the pagesize, we have to
+ * reinitialize if it does not match with the current pagesize
+ * returns 0 if not a valid swap format
+ */
+unsigned int
+swap_get_pagesize(const char *dev)
+{
+       int fd;
+       char *buf;
+       unsigned int page, last_page = 0;
+       unsigned int pagesize = 0;
+       unsigned long long size, swap_size;
+       int swap_version = 0;
+       int flip = 0;
+       int datasz;
+       struct swap_header_v1_2 *s;
+       struct stat sb;
+
+       fd = open(dev, O_RDONLY);
+       if (fd == -1) {
+               perror("open");
+               return 0;
+       }
+
+       /* get size */
+       if (fstat(fd, &sb)) {
+               perror("fstat");
+               goto err;
+       }
+       if (S_ISBLK(sb.st_mode)) {
+               if (blkdev_get_size(fd, &size)) {
+                       perror("blkdev_get_size");
+                       goto err;
+               }
+       } else
+               size = sb.st_size;
+
+       buf = malloc(MAX_PAGESIZE);
+       if (!buf) {
+               perror("malloc");
+               goto err;
+       }
+
+       datasz = read(fd, buf, MAX_PAGESIZE);
+       if (datasz == (ssize_t) -1) {
+               perror("read");
+               goto err1;
+       }
+
+       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 < (page - 10))
+                       break;
+               if (swap_detect_signature(buf + page - 10)) {
+                       pagesize = page;
+                       break;
+               }
+       }
+
+       if (pagesize) {
+               s = (struct swap_header_v1_2 *)buf;
+               if (s->version == 1) {
+                       swap_version = 1;
+                       last_page = s->last_page;
+               } else if (swab32(s->version) == 1) {
+                       flip = 1;
+                       swap_version = 1;
+                       last_page = swab32(s->last_page);
+               }
+               if (verbose)
+                       fprintf(stderr, _("found %sswap v%d signature string"
+                                       " for %d KiB PAGE_SIZE\n"),
+                               flip ? "other-endian " : "", swap_version,
+                               pagesize / 1024);
+               swap_size = (last_page + 1) * pagesize;
+               if (swap_size > size) {
+                       if (verbose)
+                               fprintf(stderr, _("last_page 0x%08llx is larger"
+                                       " than actual size of swapspace\n"),
+                                       (unsigned long long)swap_size);
+                       pagesize = 0;
+               }
+       }
+
+err1:
+       free(buf);
+err:
+       close(fd);
+       return pagesize;
+}
+
 static int
 do_swapon(const char *orig_special, int prio, int canonic) {
        int status;
+       int reinitialize = 0;
        struct stat st;
        const char *special = orig_special;
+       unsigned int swap_pagesize = 0;
 
        if (verbose)
                printf(_("%s on %s\n"), progname, orig_special);
@@ -260,6 +375,15 @@ do_swapon(const char *orig_special, int prio, int canonic) {
                return -1;
        }
 
+       swap_pagesize = swap_get_pagesize(special);
+       if (swap_pagesize && (getpagesize() != swap_pagesize)) {
+               if (verbose)
+                       fprintf(stderr, _("%s: %s: swap format pagesize does not match."
+                               " Reinitializing the swap.\n"),
+                               progname, special);
+               reinitialize = 1;
+       }
+
        /* 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.
@@ -268,6 +392,10 @@ do_swapon(const char *orig_special, int prio, int canonic) {
                fprintf(stdout, _("%s: %s: software suspend data detected. "
                                        "Reinitializing the swap.\n"),
                        progname, special);
+               reinitialize = 1;
+       }
+
+       if (reinitialize) {
                if (swap_reinitialize(special) < 0)
                        return -1;
        }