diff options
author | Tristan Schmelcher <tschmelcher@google.com> | 2013-07-08 16:19:49 -0400 |
---|---|---|
committer | Richard Weinberger <richard@nod.at> | 2013-07-19 10:45:18 +0200 |
commit | 7473534130c3156d199512c99ff1b796233e8547 (patch) | |
tree | e92d36be92de59abe737f49e8a23bcb7e1bcf12b /arch | |
parent | 0974a9cadc7886f7baaa458bb0c89f5c5f9d458e (diff) | |
download | lwn-7473534130c3156d199512c99ff1b796233e8547.tar.gz lwn-7473534130c3156d199512c99ff1b796233e8547.zip |
uml: Fix which_tmpdir failure when /dev/shm is a symlink, and in other edge cases
which_tmpdir did the wrong thing if /dev/shm was a symlink (e.g., to /run/shm),
if there were multiple mounts on top of each other, if the mount(s) were
obscured by a later mount, or if /dev/shm was a prefix of another mount point.
This fixes these cases. Applies to 3.9.6.
Signed-off-by: Tristan Schmelcher <tschmelcher@google.com>
Signed-off-by: Richard Weinberger <richard@nod.at>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/um/os-Linux/mem.c | 230 |
1 files changed, 189 insertions, 41 deletions
diff --git a/arch/um/os-Linux/mem.c b/arch/um/os-Linux/mem.c index ba4398056fe9..3c4af77e51a2 100644 --- a/arch/um/os-Linux/mem.c +++ b/arch/um/os-Linux/mem.c @@ -53,6 +53,25 @@ static void __init find_tempdir(void) } /* + * Remove bytes from the front of the buffer and refill it so that if there's a + * partial string that we care about, it will be completed, and we can recognize + * it. + */ +static int pop(int fd, char *buf, size_t size, size_t npop) +{ + ssize_t n; + size_t len = strlen(&buf[npop]); + + memmove(buf, &buf[npop], len + 1); + n = read(fd, &buf[len], size - len - 1); + if (n < 0) + return -errno; + + buf[len + n] = '\0'; + return 1; +} + +/* * This will return 1, with the first character in buf being the * character following the next instance of c in the file. This will * read the file as needed. If there's an error, -errno is returned; @@ -61,7 +80,6 @@ static void __init find_tempdir(void) static int next(int fd, char *buf, size_t size, char c) { ssize_t n; - size_t len; char *ptr; while ((ptr = strchr(buf, c)) == NULL) { @@ -74,20 +92,129 @@ static int next(int fd, char *buf, size_t size, char c) buf[n] = '\0'; } - ptr++; - len = strlen(ptr); - memmove(buf, ptr, len + 1); + return pop(fd, buf, size, ptr - buf + 1); +} + +/* + * Decode an octal-escaped and space-terminated path of the form used by + * /proc/mounts. May be used to decode a path in-place. "out" must be at least + * as large as the input. The output is always null-terminated. "len" gets the + * length of the output, excluding the trailing null. Returns 0 if a full path + * was successfully decoded, otherwise an error. + */ +static int decode_path(const char *in, char *out, size_t *len) +{ + char *first = out; + int c; + int i; + int ret = -EINVAL; + while (1) { + switch (*in) { + case '\0': + goto out; + + case ' ': + ret = 0; + goto out; + + case '\\': + in++; + c = 0; + for (i = 0; i < 3; i++) { + if (*in < '0' || *in > '7') + goto out; + c = (c << 3) | (*in++ - '0'); + } + *(unsigned char *)out++ = (unsigned char) c; + break; + + default: + *out++ = *in++; + break; + } + } + +out: + *out = '\0'; + *len = out - first; + return ret; +} + +/* + * Computes the length of s when encoded with three-digit octal escape sequences + * for the characters in chars. + */ +static size_t octal_encoded_length(const char *s, const char *chars) +{ + size_t len = strlen(s); + while ((s = strpbrk(s, chars)) != NULL) { + len += 3; + s++; + } + + return len; +} + +enum { + OUTCOME_NOTHING_MOUNTED, + OUTCOME_TMPFS_MOUNT, + OUTCOME_NON_TMPFS_MOUNT, +}; + +/* Read a line of /proc/mounts data looking for a tmpfs mount at "path". */ +static int read_mount(int fd, char *buf, size_t bufsize, const char *path, + int *outcome) +{ + int found; + int match; + char *space; + size_t len; + + enum { + MATCH_NONE, + MATCH_EXACT, + MATCH_PARENT, + }; + + found = next(fd, buf, bufsize, ' '); + if (found != 1) + return found; /* - * Refill the buffer so that if there's a partial string that we care - * about, it will be completed, and we can recognize it. + * If there's no following space in the buffer, then this path is + * truncated, so it can't be the one we're looking for. */ - n = read(fd, &buf[len], size - len - 1); - if (n < 0) - return -errno; + space = strchr(buf, ' '); + if (space) { + match = MATCH_NONE; + if (!decode_path(buf, buf, &len)) { + if (!strcmp(buf, path)) + match = MATCH_EXACT; + else if (!strncmp(buf, path, len) + && (path[len] == '/' || !strcmp(buf, "/"))) + match = MATCH_PARENT; + } + + found = pop(fd, buf, bufsize, space - buf + 1); + if (found != 1) + return found; + + switch (match) { + case MATCH_EXACT: + if (!strncmp(buf, "tmpfs", strlen("tmpfs"))) + *outcome = OUTCOME_TMPFS_MOUNT; + else + *outcome = OUTCOME_NON_TMPFS_MOUNT; + break; - buf[len + n] = '\0'; - return 1; + case MATCH_PARENT: + /* This mount obscures any previous ones. */ + *outcome = OUTCOME_NOTHING_MOUNTED; + break; + } + } + + return next(fd, buf, bufsize, '\n'); } /* which_tmpdir is called only during early boot */ @@ -106,8 +233,12 @@ static int checked_tmpdir = 0; */ static void which_tmpdir(void) { - int fd, found; - char buf[128] = { '\0' }; + int fd; + int found; + int outcome; + char *path; + char *buf; + size_t bufsize; if (checked_tmpdir) return; @@ -116,49 +247,66 @@ static void which_tmpdir(void) printf("Checking for tmpfs mount on /dev/shm..."); + path = realpath("/dev/shm", NULL); + if (!path) { + printf("failed to check real path, errno = %d\n", errno); + return; + } + printf("%s...", path); + + /* + * The buffer needs to be able to fit the full octal-escaped path, a + * space, and a trailing null in order to successfully decode it. + */ + bufsize = octal_encoded_length(path, " \t\n\\") + 2; + + if (bufsize < 128) + bufsize = 128; + + buf = malloc(bufsize); + if (!buf) { + printf("malloc failed, errno = %d\n", errno); + goto out; + } + buf[0] = '\0'; + fd = open("/proc/mounts", O_RDONLY); if (fd < 0) { printf("failed to open /proc/mounts, errno = %d\n", errno); - return; + goto out1; } + outcome = OUTCOME_NOTHING_MOUNTED; while (1) { - found = next(fd, buf, ARRAY_SIZE(buf), ' '); - if (found != 1) - break; - - if (!strncmp(buf, "/dev/shm", strlen("/dev/shm"))) - goto found; - - found = next(fd, buf, ARRAY_SIZE(buf), '\n'); + found = read_mount(fd, buf, bufsize, path, &outcome); if (found != 1) break; } -err: - if (found == 0) - printf("nothing mounted on /dev/shm\n"); - else if (found < 0) + if (found < 0) { printf("read returned errno %d\n", -found); + } else { + switch (outcome) { + case OUTCOME_TMPFS_MOUNT: + printf("OK\n"); + default_tmpdir = "/dev/shm"; + break; -out: - close(fd); - - return; - -found: - found = next(fd, buf, ARRAY_SIZE(buf), ' '); - if (found != 1) - goto err; + case OUTCOME_NON_TMPFS_MOUNT: + printf("not tmpfs\n"); + break; - if (strncmp(buf, "tmpfs", strlen("tmpfs"))) { - printf("not tmpfs\n"); - goto out; + default: + printf("nothing mounted on /dev/shm\n"); + break; + } } - printf("OK\n"); - default_tmpdir = "/dev/shm"; - goto out; + close(fd); +out1: + free(buf); +out: + free(path); } static int __init make_tempfile(const char *template, char **out_tempname, |