From: John Crispin Date: Thu, 3 Apr 2014 17:38:18 +0000 (+0100) Subject: move files around X-Git-Url: http://git.archive.openwrt.org/?p=project%2Ffstools.git;a=commitdiff_plain;h=8603b6f2e7782437f3af16778b231ec9c1045f09 move files around Signed-off-by: John Crispin --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 5266785..b81d66c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,14 @@ ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") ADD_EXECUTABLE(fs-state fs-state.c - backend/base.c - backend/snapshot.c - backend/extroot.c - backend/jffs2.c - driver/volume.c - driver/mtd.c - lib/mount.c - lib/find.c) - + libfstools/mount_root.c + libfstools/snapshot.c + libfstools/extroot.c + libfstools/jffs2.c + libfstools/volume.c + libfstools/mtd.c + libfstools/mount.c + libfstools/find.c) TARGET_LINK_LIBRARIES(fs-state ubox) INSTALL(TARGETS fs-state RUNTIME DESTINATION sbin) diff --git a/backend/base.c b/backend/base.c deleted file mode 100644 index 6bb503e..0000000 --- a/backend/base.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include "../fs-state.h" - -#include "../driver/volume.h" - -int -backend_mount(char *name) -{ - struct backend *b = find_backend(name); - - if (!b || !b->mount) - return -1; - - return b->mount(); -} - -static int -backend_info(char *name) -{ - struct backend *b = find_backend(name); - - if (!b || !b->info) - return -1; - - return b->info(); -} - -static int -start(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - - if (!getenv("PREINIT")) - return -1; - - if (!v) { - v = volume_find("rootfs"); - volume_init(v); - fprintf(stderr, "mounting /dev/root\n"); - mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0); - return 0; - } - - extroot_prefix = ""; - if (!backend_mount("extroot")) { - fprintf(stderr, "fs-state: switched to extroot\n"); - return 0; - } - - switch (volume_identify(v)) { - case FS_NONE: - case FS_DEADCODE: - return ramoverlay(); - - case FS_JFFS2: - backend_mount("overlay"); - break; - - case FS_SNAPSHOT: - backend_mount("snapshot"); - break; - } - - return 0; -} - -static int -stop(int argc, char **argv) -{ - if (!getenv("SHUTDOWN")) - return -1; - - return 0; -} - -static int -done(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - - if (!v) - return -1; - - switch (volume_identify(v)) { - case FS_NONE: - case FS_DEADCODE: - return jffs2_switch(argc, argv); - } - - return 0; -} - -static int -info(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - - if (!v) - return -1; - - switch (volume_identify(v)) { - case FS_SNAPSHOT: - backend_info("snapshot"); - return 0; - } - - return 0; -} - -static struct backend start_backend = { - .name = "start", - .cli = start, -}; -BACKEND(start_backend); - -static struct backend stop_backend = { - .name = "stop", - .cli = stop, -}; -BACKEND(stop_backend); - -static struct backend done_backend = { - .name = "done", - .cli = done, -}; -BACKEND(done_backend); - -static struct backend info_backend = { - .name = "info", - .cli = info, -}; -BACKEND(info_backend); diff --git a/backend/extroot.c b/backend/extroot.c deleted file mode 100644 index 2ed9b37..0000000 --- a/backend/extroot.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../fs-state.h" - -char const *extroot_prefix = NULL; - -static int mount_extroot(void) -{ - char block_path[32]; - char kmod_loader[64]; - struct stat s; - pid_t pid; - - if (!extroot_prefix) - return -1; - - sprintf(block_path, "%s/sbin/block", extroot_prefix); - - if (stat(block_path, &s)) - return -1; - - sprintf(kmod_loader, "/sbin/kmodloader %s/etc/modules-boot.d/ %s", extroot_prefix, extroot_prefix); - system(kmod_loader); - - pid = fork(); - if (!pid) { - mkdir("/tmp/extroot", 0755); - execl(block_path, block_path, "extroot", NULL); - exit(-1); - } else if (pid > 0) { - int status; - - waitpid(pid, &status, 0); - if (!WEXITSTATUS(status)) { - if (find_mount("/tmp/extroot/mnt")) { - mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT | MS_RDONLY, 0); - - mkdir("/tmp/extroot/mnt/proc", 0755); - mkdir("/tmp/extroot/mnt/dev", 0755); - mkdir("/tmp/extroot/mnt/sys", 0755); - mkdir("/tmp/extroot/mnt/tmp", 0755); - mkdir("/tmp/extroot/mnt/rom", 0755); - - if (mount_move("/tmp/extroot", "", "/mnt")) { - fprintf(stderr, "moving pivotroot failed - continue normal boot\n"); - umount("/tmp/extroot/mnt"); - } else if (pivot("/mnt", "/rom")) { - fprintf(stderr, "switching to pivotroot failed - continue normal boot\n"); - umount("/mnt"); - } else { - umount("/tmp/overlay"); - rmdir("/tmp/overlay"); - rmdir("/tmp/extroot/mnt"); - rmdir("/tmp/extroot"); - return 0; - } - } else if (find_mount("/tmp/extroot/overlay")) { - if (mount_move("/tmp/extroot", "", "/overlay")) { - fprintf(stderr, "moving extroot failed - continue normal boot\n"); - umount("/tmp/extroot/overlay"); - } else if (fopivot("/overlay", "/rom")) { - fprintf(stderr, "switching to extroot failed - continue normal boot\n"); - umount("/overlay"); - } else { - umount("/tmp/overlay"); - rmdir("/tmp/overlay"); - rmdir("/tmp/extroot/overlay"); - rmdir("/tmp/extroot"); - return 0; - } - } - } - } - return -1; -} - -static struct backend extroot_backend = { - .name = "extroot", - .mount = mount_extroot, -}; -BACKEND(extroot_backend); diff --git a/backend/jffs2.c b/backend/jffs2.c deleted file mode 100644 index 09c66cf..0000000 --- a/backend/jffs2.c +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../fs-state.h" -#include "../driver/volume.h" - -#define SWITCH_JFFS2 "/tmp/.switch_jffs2" - -void -foreachdir(const char *dir, int (*cb)(const char*)) -{ - char globdir[256]; - glob_t gl; - int j; - - if (dir[strlen(dir) - 1] == '/') - snprintf(globdir, 256, "%s*", dir); - else - snprintf(globdir, 256, "%s/*", dir); - - if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl)) - for (j = 0; j < gl.gl_pathc; j++) - foreachdir(gl.gl_pathv[j], cb); - - cb(dir); -} - -static int -jffs2_mount(void) -{ - struct volume *v; - - if (mkdir("/tmp/overlay", 0755)) { - fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); - return -1; - } - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "rootfs_data does not exist\n"); - return -1; - } - - if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { - fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", v->blk, strerror(errno)); - return -1; - } - - return volume_init(v); -} - -static int -switch2jffs(void) -{ - struct volume *v = volume_find("rootfs_data"); - struct stat s; - int ret; - - if (!stat(SWITCH_JFFS2, &s)) { - fprintf(stderr, "jffs2 switch already running\n"); - return -1; - } - - if (!v) { - fprintf(stderr, "no rootfs_data was found\n"); - return -1; - } - - creat("/tmp/.switch_jffs2", 0600); - ret = mount(v->blk, "/rom/overlay", "jffs2", MS_NOATIME, NULL); - unlink("/tmp/.switch_jffs2"); - if (ret) { - fprintf(stderr, "failed - mount -t jffs2 %s /rom/overlay: %s\n", v->blk, strerror(errno)); - return -1; - } - - if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) { - fprintf(stderr, "failed - mount -o remount,ro none: %s\n", strerror(errno)); - return -1; - } - - system("cp -a /tmp/root/* /rom/overlay"); - - if (pivot("/rom", "/mnt")) { - fprintf(stderr, "failed - pivot /rom /mnt: %s\n", strerror(errno)); - return -1; - } - - if (mount_move("/mnt", "/tmp/root", "")) { - fprintf(stderr, "failed - mount -o move /mnt /tmp/root %s\n", strerror(errno)); - return -1; - } - - return fopivot("/overlay", "/rom"); -} - -int -handle_whiteout(const char *dir) -{ - struct stat s; - char link[256]; - ssize_t sz; - struct dirent **namelist; - int n; - - n = scandir(dir, &namelist, NULL, NULL); - - if (n < 1) - return -1; - - while (n--) { - char file[256]; - - snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); - if (!lstat(file, &s) && S_ISLNK(s.st_mode)) { - sz = readlink(file, link, sizeof(link) - 1); - if (sz > 0) { - char *orig; - - link[sz] = '\0'; - orig = strstr(&file[1], "/"); - if (orig && !strcmp(link, "(overlay-whiteout)")) - unlink(orig); - } - } - free(namelist[n]); - } - free(namelist); - - return 0; -} - -static int -ask_user(int argc, char **argv) -{ - if ((argc < 2) || strcmp(argv[1], "-y")) { - fprintf(stderr, "This will erase all settings and remove any installed packages. Are you sure? [N/y]\n"); - if (getchar() != 'y') - return -1; - } - return 0; - -} - -static int -handle_rmdir(const char *dir) -{ - struct stat s; - struct dirent **namelist; - int n; - - n = scandir(dir, &namelist, NULL, NULL); - - if (n < 1) - return -1; - - while (n--) { - char file[256]; - - snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); - if (!lstat(file, &s) && !S_ISDIR(s.st_mode)) - unlink(file); - free(namelist[n]); - } - free(namelist); - - rmdir(dir); - - return 0; -} - -static int -jffs2_reset(int argc, char **argv) -{ - struct volume *v; - char *mp; - - if (ask_user(argc, argv)) - return -1; - - if (find_filesystem("overlay")) { - fprintf(stderr, "overlayfs not found\n"); - return -1; - } - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "no rootfs_data was found\n"); - return -1; - } - - mp = find_mount_point(v->blk, "jffs2"); - if (mp) { - fprintf(stderr, "%s is mounted as %s, only erasing files\n", v->blk, mp); - foreachdir(mp, handle_rmdir); - mount(mp, "/", NULL, MS_REMOUNT, 0); - } else { - fprintf(stderr, "%s is not mounted, erasing it\n", v->blk); - volume_erase_all(v); - } - - return 0; -} - -static int -jffs2_mark(int argc, char **argv) -{ - __u32 deadc0de = __cpu_to_be32(0xdeadc0de); - struct volume *v; - size_t sz; - int fd; - - if (ask_user(argc, argv)) - return -1; - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "no rootfs_data was found\n"); - return -1; - } - - fd = open(v->blk, O_WRONLY); - fprintf(stderr, "%s - marking with deadc0de\n", v->blk); - if (!fd) { - fprintf(stderr, "opening %s failed\n", v->blk); - return -1; - } - - sz = write(fd, &deadc0de, sizeof(deadc0de)); - close(fd); - - if (sz != 1) { - fprintf(stderr, "writing %s failed: %s\n", v->blk, strerror(errno)); - return -1; - } - - return 0; -} - -int -jffs2_switch(int argc, char **argv) -{ - struct volume *v; - char *mp; - int ret = -1; - - if (find_overlay_mount("overlayfs:/tmp/root")) - return -1; - - if (find_filesystem("overlay")) { - fprintf(stderr, "overlayfs not found\n"); - return ret; - } - - v = volume_find("rootfs_data"); - mp = find_mount_point(v->blk, NULL); - if (mp) { - fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); - return -1; - } - - switch (volume_identify(v)) { - case FS_NONE: - fprintf(stderr, "no jffs2 marker found\n"); - /* fall through */ - - case FS_DEADCODE: - ret = switch2jffs(); - if (!ret) { - fprintf(stderr, "doing fo cleanup\n"); - umount2("/tmp/root", MNT_DETACH); - foreachdir("/overlay/", handle_whiteout); - } - break; - - case FS_JFFS2: - ret = jffs2_mount(); - if (ret) - break; - if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { - fprintf(stderr, "switching to jffs2 failed\n"); - ret = -1; - } - break; - } - - return ret; -} - -static int overlay_mount_fs(void) -{ - struct volume *v; - - if (mkdir("/tmp/overlay", 0755)) { - fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); - return -1; - } - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "rootfs_data does not exist\n"); - return -1; - } - - if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { - fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", - v->blk, strerror(errno)); - return -1; - } - - volume_init(v); - - return -1; -} - -static int overlay_mount(void) -{ - struct volume *v = volume_find("rootfs_data");; - char *mp; - - if (!v) - return -1; - - mp = find_mount_point(v->blk, NULL); - if (mp) { - fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); - return -1; - } - - overlay_mount_fs(); - - extroot_prefix = "/tmp/overlay"; - if (!backend_mount("extroot")) { - fprintf(stderr, "fs-state: switched to extroot\n"); - return 0; - } - - fprintf(stderr, "switching to jffs2\n"); - if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { - fprintf(stderr, "switching to jffs2 failed - fallback to ramoverlay\n"); - return ramoverlay(); - } - - return -1; -} - -static struct backend_handler jffs2_handlers[] = { -{ - .name = "jffs2reset", - .cli = jffs2_reset, -}, { - .name = "jffs2mark", - .cli = jffs2_mark, -}}; - -static struct backend overlay_backend = { - .name = "overlay", - .num_handlers = ARRAY_SIZE(jffs2_handlers), - .handlers = jffs2_handlers, - .mount = overlay_mount, -}; -BACKEND(overlay_backend); diff --git a/backend/overlay.c b/backend/overlay.c deleted file mode 100644 index 7cd308f..0000000 --- a/backend/overlay.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../fs-state.h" -#include "../driver/volume.h" - -#define SWITCH_JFFS2 "/tmp/.switch_jffs2" - -void -foreachdir(const char *dir, int (*cb)(const char*)) -{ - char globdir[256]; - glob_t gl; - int j; - - if (dir[strlen(dir) - 1] == '/') - snprintf(globdir, 256, "%s*", dir); - else - snprintf(globdir, 256, "%s/*", dir); - - if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl)) - for (j = 0; j < gl.gl_pathc; j++) - foreachdir(gl.gl_pathv[j], cb); - - cb(dir); -} - -static int -overlay_mount(struct volume *v, char *fs) -{ - if (mkdir("/tmp/overlay", 0755)) { - fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); - return -1; - } - - if (mount(v->blk, "/tmp/overlay", fs, MS_NOATIME, NULL)) { - fprintf(stderr, "failed to mount -t %s %s /tmp/overlay: %s\n", fs, v->blk, strerror(errno)); - return -1; - } - - return volume_init(v); -} - -static int -switch2jffs(struct volume *v) -{ - struct stat s; - int ret; - - if (!stat(SWITCH_JFFS2, &s)) { - fprintf(stderr, "jffs2 switch already running\n"); - return -1; - } - - creat("/tmp/.switch_jffs2", 0600); - ret = mount(v->blk, "/rom/overlay", "jffs2", MS_NOATIME, NULL); - unlink("/tmp/.switch_jffs2"); - if (ret) { - fprintf(stderr, "failed - mount -t jffs2 %s /rom/overlay: %s\n", v->blk, strerror(errno)); - return -1; - } - - if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) { - fprintf(stderr, "failed - mount -o remount,ro none: %s\n", strerror(errno)); - return -1; - } - - system("cp -a /tmp/root/* /rom/overlay"); - - if (pivot("/rom", "/mnt")) { - fprintf(stderr, "failed - pivot /rom /mnt: %s\n", strerror(errno)); - return -1; - } - - if (mount_move("/mnt", "/tmp/root", "")) { - fprintf(stderr, "failed - mount -o move /mnt /tmp/root %s\n", strerror(errno)); - return -1; - } - - return fopivot("/overlay", "/rom"); -} - -int -handle_whiteout(const char *dir) -{ - struct stat s; - char link[256]; - ssize_t sz; - struct dirent **namelist; - int n; - - n = scandir(dir, &namelist, NULL, NULL); - - if (n < 1) - return -1; - - while (n--) { - char file[256]; - - snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); - if (!lstat(file, &s) && S_ISLNK(s.st_mode)) { - sz = readlink(file, link, sizeof(link) - 1); - if (sz > 0) { - char *orig; - - link[sz] = '\0'; - orig = strstr(&file[1], "/"); - if (orig && !strcmp(link, "(overlay-whiteout)")) - unlink(orig); - } - } - free(namelist[n]); - } - free(namelist); - - return 0; -} - -static int -ask_user(int argc, char **argv) -{ - if ((argc < 2) || strcmp(argv[1], "-y")) { - fprintf(stderr, "This will erase all settings and remove any installed packages. Are you sure? [N/y]\n"); - if (getchar() != 'y') - return -1; - } - return 0; - -} - -int -jffs2_switch(int argc, char **argv) -{ - struct volume *v; - char *mp; - int ret = -1; - - if (find_overlay_mount("overlayfs:/tmp/root")) - return -1; - - if (find_filesystem("overlay")) { - fprintf(stderr, "overlayfs not found\n"); - return ret; - } - - v = volume_find("rootfs_data"); - mp = find_mount_point(v->blk, NULL); - if (mp) { - fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); - return -1; - } - - switch (volume_identify(v)) { - case FS_NONE: - fprintf(stderr, "no jffs2 marker found\n"); - /* fall through */ - - case FS_DEADCODE: - ret = switch2jffs(); - if (!ret) { - fprintf(stderr, "doing fo cleanup\n"); - umount2("/tmp/root", MNT_DETACH); - foreachdir("/overlay/", handle_whiteout); - } - break; - - case FS_JFFS2: - ret = overlay_mount(v, "jffs2"); - if (ret) - break; - if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { - fprintf(stderr, "switching to jffs2 failed\n"); - ret = -1; - } - break; - } - - return ret; -} - -static int overlay_mount_fs(void) -{ - struct volume *v; - - if (mkdir("/tmp/overlay", 0755)) { - fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); - return -1; - } - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "rootfs_data does not exist\n"); - return -1; - } - - if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { - fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", - v->blk, strerror(errno)); - return -1; - } - - volume_init(v); - - return -1; -} - -static int overlay_mount(void) -{ - struct volume *v = volume_find("rootfs_data");; - char *mp; - - if (!v) - return -1; - - mp = find_mount_point(v->blk, NULL); - if (mp) { - fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); - return -1; - } - - overlay_mount_fs(); - - extroot_prefix = "/tmp/overlay"; - if (!backend_mount("extroot")) { - fprintf(stderr, "fs-state: switched to extroot\n"); - return 0; - } - - fprintf(stderr, "switching to jffs2\n"); - if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { - fprintf(stderr, "switching to jffs2 failed - fallback to ramoverlay\n"); - return ramoverlay(); - } - - return -1; -} - -static struct backend overlay_backend = { - .name = "overlay", - .mount = overlay_mount, -}; -BACKEND(overlay_backend); diff --git a/backend/snapshot.c b/backend/snapshot.c deleted file mode 100644 index 5873f5c..0000000 --- a/backend/snapshot.c +++ /dev/null @@ -1,602 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "../fs-state.h" -#include "../driver/volume.h" - -#define PATH_MAX 256 -#define OWRT 0x4f575254 -#define DATA 0x44415441 -#define CONF 0x434f4e46 - -struct file_header { - uint32_t magic; - uint32_t type; - uint32_t seq; - uint32_t length; - uint32_t md5[4]; -}; - -static inline int -is_config(struct file_header *h) -{ - return ((h->magic == OWRT) && (h->type == CONF)); -} - -static inline int -valid_file_size(int fs) -{ - if ((fs > 8 * 1024 * 1204) || (fs <= 0)) - return -1; - - return 0; -} - -static void -hdr_to_be32(struct file_header *hdr) -{ - uint32_t *h = (uint32_t *) hdr; - int i; - - for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++) - h[i] = cpu_to_be32(h[i]); -} - -static void -be32_to_hdr(struct file_header *hdr) -{ - uint32_t *h = (uint32_t *) hdr; - int i; - - for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++) - h[i] = be32_to_cpu(h[i]); -} - -static int -pad_file_size(struct volume *v, int size) -{ - int mod; - - size += sizeof(struct file_header); - mod = size % v->block_size; - if (mod) { - size -= mod; - size += v->block_size; - } - - return size; -} - -static int -verify_file_hash(char *file, uint32_t *hash) -{ - uint32_t md5[4]; - - if (md5sum(file, md5)) { - fprintf(stderr, "failed to generate md5 sum\n"); - return -1; - } - - if (memcmp(md5, hash, sizeof(md5))) { - fprintf(stderr, "failed to verify hash of %s.\n", file); - return -1; - } - - return 0; -} - -static int -snapshot_next_free(struct volume *v, uint32_t *seq) -{ - struct file_header hdr = { 0 }; - int block = 0; - - *seq = rand(); - - do { - if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { - fprintf(stderr, "scanning for next free block failed\n"); - return 0; - } - - be32_to_hdr(&hdr); - - if (hdr.magic != OWRT) - break; - - if (hdr.type == DATA && !valid_file_size(hdr.length)) { - if (*seq + 1 != hdr.seq && block) - return block; - *seq = hdr.seq; - block += pad_file_size(v, hdr.length) / v->block_size; - } - } while (hdr.type == DATA); - - return block; -} - -static int -config_find(struct volume *v, struct file_header *conf, struct file_header *sentinel) -{ - uint32_t seq; - int i, next = snapshot_next_free(v, &seq); - - conf->magic = sentinel->magic = 0; - - if (!volume_read(v, conf, next, sizeof(*conf))) - be32_to_hdr(conf); - - for (i = (v->size / v->block_size) - 1; i > 0; i--) { - if (volume_read(v, sentinel, i * v->block_size, sizeof(*sentinel))) { - fprintf(stderr, "failed to read header\n"); - return -1; - } - be32_to_hdr(sentinel); - - if (sentinel->magic == OWRT && sentinel->type == CONF && !valid_file_size(sentinel->length)) { - if (next == i) - return -1; - return i; - } - } - - return -1; -} - -static int -snapshot_info(void) -{ - struct volume *v = volume_find("rootfs_data"); - struct file_header hdr = { 0 }, conf; - int block = 0; - - if (!v) - return -1; - - fprintf(stderr, "sectors:\t%llu, block_size:\t%dK\n", v->size / v->block_size, v->block_size / 1024); - do { - if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { - fprintf(stderr, "scanning for next free block failed\n"); - return 0; - } - - be32_to_hdr(&hdr); - - if (hdr.magic != OWRT) - break; - - if (hdr.type == DATA) - fprintf(stderr, "block %d:\tsnapshot entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); - else if (hdr.type == CONF) - fprintf(stderr, "block %d:\tvolatile entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); - - if (hdr.type == DATA && !valid_file_size(hdr.length)) - block += pad_file_size(v, hdr.length) / v->block_size; - } while (hdr.type == DATA); - block = config_find(v, &conf, &hdr); - if (block > 0) - fprintf(stderr, "block %d:\tsentinel entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); - - return 0; -} - -static int -snapshot_write_file(struct volume *v, int block, char *file, uint32_t seq, uint32_t type) -{ - uint32_t md5[4] = { 0 }; - struct file_header hdr; - struct stat s; - char buffer[256]; - int in = 0, len, offset; - int ret = -1; - - if (stat(file, &s) || md5sum(file, md5)) { - fprintf(stderr, "stat failed on %s\n", file); - goto out; - } - - if ((block * v->block_size) + pad_file_size(v, s.st_size) > v->size) { - fprintf(stderr, "upgrade is too big for the flash\n"); - goto out; - } - volume_erase(v, block * v->block_size, pad_file_size(v, s.st_size)); - volume_erase(v, block * v->block_size + pad_file_size(v, s.st_size), v->block_size); - - hdr.length = s.st_size; - hdr.magic = OWRT; - hdr.type = type; - hdr.seq = seq; - memcpy(hdr.md5, md5, sizeof(md5)); - hdr_to_be32(&hdr); - - if (volume_write(v, &hdr, block * v->block_size, sizeof(struct file_header))) { - fprintf(stderr, "failed to write header\n"); - goto out; - } - - in = open(file, O_RDONLY); - if (in < 1) { - fprintf(stderr, "failed to open %s\n", file); - goto out; - } - - offset = (block * v->block_size) + sizeof(struct file_header); - - while ((len = read(in, buffer, sizeof(buffer))) > 0) { - if (volume_write(v, buffer, offset, len) < 0) - goto out; - offset += len; - } - - ret = 0; - -out: - if (in > 0) - close(in); - - return ret; -} - -static int -snapshot_read_file(struct volume *v, int block, char *file, uint32_t type) -{ - struct file_header hdr; - char buffer[256]; - int out, offset = 0; - - if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { - fprintf(stderr, "failed to read header\n"); - return -1; - } - be32_to_hdr(&hdr); - - if (hdr.magic != OWRT) - return -1; - - if (hdr.type != type) - return -1; - - if (valid_file_size(hdr.length)) - return -1; - - out = open(file, O_WRONLY | O_CREAT, 0700); - if (!out) { - fprintf(stderr, "failed to open %s\n", file); - return -1; - } - - while (hdr.length > 0) { - int len = sizeof(buffer); - - if (hdr.length < len) - len = hdr.length; - - if ((volume_read(v, buffer, offset, len) != len) || (write(out, buffer, len) != len)) - return -1; - - offset += len; - hdr.length -= len; - } - - close(out); - - if (verify_file_hash(file, hdr.md5)) { - fprintf(stderr, "md5 verification failed\n"); - unlink(file); - return 0; - } - - block += pad_file_size(v, hdr.length) / v->block_size; - - return block; -} - -static int -sentinel_write(struct volume *v, uint32_t _seq) -{ - int ret, block; - struct stat s; - uint32_t seq; - - if (stat("/tmp/config.tar.gz", &s)) { - fprintf(stderr, "failed to stat /tmp/config.tar.gz\n"); - return -1; - } - - snapshot_next_free(v, &seq); - if (_seq) - seq = _seq; - block = v->size / v->block_size; - block -= pad_file_size(v, s.st_size) / v->block_size; - if (block < 0) - block = 0; - - ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF); - if (ret) - fprintf(stderr, "failed to write sentinel\n"); - else - fprintf(stderr, "wrote /tmp/config.tar.gz sentinel\n"); - return ret; -} - -static int -volatile_write(struct volume *v, uint32_t _seq) -{ - int block, ret; - uint32_t seq; - - block = snapshot_next_free(v, &seq); - if (_seq) - seq = _seq; - if (block < 0) - block = 0; - - ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF); - if (ret) - fprintf(stderr, "failed to write /tmp/config.tar.gz\n"); - else - fprintf(stderr, "wrote /tmp/config.tar.gz\n"); - return ret; -} - -static int -config_write(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - int ret; - - if (!v) - return -1; - - ret = volatile_write(v, 0); - if (!ret) - ret = sentinel_write(v, 0); - - return ret; -} - -static int -config_read(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - struct file_header conf, sentinel; - int next, block, ret = 0; - uint32_t seq; - - if (!v) - return -1; - - block = config_find(v, &conf, &sentinel); - next = snapshot_next_free(v, &seq); - if (is_config(&conf) && conf.seq == seq) - block = next; - else if (!is_config(&sentinel) || sentinel.seq != seq) - return -1; - - unlink("/tmp/config.tar.gz"); - ret = snapshot_read_file(v, block, "/tmp/config.tar.gz", CONF); - - if (ret < 1) - fprintf(stderr, "failed to read /tmp/config.tar.gz\n"); - - return ret; -} - -static int -snapshot_write(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data"); - int block, ret; - uint32_t seq; - - if (!v) - return -1; - - block = snapshot_next_free(v, &seq); - if (block < 0) - block = 0; - - ret = snapshot_write_file(v, block, "/tmp/snapshot.tar.gz", seq + 1, DATA); - if (ret) - fprintf(stderr, "failed to write /tmp/snapshot.tar.gz\n"); - else - fprintf(stderr, "wrote /tmp/snapshot.tar.gz\n"); - - return ret; -} - -static int -snapshot_mark(int argc, char **argv) -{ - __be32 owrt = cpu_to_be32(OWRT); - struct volume *v; - size_t sz; - int fd; - - fprintf(stderr, "This will remove all snapshot data stored on the system. Are you sure? [N/y]\n"); - if (getchar() != 'y') - return -1; - - v = volume_find("rootfs_data"); - if (!v) { - fprintf(stderr, "no rootfs_data was found\n"); - return -1; - } - - fd = open(v->blk, O_WRONLY); - fprintf(stderr, "%s - marking with 0x%08x\n", v->blk, owrt); - if (fd < 0) { - fprintf(stderr, "opening %s failed\n", v->blk); - return -1; - } - - sz = write(fd, &owrt, sizeof(owrt)); - close(fd); - - if (sz != 1) { - fprintf(stderr, "writing %s failed: %s\n", v->blk, strerror(errno)); - return -1; - } - - return 0; -} - -static int -snapshot_read(int argc, char **argv) -{ - struct volume *v = volume_find("rootfs_data");; - int block = 0, ret = 0; - char file[64]; - - if (!v) - return -1; - - if (argc > 1) { - block = atoi(argv[1]); - if (block >= (v->size / v->block_size)) { - fprintf(stderr, "invalid block %d > %llu\n", block, v->size / v->block_size); - goto out; - } - snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block); - - ret = snapshot_read_file(v, block, file, DATA); - goto out; - } - - do { - snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block); - block = snapshot_read_file(v, block, file, DATA); - } while (block > 0); - -out: - return ret; -} - -static int -snapshot_sync(void) -{ - struct volume *v = volume_find("rootfs_data"); - struct file_header sentinel, conf; - int next, block = 0; - uint32_t seq; - - if (!v) - return -1; - - next = snapshot_next_free(v, &seq); - block = config_find(v, &conf, &sentinel); - if (is_config(&conf) && conf.seq != seq) { - conf.magic = 0; - volume_erase(v, next * v->block_size, 2 * v->block_size); - } - - if (is_config(&sentinel) && (sentinel.seq != seq)) { - sentinel.magic = 0; - volume_erase(v, block * v->block_size, v->block_size); - } - - if (!is_config(&conf) && !is_config(&sentinel)) { - // fprintf(stderr, "no config found\n"); - } else if (((is_config(&conf) && is_config(&sentinel)) && - (memcmp(conf.md5, sentinel.md5, sizeof(conf.md5)) || (conf.seq != sentinel.seq))) || - (is_config(&conf) && !is_config(&sentinel))) { - uint32_t seq; - int next = snapshot_next_free(v, &seq); - int ret = snapshot_read_file(v, next, "/tmp/config.tar.gz", CONF); - if (ret > 0) { - if (sentinel_write(v, conf.seq)) - fprintf(stderr, "failed to write sentinel data"); - } - } else if (!is_config(&conf) && is_config(&sentinel) && next) { - int ret = snapshot_read_file(v, block, "/tmp/config.tar.gz", CONF); - if (ret > 0) - if (volatile_write(v, sentinel.seq)) - fprintf(stderr, "failed to write sentinel data"); - } else - fprintf(stderr, "config in sync\n"); - - unlink("/tmp/config.tar.gz"); - - return 0; -} - -static int -_ramoverlay(char *rom, char *overlay) -{ - mount("tmpfs", overlay, "tmpfs", MS_NOATIME, "mode=0755"); - return fopivot(overlay, rom); -} - -static int -snapshot_mount(void) -{ - snapshot_sync(); - setenv("SNAPSHOT", "magic", 1); - _ramoverlay("/rom", "/overlay"); - system("/sbin/snapshot unpack"); - foreachdir("/overlay/", handle_whiteout); - mkdir("/volatile", 0700); - _ramoverlay("/rom", "/volatile"); - mount_move("/rom/volatile", "/volatile", ""); - mount_move("/rom/rom", "/rom", ""); - system("/sbin/snapshot config_unpack"); - foreachdir("/volatile/", handle_whiteout); - unsetenv("SNAPSHOT"); - return -1; -} - -static struct backend_handler snapshot_handlers[] = { -{ - .name = "config_read", - .cli = config_read, -}, { - .name = "config_write", - .cli = config_write, -}, { - .name = "read", - .cli = snapshot_read, -}, { - .name = "write", - .cli = snapshot_write, -}, { - .name = "mark", - .cli = snapshot_mark, -}}; - -static struct backend snapshot_backend = { - .name = "snapshot", - .num_handlers = ARRAY_SIZE(snapshot_handlers), - .handlers = snapshot_handlers, - .mount = snapshot_mount, - .info = snapshot_info, -}; -BACKEND(snapshot_backend); diff --git a/driver/mtd.c b/driver/mtd.c deleted file mode 100644 index a0005d7..0000000 --- a/driver/mtd.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../fs-state.h" - -#include "volume.h" - -#define PATH_MAX 256 - -struct mtd_priv { - int fd; - int idx; - char *chr; -}; - -static struct driver mtd_driver; - -static int mtd_open(const char *mtd, int block) -{ - FILE *fp; - char dev[PATH_MAX]; - int i, ret, flags = O_RDWR | O_SYNC; - - if ((fp = fopen("/proc/mtd", "r"))) { - while (fgets(dev, sizeof(dev), fp)) { - if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) { - snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i); - ret = open(dev, flags); - if (ret < 0) { - snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i); - ret = open(dev, flags); - } - fclose(fp); - return ret; - } - } - fclose(fp); - } - - return open(mtd, flags); -} - -static void mtd_volume_close(struct volume *v) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - - if (!p->fd) - return; - - close(p->fd); - p->fd = 0; -} - -static int mtd_volume_load(struct volume *v) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - struct mtd_info_user mtdInfo; - struct erase_info_user mtdLockInfo; - - if (p->fd) - return 0; - - if (!p->chr) - return -1; - - p->fd = mtd_open(p->chr, 0); - if (p->fd < 0) { - p->fd = 0; - fprintf(stderr, "Could not open mtd device: %s\n", p->chr); - return -1; - } - - if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) { - mtd_volume_close(v); - fprintf(stderr, "Could not get MTD device info from %s\n", p->chr); - return -1; - } - - v->size = mtdInfo.size; - v->block_size = mtdInfo.erasesize; - switch (mtdInfo.type) { - case MTD_NORFLASH: - v->type = NORFLASH; - break; - case MTD_NANDFLASH: - v->type = NANDFLASH; - break; - case MTD_UBIVOLUME: - v->type = UBIVOLUME; - break; - default: - v->type = UNKNOWN_TYPE; - break; - } - - mtdLockInfo.start = 0; - mtdLockInfo.length = v->size; - ioctl(p->fd, MEMUNLOCK, &mtdLockInfo); - - return 0; -} - -static char* mtd_find_index(char *name) -{ - FILE *fp = fopen("/proc/mtd", "r"); - static char line[256]; - char *index = NULL; - - if(!fp) - return index; - - while (!index && fgets(line, sizeof(line), fp)) { - if (strstr(line, name)) { - char *eol = strstr(line, ":"); - - if (!eol) - continue; - - *eol = '\0'; - index = &line[3]; - } - } - - fclose(fp); - - return index; -} - -static int mtd_volume_find(struct volume *v, char *name) -{ - char *idx = mtd_find_index(name); - struct mtd_priv *p; - char buffer[32]; - - if (!idx) - return -1; - - p = calloc(1, sizeof(struct mtd_priv)); - if (!p) - return -1; - - v->priv = p; - v->name = strdup(name); - v->drv = &mtd_driver; - p->idx = atoi(idx); - - snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx); - v->blk = strdup(buffer); - - snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx); - p->chr = strdup(buffer); - - return 0; -} - -static int mtd_volume_identify(struct volume *v) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - __u32 deadc0de; - __u16 jffs2; - size_t sz; - - if (mtd_volume_load(v)) { - fprintf(stderr, "reading %s failed\n", v->name); - return -1; - } - - sz = read(p->fd, &deadc0de, sizeof(deadc0de)); - - if (sz != sizeof(deadc0de)) { - fprintf(stderr, "reading %s failed: %s\n", v->name, strerror(errno)); - return -1; - } - - if (deadc0de == 0x4f575254) - return FS_SNAPSHOT; - - deadc0de = __be32_to_cpu(deadc0de); - if (deadc0de == 0xdeadc0de) { - fprintf(stderr, "jffs2 is not ready - marker found\n"); - return FS_DEADCODE; - } - - jffs2 = __be16_to_cpu(deadc0de >> 16); - if (jffs2 == 0x1985) { - fprintf(stderr, "jffs2 is ready\n"); - return FS_JFFS2; - } - - if (v->type == UBIVOLUME && deadc0de == 0xffffffff) { - fprintf(stderr, "jffs2 is ready\n"); - return FS_JFFS2; - } - - fprintf(stderr, "No jffs2 marker was found\n"); - - return FS_NONE; -} - -static int mtd_volume_erase(struct volume *v, int offset, int len) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - struct erase_info_user eiu; - int first_block, num_blocks; - - if (mtd_volume_load(v)) - return -1; - - if (offset % v->block_size || len % v->block_size) { - fprintf(stderr, "mtd erase needs to be block aligned\n"); - return -1; - } - - first_block = offset / v->block_size; - num_blocks = len / v->block_size; - eiu.length = v->block_size; - - for (eiu.start = first_block * v->block_size; - eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size; - eiu.start += v->block_size) { - fprintf(stderr, "erasing %x %x\n", eiu.start, v->block_size); - ioctl(p->fd, MEMUNLOCK, &eiu); - if (ioctl(p->fd, MEMERASE, &eiu)) - fprintf(stderr, "Failed to erase block at 0x%x\n", eiu.start); - } - - mtd_volume_close(v); - - return 0; -} - -static int mtd_volume_erase_all(struct volume *v) -{ - mtd_volume_erase(v, 0, v->size); - mtd_volume_close(v); - - return 0; -} - -static int mtd_volume_init(struct volume *v) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - struct mtd_info_user mtdinfo; - int ret; - - if (mtd_volume_load(v)) - return -1; - - ret = ioctl(p->fd, MEMGETINFO, &mtdinfo); - if (ret) { - fprintf(stderr, "ioctl(%d, MEMGETINFO) failed: %s\n", p->fd, strerror(errno)); - } else { - struct erase_info_user mtdlock; - - mtdlock.start = 0; - mtdlock.length = mtdinfo.size; - ioctl(p->fd, MEMUNLOCK, &mtdlock); - } - - return ret; -} - -static int mtd_volume_read(struct volume *v, void *buf, int offset, int length) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - - if (mtd_volume_load(v)) - return -1; - - if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) { - fprintf(stderr, "lseek/read failed\n"); - return -1; - } - - if (read(p->fd, buf, length) == -1) { - fprintf(stderr, "read failed\n"); - return -1; - } - - return 0; -} - -static int mtd_volume_write(struct volume *v, void *buf, int offset, int length) -{ - struct mtd_priv *p = (struct mtd_priv*) v->priv; - - if (mtd_volume_load(v)) - return -1; - - if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) { - fprintf(stderr, "lseek/write failed at offset %d\n", offset); - perror("lseek"); - return -1; - } - - if (write(p->fd, buf, length) == -1) { - fprintf(stderr, "write failed\n"); - return -1; - } - - return 0; -} - -static struct driver mtd_driver = { - .name = "mtd", - .find = mtd_volume_find, - .init = mtd_volume_init, - .erase = mtd_volume_erase, - .erase_all = mtd_volume_erase_all, - .read = mtd_volume_read, - .write = mtd_volume_write, - .identify = mtd_volume_identify, -}; -DRIVER(mtd_driver); diff --git a/driver/volume.c b/driver/volume.c deleted file mode 100644 index 4dc0a8e..0000000 --- a/driver/volume.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include "../fs-state.h" -#include "volume.h" - -enum { - FLASH_NOR, - FLASH_NAND, -}; - -static LIST_HEAD(drivers); - -void -volume_register_driver(struct driver *d) -{ - list_add(&d->list, &drivers); -} - -struct volume* volume_find(char *name) -{ - struct volume *v = malloc(sizeof(struct volume)); - struct driver *d; - - if (!v) - return NULL; - - list_for_each_entry(d, &drivers, list) { - memset(v, 0, sizeof(struct volume)); - - if (d->find && !d->find(v, name)) - return v; - } - - free(v); - - return NULL; -} diff --git a/driver/volume.h b/driver/volume.h deleted file mode 100644 index 4fa5641..0000000 --- a/driver/volume.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#ifndef _VOLUME_H__ -#define _VOLUME_H__ - -#include - -struct volume; - -typedef int (*volume_probe_t)(void); -typedef int (*volume_init_t)(struct volume *v); -typedef void (*volume_stop_t)(struct volume *v); -typedef int (*volume_find_t)(struct volume *v, char *name); -typedef int (*volume_identify_t)(struct volume *v); -typedef int (*volume_read_t)(struct volume *v, void *buf, int offset, int length); -typedef int (*volume_write_t)(struct volume *v, void *buf, int offset, int length); -typedef int (*volume_erase_t)(struct volume *v, int start, int len); -typedef int (*volume_erase_all_t)(struct volume *v); - -struct driver { - struct list_head list; - - char *name; - volume_probe_t probe; - volume_init_t init; - volume_stop_t stop; - volume_find_t find; - volume_identify_t identify; - volume_read_t read; - volume_write_t write; - volume_erase_t erase; - volume_erase_all_t erase_all; -}; - -enum { - UNKNOWN_TYPE, - NANDFLASH, - NORFLASH, - UBIVOLUME, -}; - -struct volume { - struct driver *drv; - void *priv; - char *name; - char *blk; - - __u64 size; - __u32 block_size; - int type; -}; - -extern struct volume* volume_find(char *name); -extern void volume_register_driver(struct driver *drv); - -static inline int volume_init(struct volume *v) -{ - if (v && v->drv->init) - return v->drv->init(v); - return -1; -} - -static inline int volume_identify(struct volume *v) -{ - if (v && v->drv->identify) - return v->drv->identify(v); - return -1; -} - -static inline int volume_erase(struct volume *v, int offset, int len) -{ - if (v && v->drv->erase) - return v->drv->erase(v, offset, len); - return -1; -} - -static inline int volume_erase_all(struct volume *v) -{ - if (v && v->drv->erase_all) - return v->drv->erase_all(v); - return -1; -} - -static inline int volume_read(struct volume *v, void *buf, int offset, int length) -{ - if (v && v->drv->read) - return v->drv->read(v, buf, offset, length); - return -1; -} - -static inline int volume_write(struct volume *v, void *buf, int offset, int length) -{ - if (v && v->drv->write) - return v->drv->write(v, buf, offset, length); - return -1; -} - -#define DRIVER(x) \ - static void __attribute__((constructor)) \ - drv_register_##x(void) { \ - volume_register_driver(&x); \ - } - -#endif diff --git a/lib/find.c b/lib/find.c deleted file mode 100644 index 35e37e7..0000000 --- a/lib/find.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include "../fs-state.h" - -int -find_overlay_mount(char *overlay) -{ - FILE *fp = fopen("/proc/mounts", "r"); - static char line[256]; - int ret = -1; - - if(!fp) - return ret; - - while (ret && fgets(line, sizeof(line), fp)) - if (!strncmp(line, overlay, strlen(overlay))) - ret = 0; - - fclose(fp); - - return ret; -} - -char* -find_mount(char *mp) -{ - FILE *fp = fopen("/proc/mounts", "r"); - static char line[256]; - char *point = NULL; - - if(!fp) - return NULL; - - while (fgets(line, sizeof(line), fp)) { - char *s, *t = strstr(line, " "); - - if (!t) { - fclose(fp); - return NULL; - } - t++; - s = strstr(t, " "); - if (!s) { - fclose(fp); - return NULL; - } - *s = '\0'; - - if (!strcmp(t, mp)) { - fclose(fp); - return t; - } - } - - fclose(fp); - - return point; -} - -char* -find_mount_point(char *block, char *fs) -{ - FILE *fp = fopen("/proc/mounts", "r"); - static char line[256]; - int len = strlen(block); - char *point = NULL; - - if(!fp) - return NULL; - - while (fgets(line, sizeof(line), fp)) { - if (!strncmp(line, block, len)) { - char *p = &line[len + 1]; - char *t = strstr(p, " "); - - if (!t) { - fclose(fp); - return NULL; - } - - *t = '\0'; - t++; - - if (fs && strncmp(t, fs, strlen(fs))) { - fclose(fp); - fprintf(stderr, "block is mounted with wrong fs\n"); - return NULL; - } - point = p; - - break; - } - } - - fclose(fp); - - return point; -} - -int -find_filesystem(char *fs) -{ - FILE *fp = fopen("/proc/filesystems", "r"); - static char line[256]; - int ret = -1; - - if (!fp) { - fprintf(stderr, "opening /proc/filesystems failed: %s\n", strerror(errno)); - goto out; - } - - while (ret && fgets(line, sizeof(line), fp)) - if (strstr(line, fs)) - ret = 0; - - fclose(fp); - -out: - return ret; -} diff --git a/lib/mount.c b/lib/mount.c deleted file mode 100644 index e7b57f0..0000000 --- a/lib/mount.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2014 John Crispin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation - * - * This program 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 General Public License for more details. - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "../fs-state.h" - -/* this is a raw syscall - man 2 pivot_root */ -extern int pivot_root(const char *new_root, const char *put_old); - -int -mount_move(char *oldroot, char *newroot, char *dir) -{ -#ifndef MS_MOVE -#define MS_MOVE (1 << 13) -#endif - struct stat s; - char olddir[64]; - char newdir[64]; - int ret; - - snprintf(olddir, sizeof(olddir), "%s%s", oldroot, dir); - snprintf(newdir, sizeof(newdir), "%s%s", newroot, dir); - - if (stat(olddir, &s) || !S_ISDIR(s.st_mode)) - return -1; - - if (stat(newdir, &s) || !S_ISDIR(s.st_mode)) - return -1; - - ret = mount(olddir, newdir, NULL, MS_NOATIME | MS_MOVE, NULL); - -/* if (ret) - fprintf(stderr, "failed %s %s: %s\n", olddir, newdir, strerror(errno));*/ - - return ret; -} - -int -pivot(char *new, char *old) -{ - char pivotdir[64]; - int ret; - - if (mount_move("", new, "/proc")) - return -1; - - snprintf(pivotdir, sizeof(pivotdir), "%s%s", new, old); - - ret = pivot_root(new, pivotdir); - - if (ret < 0) { - fprintf(stderr, "pivot_root failed %s %s: %s\n", new, pivotdir, strerror(errno)); - return -1; - } - - mount_move(old, "", "/dev"); - mount_move(old, "", "/tmp"); - mount_move(old, "", "/sys"); - mount_move(old, "", "/overlay"); - - return 0; -} - -int -fopivot(char *rw_root, char *ro_root) -{ - char overlay[64], lowerdir[64]; - - if (find_filesystem("overlay")) { - fprintf(stderr, "BUG: no suitable fs found\n"); - return -1; - } - - snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root); - snprintf(lowerdir, sizeof(lowerdir), "lowerdir=/,upperdir=%s", rw_root); - - if (mount(overlay, "/mnt", "overlayfs", MS_NOATIME, lowerdir)) { - fprintf(stderr, "mount failed: %s\n", strerror(errno)); - return -1; - } - - return pivot("/mnt", ro_root); -} - -int -ramoverlay(void) -{ - mkdir("/tmp/root", 0755); - mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755"); - - return fopivot("/tmp/root", "/rom"); -} diff --git a/libfstools/extroot.c b/libfstools/extroot.c new file mode 100644 index 0000000..2ed9b37 --- /dev/null +++ b/libfstools/extroot.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs-state.h" + +char const *extroot_prefix = NULL; + +static int mount_extroot(void) +{ + char block_path[32]; + char kmod_loader[64]; + struct stat s; + pid_t pid; + + if (!extroot_prefix) + return -1; + + sprintf(block_path, "%s/sbin/block", extroot_prefix); + + if (stat(block_path, &s)) + return -1; + + sprintf(kmod_loader, "/sbin/kmodloader %s/etc/modules-boot.d/ %s", extroot_prefix, extroot_prefix); + system(kmod_loader); + + pid = fork(); + if (!pid) { + mkdir("/tmp/extroot", 0755); + execl(block_path, block_path, "extroot", NULL); + exit(-1); + } else if (pid > 0) { + int status; + + waitpid(pid, &status, 0); + if (!WEXITSTATUS(status)) { + if (find_mount("/tmp/extroot/mnt")) { + mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT | MS_RDONLY, 0); + + mkdir("/tmp/extroot/mnt/proc", 0755); + mkdir("/tmp/extroot/mnt/dev", 0755); + mkdir("/tmp/extroot/mnt/sys", 0755); + mkdir("/tmp/extroot/mnt/tmp", 0755); + mkdir("/tmp/extroot/mnt/rom", 0755); + + if (mount_move("/tmp/extroot", "", "/mnt")) { + fprintf(stderr, "moving pivotroot failed - continue normal boot\n"); + umount("/tmp/extroot/mnt"); + } else if (pivot("/mnt", "/rom")) { + fprintf(stderr, "switching to pivotroot failed - continue normal boot\n"); + umount("/mnt"); + } else { + umount("/tmp/overlay"); + rmdir("/tmp/overlay"); + rmdir("/tmp/extroot/mnt"); + rmdir("/tmp/extroot"); + return 0; + } + } else if (find_mount("/tmp/extroot/overlay")) { + if (mount_move("/tmp/extroot", "", "/overlay")) { + fprintf(stderr, "moving extroot failed - continue normal boot\n"); + umount("/tmp/extroot/overlay"); + } else if (fopivot("/overlay", "/rom")) { + fprintf(stderr, "switching to extroot failed - continue normal boot\n"); + umount("/overlay"); + } else { + umount("/tmp/overlay"); + rmdir("/tmp/overlay"); + rmdir("/tmp/extroot/overlay"); + rmdir("/tmp/extroot"); + return 0; + } + } + } + } + return -1; +} + +static struct backend extroot_backend = { + .name = "extroot", + .mount = mount_extroot, +}; +BACKEND(extroot_backend); diff --git a/libfstools/find.c b/libfstools/find.c new file mode 100644 index 0000000..35e37e7 --- /dev/null +++ b/libfstools/find.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include "../fs-state.h" + +int +find_overlay_mount(char *overlay) +{ + FILE *fp = fopen("/proc/mounts", "r"); + static char line[256]; + int ret = -1; + + if(!fp) + return ret; + + while (ret && fgets(line, sizeof(line), fp)) + if (!strncmp(line, overlay, strlen(overlay))) + ret = 0; + + fclose(fp); + + return ret; +} + +char* +find_mount(char *mp) +{ + FILE *fp = fopen("/proc/mounts", "r"); + static char line[256]; + char *point = NULL; + + if(!fp) + return NULL; + + while (fgets(line, sizeof(line), fp)) { + char *s, *t = strstr(line, " "); + + if (!t) { + fclose(fp); + return NULL; + } + t++; + s = strstr(t, " "); + if (!s) { + fclose(fp); + return NULL; + } + *s = '\0'; + + if (!strcmp(t, mp)) { + fclose(fp); + return t; + } + } + + fclose(fp); + + return point; +} + +char* +find_mount_point(char *block, char *fs) +{ + FILE *fp = fopen("/proc/mounts", "r"); + static char line[256]; + int len = strlen(block); + char *point = NULL; + + if(!fp) + return NULL; + + while (fgets(line, sizeof(line), fp)) { + if (!strncmp(line, block, len)) { + char *p = &line[len + 1]; + char *t = strstr(p, " "); + + if (!t) { + fclose(fp); + return NULL; + } + + *t = '\0'; + t++; + + if (fs && strncmp(t, fs, strlen(fs))) { + fclose(fp); + fprintf(stderr, "block is mounted with wrong fs\n"); + return NULL; + } + point = p; + + break; + } + } + + fclose(fp); + + return point; +} + +int +find_filesystem(char *fs) +{ + FILE *fp = fopen("/proc/filesystems", "r"); + static char line[256]; + int ret = -1; + + if (!fp) { + fprintf(stderr, "opening /proc/filesystems failed: %s\n", strerror(errno)); + goto out; + } + + while (ret && fgets(line, sizeof(line), fp)) + if (strstr(line, fs)) + ret = 0; + + fclose(fp); + +out: + return ret; +} diff --git a/libfstools/jffs2.c b/libfstools/jffs2.c new file mode 100644 index 0000000..9a06ff8 --- /dev/null +++ b/libfstools/jffs2.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs-state.h" +#include "volume.h" + +#define SWITCH_JFFS2 "/tmp/.switch_jffs2" + +void +foreachdir(const char *dir, int (*cb)(const char*)) +{ + char globdir[256]; + glob_t gl; + int j; + + if (dir[strlen(dir) - 1] == '/') + snprintf(globdir, 256, "%s*", dir); + else + snprintf(globdir, 256, "%s/*", dir); + + if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl)) + for (j = 0; j < gl.gl_pathc; j++) + foreachdir(gl.gl_pathv[j], cb); + + cb(dir); +} + +static int +jffs2_mount(void) +{ + struct volume *v; + + if (mkdir("/tmp/overlay", 0755)) { + fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); + return -1; + } + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "rootfs_data does not exist\n"); + return -1; + } + + if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { + fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", v->blk, strerror(errno)); + return -1; + } + + return volume_init(v); +} + +static int +switch2jffs(void) +{ + struct volume *v = volume_find("rootfs_data"); + struct stat s; + int ret; + + if (!stat(SWITCH_JFFS2, &s)) { + fprintf(stderr, "jffs2 switch already running\n"); + return -1; + } + + if (!v) { + fprintf(stderr, "no rootfs_data was found\n"); + return -1; + } + + creat("/tmp/.switch_jffs2", 0600); + ret = mount(v->blk, "/rom/overlay", "jffs2", MS_NOATIME, NULL); + unlink("/tmp/.switch_jffs2"); + if (ret) { + fprintf(stderr, "failed - mount -t jffs2 %s /rom/overlay: %s\n", v->blk, strerror(errno)); + return -1; + } + + if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) { + fprintf(stderr, "failed - mount -o remount,ro none: %s\n", strerror(errno)); + return -1; + } + + system("cp -a /tmp/root/* /rom/overlay"); + + if (pivot("/rom", "/mnt")) { + fprintf(stderr, "failed - pivot /rom /mnt: %s\n", strerror(errno)); + return -1; + } + + if (mount_move("/mnt", "/tmp/root", "")) { + fprintf(stderr, "failed - mount -o move /mnt /tmp/root %s\n", strerror(errno)); + return -1; + } + + return fopivot("/overlay", "/rom"); +} + +int +handle_whiteout(const char *dir) +{ + struct stat s; + char link[256]; + ssize_t sz; + struct dirent **namelist; + int n; + + n = scandir(dir, &namelist, NULL, NULL); + + if (n < 1) + return -1; + + while (n--) { + char file[256]; + + snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); + if (!lstat(file, &s) && S_ISLNK(s.st_mode)) { + sz = readlink(file, link, sizeof(link) - 1); + if (sz > 0) { + char *orig; + + link[sz] = '\0'; + orig = strstr(&file[1], "/"); + if (orig && !strcmp(link, "(overlay-whiteout)")) + unlink(orig); + } + } + free(namelist[n]); + } + free(namelist); + + return 0; +} + +static int +ask_user(int argc, char **argv) +{ + if ((argc < 2) || strcmp(argv[1], "-y")) { + fprintf(stderr, "This will erase all settings and remove any installed packages. Are you sure? [N/y]\n"); + if (getchar() != 'y') + return -1; + } + return 0; + +} + +static int +handle_rmdir(const char *dir) +{ + struct stat s; + struct dirent **namelist; + int n; + + n = scandir(dir, &namelist, NULL, NULL); + + if (n < 1) + return -1; + + while (n--) { + char file[256]; + + snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); + if (!lstat(file, &s) && !S_ISDIR(s.st_mode)) + unlink(file); + free(namelist[n]); + } + free(namelist); + + rmdir(dir); + + return 0; +} + +static int +jffs2_reset(int argc, char **argv) +{ + struct volume *v; + char *mp; + + if (ask_user(argc, argv)) + return -1; + + if (find_filesystem("overlay")) { + fprintf(stderr, "overlayfs not found\n"); + return -1; + } + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "no rootfs_data was found\n"); + return -1; + } + + mp = find_mount_point(v->blk, "jffs2"); + if (mp) { + fprintf(stderr, "%s is mounted as %s, only erasing files\n", v->blk, mp); + foreachdir(mp, handle_rmdir); + mount(mp, "/", NULL, MS_REMOUNT, 0); + } else { + fprintf(stderr, "%s is not mounted, erasing it\n", v->blk); + volume_erase_all(v); + } + + return 0; +} + +static int +jffs2_mark(int argc, char **argv) +{ + __u32 deadc0de = __cpu_to_be32(0xdeadc0de); + struct volume *v; + size_t sz; + int fd; + + if (ask_user(argc, argv)) + return -1; + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "no rootfs_data was found\n"); + return -1; + } + + fd = open(v->blk, O_WRONLY); + fprintf(stderr, "%s - marking with deadc0de\n", v->blk); + if (!fd) { + fprintf(stderr, "opening %s failed\n", v->blk); + return -1; + } + + sz = write(fd, &deadc0de, sizeof(deadc0de)); + close(fd); + + if (sz != 1) { + fprintf(stderr, "writing %s failed: %s\n", v->blk, strerror(errno)); + return -1; + } + + return 0; +} + +int +jffs2_switch(int argc, char **argv) +{ + struct volume *v; + char *mp; + int ret = -1; + + if (find_overlay_mount("overlayfs:/tmp/root")) + return -1; + + if (find_filesystem("overlay")) { + fprintf(stderr, "overlayfs not found\n"); + return ret; + } + + v = volume_find("rootfs_data"); + mp = find_mount_point(v->blk, NULL); + if (mp) { + fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); + return -1; + } + + switch (volume_identify(v)) { + case FS_NONE: + fprintf(stderr, "no jffs2 marker found\n"); + /* fall through */ + + case FS_DEADCODE: + ret = switch2jffs(); + if (!ret) { + fprintf(stderr, "doing fo cleanup\n"); + umount2("/tmp/root", MNT_DETACH); + foreachdir("/overlay/", handle_whiteout); + } + break; + + case FS_JFFS2: + ret = jffs2_mount(); + if (ret) + break; + if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { + fprintf(stderr, "switching to jffs2 failed\n"); + ret = -1; + } + break; + } + + return ret; +} + +static int overlay_mount_fs(void) +{ + struct volume *v; + + if (mkdir("/tmp/overlay", 0755)) { + fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); + return -1; + } + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "rootfs_data does not exist\n"); + return -1; + } + + if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { + fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", + v->blk, strerror(errno)); + return -1; + } + + volume_init(v); + + return -1; +} + +static int overlay_mount(void) +{ + struct volume *v = volume_find("rootfs_data");; + char *mp; + + if (!v) + return -1; + + mp = find_mount_point(v->blk, NULL); + if (mp) { + fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); + return -1; + } + + overlay_mount_fs(); + + extroot_prefix = "/tmp/overlay"; + if (!backend_mount("extroot")) { + fprintf(stderr, "fs-state: switched to extroot\n"); + return 0; + } + + fprintf(stderr, "switching to jffs2\n"); + if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { + fprintf(stderr, "switching to jffs2 failed - fallback to ramoverlay\n"); + return ramoverlay(); + } + + return -1; +} + +static struct backend_handler jffs2_handlers[] = { +{ + .name = "jffs2reset", + .cli = jffs2_reset, +}, { + .name = "jffs2mark", + .cli = jffs2_mark, +}}; + +static struct backend overlay_backend = { + .name = "overlay", + .num_handlers = ARRAY_SIZE(jffs2_handlers), + .handlers = jffs2_handlers, + .mount = overlay_mount, +}; +BACKEND(overlay_backend); diff --git a/libfstools/mount.c b/libfstools/mount.c new file mode 100644 index 0000000..e7b57f0 --- /dev/null +++ b/libfstools/mount.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "../fs-state.h" + +/* this is a raw syscall - man 2 pivot_root */ +extern int pivot_root(const char *new_root, const char *put_old); + +int +mount_move(char *oldroot, char *newroot, char *dir) +{ +#ifndef MS_MOVE +#define MS_MOVE (1 << 13) +#endif + struct stat s; + char olddir[64]; + char newdir[64]; + int ret; + + snprintf(olddir, sizeof(olddir), "%s%s", oldroot, dir); + snprintf(newdir, sizeof(newdir), "%s%s", newroot, dir); + + if (stat(olddir, &s) || !S_ISDIR(s.st_mode)) + return -1; + + if (stat(newdir, &s) || !S_ISDIR(s.st_mode)) + return -1; + + ret = mount(olddir, newdir, NULL, MS_NOATIME | MS_MOVE, NULL); + +/* if (ret) + fprintf(stderr, "failed %s %s: %s\n", olddir, newdir, strerror(errno));*/ + + return ret; +} + +int +pivot(char *new, char *old) +{ + char pivotdir[64]; + int ret; + + if (mount_move("", new, "/proc")) + return -1; + + snprintf(pivotdir, sizeof(pivotdir), "%s%s", new, old); + + ret = pivot_root(new, pivotdir); + + if (ret < 0) { + fprintf(stderr, "pivot_root failed %s %s: %s\n", new, pivotdir, strerror(errno)); + return -1; + } + + mount_move(old, "", "/dev"); + mount_move(old, "", "/tmp"); + mount_move(old, "", "/sys"); + mount_move(old, "", "/overlay"); + + return 0; +} + +int +fopivot(char *rw_root, char *ro_root) +{ + char overlay[64], lowerdir[64]; + + if (find_filesystem("overlay")) { + fprintf(stderr, "BUG: no suitable fs found\n"); + return -1; + } + + snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root); + snprintf(lowerdir, sizeof(lowerdir), "lowerdir=/,upperdir=%s", rw_root); + + if (mount(overlay, "/mnt", "overlayfs", MS_NOATIME, lowerdir)) { + fprintf(stderr, "mount failed: %s\n", strerror(errno)); + return -1; + } + + return pivot("/mnt", ro_root); +} + +int +ramoverlay(void) +{ + mkdir("/tmp/root", 0755); + mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755"); + + return fopivot("/tmp/root", "/rom"); +} diff --git a/libfstools/mount_root.c b/libfstools/mount_root.c new file mode 100644 index 0000000..ce20604 --- /dev/null +++ b/libfstools/mount_root.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include "../fs-state.h" + +#include "volume.h" + +int +backend_mount(char *name) +{ + struct backend *b = find_backend(name); + + if (!b || !b->mount) + return -1; + + return b->mount(); +} + +static int +backend_info(char *name) +{ + struct backend *b = find_backend(name); + + if (!b || !b->info) + return -1; + + return b->info(); +} + +static int +start(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + + if (!getenv("PREINIT")) + return -1; + + if (!v) { + v = volume_find("rootfs"); + volume_init(v); + fprintf(stderr, "mounting /dev/root\n"); + mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0); + return 0; + } + + extroot_prefix = ""; + if (!backend_mount("extroot")) { + fprintf(stderr, "fs-state: switched to extroot\n"); + return 0; + } + + switch (volume_identify(v)) { + case FS_NONE: + case FS_DEADCODE: + return ramoverlay(); + + case FS_JFFS2: + backend_mount("overlay"); + break; + + case FS_SNAPSHOT: + backend_mount("snapshot"); + break; + } + + return 0; +} + +static int +stop(int argc, char **argv) +{ + if (!getenv("SHUTDOWN")) + return -1; + + return 0; +} + +static int +done(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + + if (!v) + return -1; + + switch (volume_identify(v)) { + case FS_NONE: + case FS_DEADCODE: + return jffs2_switch(argc, argv); + } + + return 0; +} + +static int +info(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + + if (!v) + return -1; + + switch (volume_identify(v)) { + case FS_SNAPSHOT: + backend_info("snapshot"); + return 0; + } + + return 0; +} + +static struct backend start_backend = { + .name = "start", + .cli = start, +}; +BACKEND(start_backend); + +static struct backend stop_backend = { + .name = "stop", + .cli = stop, +}; +BACKEND(stop_backend); + +static struct backend done_backend = { + .name = "done", + .cli = done, +}; +BACKEND(done_backend); + +static struct backend info_backend = { + .name = "info", + .cli = info, +}; +BACKEND(info_backend); diff --git a/libfstools/mtd.c b/libfstools/mtd.c new file mode 100644 index 0000000..a0005d7 --- /dev/null +++ b/libfstools/mtd.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs-state.h" + +#include "volume.h" + +#define PATH_MAX 256 + +struct mtd_priv { + int fd; + int idx; + char *chr; +}; + +static struct driver mtd_driver; + +static int mtd_open(const char *mtd, int block) +{ + FILE *fp; + char dev[PATH_MAX]; + int i, ret, flags = O_RDWR | O_SYNC; + + if ((fp = fopen("/proc/mtd", "r"))) { + while (fgets(dev, sizeof(dev), fp)) { + if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) { + snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i); + ret = open(dev, flags); + if (ret < 0) { + snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i); + ret = open(dev, flags); + } + fclose(fp); + return ret; + } + } + fclose(fp); + } + + return open(mtd, flags); +} + +static void mtd_volume_close(struct volume *v) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + + if (!p->fd) + return; + + close(p->fd); + p->fd = 0; +} + +static int mtd_volume_load(struct volume *v) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + struct mtd_info_user mtdInfo; + struct erase_info_user mtdLockInfo; + + if (p->fd) + return 0; + + if (!p->chr) + return -1; + + p->fd = mtd_open(p->chr, 0); + if (p->fd < 0) { + p->fd = 0; + fprintf(stderr, "Could not open mtd device: %s\n", p->chr); + return -1; + } + + if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) { + mtd_volume_close(v); + fprintf(stderr, "Could not get MTD device info from %s\n", p->chr); + return -1; + } + + v->size = mtdInfo.size; + v->block_size = mtdInfo.erasesize; + switch (mtdInfo.type) { + case MTD_NORFLASH: + v->type = NORFLASH; + break; + case MTD_NANDFLASH: + v->type = NANDFLASH; + break; + case MTD_UBIVOLUME: + v->type = UBIVOLUME; + break; + default: + v->type = UNKNOWN_TYPE; + break; + } + + mtdLockInfo.start = 0; + mtdLockInfo.length = v->size; + ioctl(p->fd, MEMUNLOCK, &mtdLockInfo); + + return 0; +} + +static char* mtd_find_index(char *name) +{ + FILE *fp = fopen("/proc/mtd", "r"); + static char line[256]; + char *index = NULL; + + if(!fp) + return index; + + while (!index && fgets(line, sizeof(line), fp)) { + if (strstr(line, name)) { + char *eol = strstr(line, ":"); + + if (!eol) + continue; + + *eol = '\0'; + index = &line[3]; + } + } + + fclose(fp); + + return index; +} + +static int mtd_volume_find(struct volume *v, char *name) +{ + char *idx = mtd_find_index(name); + struct mtd_priv *p; + char buffer[32]; + + if (!idx) + return -1; + + p = calloc(1, sizeof(struct mtd_priv)); + if (!p) + return -1; + + v->priv = p; + v->name = strdup(name); + v->drv = &mtd_driver; + p->idx = atoi(idx); + + snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx); + v->blk = strdup(buffer); + + snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx); + p->chr = strdup(buffer); + + return 0; +} + +static int mtd_volume_identify(struct volume *v) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + __u32 deadc0de; + __u16 jffs2; + size_t sz; + + if (mtd_volume_load(v)) { + fprintf(stderr, "reading %s failed\n", v->name); + return -1; + } + + sz = read(p->fd, &deadc0de, sizeof(deadc0de)); + + if (sz != sizeof(deadc0de)) { + fprintf(stderr, "reading %s failed: %s\n", v->name, strerror(errno)); + return -1; + } + + if (deadc0de == 0x4f575254) + return FS_SNAPSHOT; + + deadc0de = __be32_to_cpu(deadc0de); + if (deadc0de == 0xdeadc0de) { + fprintf(stderr, "jffs2 is not ready - marker found\n"); + return FS_DEADCODE; + } + + jffs2 = __be16_to_cpu(deadc0de >> 16); + if (jffs2 == 0x1985) { + fprintf(stderr, "jffs2 is ready\n"); + return FS_JFFS2; + } + + if (v->type == UBIVOLUME && deadc0de == 0xffffffff) { + fprintf(stderr, "jffs2 is ready\n"); + return FS_JFFS2; + } + + fprintf(stderr, "No jffs2 marker was found\n"); + + return FS_NONE; +} + +static int mtd_volume_erase(struct volume *v, int offset, int len) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + struct erase_info_user eiu; + int first_block, num_blocks; + + if (mtd_volume_load(v)) + return -1; + + if (offset % v->block_size || len % v->block_size) { + fprintf(stderr, "mtd erase needs to be block aligned\n"); + return -1; + } + + first_block = offset / v->block_size; + num_blocks = len / v->block_size; + eiu.length = v->block_size; + + for (eiu.start = first_block * v->block_size; + eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size; + eiu.start += v->block_size) { + fprintf(stderr, "erasing %x %x\n", eiu.start, v->block_size); + ioctl(p->fd, MEMUNLOCK, &eiu); + if (ioctl(p->fd, MEMERASE, &eiu)) + fprintf(stderr, "Failed to erase block at 0x%x\n", eiu.start); + } + + mtd_volume_close(v); + + return 0; +} + +static int mtd_volume_erase_all(struct volume *v) +{ + mtd_volume_erase(v, 0, v->size); + mtd_volume_close(v); + + return 0; +} + +static int mtd_volume_init(struct volume *v) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + struct mtd_info_user mtdinfo; + int ret; + + if (mtd_volume_load(v)) + return -1; + + ret = ioctl(p->fd, MEMGETINFO, &mtdinfo); + if (ret) { + fprintf(stderr, "ioctl(%d, MEMGETINFO) failed: %s\n", p->fd, strerror(errno)); + } else { + struct erase_info_user mtdlock; + + mtdlock.start = 0; + mtdlock.length = mtdinfo.size; + ioctl(p->fd, MEMUNLOCK, &mtdlock); + } + + return ret; +} + +static int mtd_volume_read(struct volume *v, void *buf, int offset, int length) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + + if (mtd_volume_load(v)) + return -1; + + if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "lseek/read failed\n"); + return -1; + } + + if (read(p->fd, buf, length) == -1) { + fprintf(stderr, "read failed\n"); + return -1; + } + + return 0; +} + +static int mtd_volume_write(struct volume *v, void *buf, int offset, int length) +{ + struct mtd_priv *p = (struct mtd_priv*) v->priv; + + if (mtd_volume_load(v)) + return -1; + + if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) { + fprintf(stderr, "lseek/write failed at offset %d\n", offset); + perror("lseek"); + return -1; + } + + if (write(p->fd, buf, length) == -1) { + fprintf(stderr, "write failed\n"); + return -1; + } + + return 0; +} + +static struct driver mtd_driver = { + .name = "mtd", + .find = mtd_volume_find, + .init = mtd_volume_init, + .erase = mtd_volume_erase, + .erase_all = mtd_volume_erase_all, + .read = mtd_volume_read, + .write = mtd_volume_write, + .identify = mtd_volume_identify, +}; +DRIVER(mtd_driver); diff --git a/libfstools/overlay.c b/libfstools/overlay.c new file mode 100644 index 0000000..85237e4 --- /dev/null +++ b/libfstools/overlay.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../fs-state.h" +#include "volume.h" + +#define SWITCH_JFFS2 "/tmp/.switch_jffs2" + +void +foreachdir(const char *dir, int (*cb)(const char*)) +{ + char globdir[256]; + glob_t gl; + int j; + + if (dir[strlen(dir) - 1] == '/') + snprintf(globdir, 256, "%s*", dir); + else + snprintf(globdir, 256, "%s/*", dir); + + if (!glob(globdir, GLOB_NOESCAPE | GLOB_MARK | GLOB_ONLYDIR, NULL, &gl)) + for (j = 0; j < gl.gl_pathc; j++) + foreachdir(gl.gl_pathv[j], cb); + + cb(dir); +} + +static int +overlay_mount(struct volume *v, char *fs) +{ + if (mkdir("/tmp/overlay", 0755)) { + fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); + return -1; + } + + if (mount(v->blk, "/tmp/overlay", fs, MS_NOATIME, NULL)) { + fprintf(stderr, "failed to mount -t %s %s /tmp/overlay: %s\n", fs, v->blk, strerror(errno)); + return -1; + } + + return volume_init(v); +} + +static int +switch2jffs(struct volume *v) +{ + struct stat s; + int ret; + + if (!stat(SWITCH_JFFS2, &s)) { + fprintf(stderr, "jffs2 switch already running\n"); + return -1; + } + + creat("/tmp/.switch_jffs2", 0600); + ret = mount(v->blk, "/rom/overlay", "jffs2", MS_NOATIME, NULL); + unlink("/tmp/.switch_jffs2"); + if (ret) { + fprintf(stderr, "failed - mount -t jffs2 %s /rom/overlay: %s\n", v->blk, strerror(errno)); + return -1; + } + + if (mount("none", "/", NULL, MS_NOATIME | MS_REMOUNT, 0)) { + fprintf(stderr, "failed - mount -o remount,ro none: %s\n", strerror(errno)); + return -1; + } + + system("cp -a /tmp/root/* /rom/overlay"); + + if (pivot("/rom", "/mnt")) { + fprintf(stderr, "failed - pivot /rom /mnt: %s\n", strerror(errno)); + return -1; + } + + if (mount_move("/mnt", "/tmp/root", "")) { + fprintf(stderr, "failed - mount -o move /mnt /tmp/root %s\n", strerror(errno)); + return -1; + } + + return fopivot("/overlay", "/rom"); +} + +int +handle_whiteout(const char *dir) +{ + struct stat s; + char link[256]; + ssize_t sz; + struct dirent **namelist; + int n; + + n = scandir(dir, &namelist, NULL, NULL); + + if (n < 1) + return -1; + + while (n--) { + char file[256]; + + snprintf(file, sizeof(file), "%s%s", dir, namelist[n]->d_name); + if (!lstat(file, &s) && S_ISLNK(s.st_mode)) { + sz = readlink(file, link, sizeof(link) - 1); + if (sz > 0) { + char *orig; + + link[sz] = '\0'; + orig = strstr(&file[1], "/"); + if (orig && !strcmp(link, "(overlay-whiteout)")) + unlink(orig); + } + } + free(namelist[n]); + } + free(namelist); + + return 0; +} + +static int +ask_user(int argc, char **argv) +{ + if ((argc < 2) || strcmp(argv[1], "-y")) { + fprintf(stderr, "This will erase all settings and remove any installed packages. Are you sure? [N/y]\n"); + if (getchar() != 'y') + return -1; + } + return 0; + +} + +int +jffs2_switch(int argc, char **argv) +{ + struct volume *v; + char *mp; + int ret = -1; + + if (find_overlay_mount("overlayfs:/tmp/root")) + return -1; + + if (find_filesystem("overlay")) { + fprintf(stderr, "overlayfs not found\n"); + return ret; + } + + v = volume_find("rootfs_data"); + mp = find_mount_point(v->blk, NULL); + if (mp) { + fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); + return -1; + } + + switch (volume_identify(v)) { + case FS_NONE: + fprintf(stderr, "no jffs2 marker found\n"); + /* fall through */ + + case FS_DEADCODE: + ret = switch2jffs(); + if (!ret) { + fprintf(stderr, "doing fo cleanup\n"); + umount2("/tmp/root", MNT_DETACH); + foreachdir("/overlay/", handle_whiteout); + } + break; + + case FS_JFFS2: + ret = overlay_mount(v, "jffs2"); + if (ret) + break; + if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { + fprintf(stderr, "switching to jffs2 failed\n"); + ret = -1; + } + break; + } + + return ret; +} + +static int overlay_mount_fs(void) +{ + struct volume *v; + + if (mkdir("/tmp/overlay", 0755)) { + fprintf(stderr, "failed to mkdir /tmp/overlay: %s\n", strerror(errno)); + return -1; + } + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "rootfs_data does not exist\n"); + return -1; + } + + if (mount(v->blk, "/tmp/overlay", "jffs2", MS_NOATIME, NULL)) { + fprintf(stderr, "failed to mount -t jffs2 %s /tmp/overlay: %s\n", + v->blk, strerror(errno)); + return -1; + } + + volume_init(v); + + return -1; +} + +static int overlay_mount(void) +{ + struct volume *v = volume_find("rootfs_data");; + char *mp; + + if (!v) + return -1; + + mp = find_mount_point(v->blk, NULL); + if (mp) { + fprintf(stderr, "rootfs_data:%s is already mounted as %s\n", v->blk, mp); + return -1; + } + + overlay_mount_fs(); + + extroot_prefix = "/tmp/overlay"; + if (!backend_mount("extroot")) { + fprintf(stderr, "fs-state: switched to extroot\n"); + return 0; + } + + fprintf(stderr, "switching to jffs2\n"); + if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) { + fprintf(stderr, "switching to jffs2 failed - fallback to ramoverlay\n"); + return ramoverlay(); + } + + return -1; +} + +static struct backend overlay_backend = { + .name = "overlay", + .mount = overlay_mount, +}; +BACKEND(overlay_backend); diff --git a/libfstools/snapshot.c b/libfstools/snapshot.c new file mode 100644 index 0000000..bcbce94 --- /dev/null +++ b/libfstools/snapshot.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../fs-state.h" +#include "volume.h" + +#define PATH_MAX 256 +#define OWRT 0x4f575254 +#define DATA 0x44415441 +#define CONF 0x434f4e46 + +struct file_header { + uint32_t magic; + uint32_t type; + uint32_t seq; + uint32_t length; + uint32_t md5[4]; +}; + +static inline int +is_config(struct file_header *h) +{ + return ((h->magic == OWRT) && (h->type == CONF)); +} + +static inline int +valid_file_size(int fs) +{ + if ((fs > 8 * 1024 * 1204) || (fs <= 0)) + return -1; + + return 0; +} + +static void +hdr_to_be32(struct file_header *hdr) +{ + uint32_t *h = (uint32_t *) hdr; + int i; + + for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++) + h[i] = cpu_to_be32(h[i]); +} + +static void +be32_to_hdr(struct file_header *hdr) +{ + uint32_t *h = (uint32_t *) hdr; + int i; + + for (i = 0; i < sizeof(struct file_header) / sizeof(uint32_t); i++) + h[i] = be32_to_cpu(h[i]); +} + +static int +pad_file_size(struct volume *v, int size) +{ + int mod; + + size += sizeof(struct file_header); + mod = size % v->block_size; + if (mod) { + size -= mod; + size += v->block_size; + } + + return size; +} + +static int +verify_file_hash(char *file, uint32_t *hash) +{ + uint32_t md5[4]; + + if (md5sum(file, md5)) { + fprintf(stderr, "failed to generate md5 sum\n"); + return -1; + } + + if (memcmp(md5, hash, sizeof(md5))) { + fprintf(stderr, "failed to verify hash of %s.\n", file); + return -1; + } + + return 0; +} + +static int +snapshot_next_free(struct volume *v, uint32_t *seq) +{ + struct file_header hdr = { 0 }; + int block = 0; + + *seq = rand(); + + do { + if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { + fprintf(stderr, "scanning for next free block failed\n"); + return 0; + } + + be32_to_hdr(&hdr); + + if (hdr.magic != OWRT) + break; + + if (hdr.type == DATA && !valid_file_size(hdr.length)) { + if (*seq + 1 != hdr.seq && block) + return block; + *seq = hdr.seq; + block += pad_file_size(v, hdr.length) / v->block_size; + } + } while (hdr.type == DATA); + + return block; +} + +static int +config_find(struct volume *v, struct file_header *conf, struct file_header *sentinel) +{ + uint32_t seq; + int i, next = snapshot_next_free(v, &seq); + + conf->magic = sentinel->magic = 0; + + if (!volume_read(v, conf, next, sizeof(*conf))) + be32_to_hdr(conf); + + for (i = (v->size / v->block_size) - 1; i > 0; i--) { + if (volume_read(v, sentinel, i * v->block_size, sizeof(*sentinel))) { + fprintf(stderr, "failed to read header\n"); + return -1; + } + be32_to_hdr(sentinel); + + if (sentinel->magic == OWRT && sentinel->type == CONF && !valid_file_size(sentinel->length)) { + if (next == i) + return -1; + return i; + } + } + + return -1; +} + +static int +snapshot_info(void) +{ + struct volume *v = volume_find("rootfs_data"); + struct file_header hdr = { 0 }, conf; + int block = 0; + + if (!v) + return -1; + + fprintf(stderr, "sectors:\t%llu, block_size:\t%dK\n", v->size / v->block_size, v->block_size / 1024); + do { + if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { + fprintf(stderr, "scanning for next free block failed\n"); + return 0; + } + + be32_to_hdr(&hdr); + + if (hdr.magic != OWRT) + break; + + if (hdr.type == DATA) + fprintf(stderr, "block %d:\tsnapshot entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); + else if (hdr.type == CONF) + fprintf(stderr, "block %d:\tvolatile entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); + + if (hdr.type == DATA && !valid_file_size(hdr.length)) + block += pad_file_size(v, hdr.length) / v->block_size; + } while (hdr.type == DATA); + block = config_find(v, &conf, &hdr); + if (block > 0) + fprintf(stderr, "block %d:\tsentinel entry, size: %d, sectors: %d, sequence: %d\n", block, hdr.length, pad_file_size(v, hdr.length) / v->block_size, hdr.seq); + + return 0; +} + +static int +snapshot_write_file(struct volume *v, int block, char *file, uint32_t seq, uint32_t type) +{ + uint32_t md5[4] = { 0 }; + struct file_header hdr; + struct stat s; + char buffer[256]; + int in = 0, len, offset; + int ret = -1; + + if (stat(file, &s) || md5sum(file, md5)) { + fprintf(stderr, "stat failed on %s\n", file); + goto out; + } + + if ((block * v->block_size) + pad_file_size(v, s.st_size) > v->size) { + fprintf(stderr, "upgrade is too big for the flash\n"); + goto out; + } + volume_erase(v, block * v->block_size, pad_file_size(v, s.st_size)); + volume_erase(v, block * v->block_size + pad_file_size(v, s.st_size), v->block_size); + + hdr.length = s.st_size; + hdr.magic = OWRT; + hdr.type = type; + hdr.seq = seq; + memcpy(hdr.md5, md5, sizeof(md5)); + hdr_to_be32(&hdr); + + if (volume_write(v, &hdr, block * v->block_size, sizeof(struct file_header))) { + fprintf(stderr, "failed to write header\n"); + goto out; + } + + in = open(file, O_RDONLY); + if (in < 1) { + fprintf(stderr, "failed to open %s\n", file); + goto out; + } + + offset = (block * v->block_size) + sizeof(struct file_header); + + while ((len = read(in, buffer, sizeof(buffer))) > 0) { + if (volume_write(v, buffer, offset, len) < 0) + goto out; + offset += len; + } + + ret = 0; + +out: + if (in > 0) + close(in); + + return ret; +} + +static int +snapshot_read_file(struct volume *v, int block, char *file, uint32_t type) +{ + struct file_header hdr; + char buffer[256]; + int out, offset = 0; + + if (volume_read(v, &hdr, block * v->block_size, sizeof(struct file_header))) { + fprintf(stderr, "failed to read header\n"); + return -1; + } + be32_to_hdr(&hdr); + + if (hdr.magic != OWRT) + return -1; + + if (hdr.type != type) + return -1; + + if (valid_file_size(hdr.length)) + return -1; + + out = open(file, O_WRONLY | O_CREAT, 0700); + if (!out) { + fprintf(stderr, "failed to open %s\n", file); + return -1; + } + + while (hdr.length > 0) { + int len = sizeof(buffer); + + if (hdr.length < len) + len = hdr.length; + + if ((volume_read(v, buffer, offset, len) != len) || (write(out, buffer, len) != len)) + return -1; + + offset += len; + hdr.length -= len; + } + + close(out); + + if (verify_file_hash(file, hdr.md5)) { + fprintf(stderr, "md5 verification failed\n"); + unlink(file); + return 0; + } + + block += pad_file_size(v, hdr.length) / v->block_size; + + return block; +} + +static int +sentinel_write(struct volume *v, uint32_t _seq) +{ + int ret, block; + struct stat s; + uint32_t seq; + + if (stat("/tmp/config.tar.gz", &s)) { + fprintf(stderr, "failed to stat /tmp/config.tar.gz\n"); + return -1; + } + + snapshot_next_free(v, &seq); + if (_seq) + seq = _seq; + block = v->size / v->block_size; + block -= pad_file_size(v, s.st_size) / v->block_size; + if (block < 0) + block = 0; + + ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF); + if (ret) + fprintf(stderr, "failed to write sentinel\n"); + else + fprintf(stderr, "wrote /tmp/config.tar.gz sentinel\n"); + return ret; +} + +static int +volatile_write(struct volume *v, uint32_t _seq) +{ + int block, ret; + uint32_t seq; + + block = snapshot_next_free(v, &seq); + if (_seq) + seq = _seq; + if (block < 0) + block = 0; + + ret = snapshot_write_file(v, block, "/tmp/config.tar.gz", seq, CONF); + if (ret) + fprintf(stderr, "failed to write /tmp/config.tar.gz\n"); + else + fprintf(stderr, "wrote /tmp/config.tar.gz\n"); + return ret; +} + +static int +config_write(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + int ret; + + if (!v) + return -1; + + ret = volatile_write(v, 0); + if (!ret) + ret = sentinel_write(v, 0); + + return ret; +} + +static int +config_read(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + struct file_header conf, sentinel; + int next, block, ret = 0; + uint32_t seq; + + if (!v) + return -1; + + block = config_find(v, &conf, &sentinel); + next = snapshot_next_free(v, &seq); + if (is_config(&conf) && conf.seq == seq) + block = next; + else if (!is_config(&sentinel) || sentinel.seq != seq) + return -1; + + unlink("/tmp/config.tar.gz"); + ret = snapshot_read_file(v, block, "/tmp/config.tar.gz", CONF); + + if (ret < 1) + fprintf(stderr, "failed to read /tmp/config.tar.gz\n"); + + return ret; +} + +static int +snapshot_write(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data"); + int block, ret; + uint32_t seq; + + if (!v) + return -1; + + block = snapshot_next_free(v, &seq); + if (block < 0) + block = 0; + + ret = snapshot_write_file(v, block, "/tmp/snapshot.tar.gz", seq + 1, DATA); + if (ret) + fprintf(stderr, "failed to write /tmp/snapshot.tar.gz\n"); + else + fprintf(stderr, "wrote /tmp/snapshot.tar.gz\n"); + + return ret; +} + +static int +snapshot_mark(int argc, char **argv) +{ + __be32 owrt = cpu_to_be32(OWRT); + struct volume *v; + size_t sz; + int fd; + + fprintf(stderr, "This will remove all snapshot data stored on the system. Are you sure? [N/y]\n"); + if (getchar() != 'y') + return -1; + + v = volume_find("rootfs_data"); + if (!v) { + fprintf(stderr, "no rootfs_data was found\n"); + return -1; + } + + fd = open(v->blk, O_WRONLY); + fprintf(stderr, "%s - marking with 0x%08x\n", v->blk, owrt); + if (fd < 0) { + fprintf(stderr, "opening %s failed\n", v->blk); + return -1; + } + + sz = write(fd, &owrt, sizeof(owrt)); + close(fd); + + if (sz != 1) { + fprintf(stderr, "writing %s failed: %s\n", v->blk, strerror(errno)); + return -1; + } + + return 0; +} + +static int +snapshot_read(int argc, char **argv) +{ + struct volume *v = volume_find("rootfs_data");; + int block = 0, ret = 0; + char file[64]; + + if (!v) + return -1; + + if (argc > 1) { + block = atoi(argv[1]); + if (block >= (v->size / v->block_size)) { + fprintf(stderr, "invalid block %d > %llu\n", block, v->size / v->block_size); + goto out; + } + snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block); + + ret = snapshot_read_file(v, block, file, DATA); + goto out; + } + + do { + snprintf(file, sizeof(file), "/tmp/snapshot/block%d.tar.gz", block); + block = snapshot_read_file(v, block, file, DATA); + } while (block > 0); + +out: + return ret; +} + +static int +snapshot_sync(void) +{ + struct volume *v = volume_find("rootfs_data"); + struct file_header sentinel, conf; + int next, block = 0; + uint32_t seq; + + if (!v) + return -1; + + next = snapshot_next_free(v, &seq); + block = config_find(v, &conf, &sentinel); + if (is_config(&conf) && conf.seq != seq) { + conf.magic = 0; + volume_erase(v, next * v->block_size, 2 * v->block_size); + } + + if (is_config(&sentinel) && (sentinel.seq != seq)) { + sentinel.magic = 0; + volume_erase(v, block * v->block_size, v->block_size); + } + + if (!is_config(&conf) && !is_config(&sentinel)) { + // fprintf(stderr, "no config found\n"); + } else if (((is_config(&conf) && is_config(&sentinel)) && + (memcmp(conf.md5, sentinel.md5, sizeof(conf.md5)) || (conf.seq != sentinel.seq))) || + (is_config(&conf) && !is_config(&sentinel))) { + uint32_t seq; + int next = snapshot_next_free(v, &seq); + int ret = snapshot_read_file(v, next, "/tmp/config.tar.gz", CONF); + if (ret > 0) { + if (sentinel_write(v, conf.seq)) + fprintf(stderr, "failed to write sentinel data"); + } + } else if (!is_config(&conf) && is_config(&sentinel) && next) { + int ret = snapshot_read_file(v, block, "/tmp/config.tar.gz", CONF); + if (ret > 0) + if (volatile_write(v, sentinel.seq)) + fprintf(stderr, "failed to write sentinel data"); + } else + fprintf(stderr, "config in sync\n"); + + unlink("/tmp/config.tar.gz"); + + return 0; +} + +static int +_ramoverlay(char *rom, char *overlay) +{ + mount("tmpfs", overlay, "tmpfs", MS_NOATIME, "mode=0755"); + return fopivot(overlay, rom); +} + +static int +snapshot_mount(void) +{ + snapshot_sync(); + setenv("SNAPSHOT", "magic", 1); + _ramoverlay("/rom", "/overlay"); + system("/sbin/snapshot unpack"); + foreachdir("/overlay/", handle_whiteout); + mkdir("/volatile", 0700); + _ramoverlay("/rom", "/volatile"); + mount_move("/rom/volatile", "/volatile", ""); + mount_move("/rom/rom", "/rom", ""); + system("/sbin/snapshot config_unpack"); + foreachdir("/volatile/", handle_whiteout); + unsetenv("SNAPSHOT"); + return -1; +} + +static struct backend_handler snapshot_handlers[] = { +{ + .name = "config_read", + .cli = config_read, +}, { + .name = "config_write", + .cli = config_write, +}, { + .name = "read", + .cli = snapshot_read, +}, { + .name = "write", + .cli = snapshot_write, +}, { + .name = "mark", + .cli = snapshot_mark, +}}; + +static struct backend snapshot_backend = { + .name = "snapshot", + .num_handlers = ARRAY_SIZE(snapshot_handlers), + .handlers = snapshot_handlers, + .mount = snapshot_mount, + .info = snapshot_info, +}; +BACKEND(snapshot_backend); diff --git a/libfstools/volume.c b/libfstools/volume.c new file mode 100644 index 0000000..4dc0a8e --- /dev/null +++ b/libfstools/volume.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#include +#include +#include + +#include "../fs-state.h" +#include "volume.h" + +enum { + FLASH_NOR, + FLASH_NAND, +}; + +static LIST_HEAD(drivers); + +void +volume_register_driver(struct driver *d) +{ + list_add(&d->list, &drivers); +} + +struct volume* volume_find(char *name) +{ + struct volume *v = malloc(sizeof(struct volume)); + struct driver *d; + + if (!v) + return NULL; + + list_for_each_entry(d, &drivers, list) { + memset(v, 0, sizeof(struct volume)); + + if (d->find && !d->find(v, name)) + return v; + } + + free(v); + + return NULL; +} diff --git a/libfstools/volume.h b/libfstools/volume.h new file mode 100644 index 0000000..4fa5641 --- /dev/null +++ b/libfstools/volume.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program 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 General Public License for more details. + */ + +#ifndef _VOLUME_H__ +#define _VOLUME_H__ + +#include + +struct volume; + +typedef int (*volume_probe_t)(void); +typedef int (*volume_init_t)(struct volume *v); +typedef void (*volume_stop_t)(struct volume *v); +typedef int (*volume_find_t)(struct volume *v, char *name); +typedef int (*volume_identify_t)(struct volume *v); +typedef int (*volume_read_t)(struct volume *v, void *buf, int offset, int length); +typedef int (*volume_write_t)(struct volume *v, void *buf, int offset, int length); +typedef int (*volume_erase_t)(struct volume *v, int start, int len); +typedef int (*volume_erase_all_t)(struct volume *v); + +struct driver { + struct list_head list; + + char *name; + volume_probe_t probe; + volume_init_t init; + volume_stop_t stop; + volume_find_t find; + volume_identify_t identify; + volume_read_t read; + volume_write_t write; + volume_erase_t erase; + volume_erase_all_t erase_all; +}; + +enum { + UNKNOWN_TYPE, + NANDFLASH, + NORFLASH, + UBIVOLUME, +}; + +struct volume { + struct driver *drv; + void *priv; + char *name; + char *blk; + + __u64 size; + __u32 block_size; + int type; +}; + +extern struct volume* volume_find(char *name); +extern void volume_register_driver(struct driver *drv); + +static inline int volume_init(struct volume *v) +{ + if (v && v->drv->init) + return v->drv->init(v); + return -1; +} + +static inline int volume_identify(struct volume *v) +{ + if (v && v->drv->identify) + return v->drv->identify(v); + return -1; +} + +static inline int volume_erase(struct volume *v, int offset, int len) +{ + if (v && v->drv->erase) + return v->drv->erase(v, offset, len); + return -1; +} + +static inline int volume_erase_all(struct volume *v) +{ + if (v && v->drv->erase_all) + return v->drv->erase_all(v); + return -1; +} + +static inline int volume_read(struct volume *v, void *buf, int offset, int length) +{ + if (v && v->drv->read) + return v->drv->read(v, buf, offset, length); + return -1; +} + +static inline int volume_write(struct volume *v, void *buf, int offset, int length) +{ + if (v && v->drv->write) + return v->drv->write(v, buf, offset, length); + return -1; +} + +#define DRIVER(x) \ + static void __attribute__((constructor)) \ + drv_register_##x(void) { \ + volume_register_driver(&x); \ + } + +#endif