/* * main.c * * the command line user interface */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #ifndef _SYS_STAT_H #include #endif #include "logic.h" #include "sh.h" #define STRING_BUFFER_SIZE PATH_MAX * 2 // currently, brief and verbose are mutually exclusive bool verbose; bool brief; bool yes; void print_help(const char *program) { printf("Usage: %s command options\n", program); puts(""); puts("Commands:"); puts(" vacuum - remove duplicated files in upperdir where copy_up is done but the file is not actually modified"); puts(" diff - show the list of actually changed files"); puts(" merge - merge all changes from upperdir to lowerdir, and clear upperdir"); puts(" deref - copy changes from upperdir to a new upperdir unfolding redirect and metacopy"); puts(""); puts("Options:"); puts(" -l, --lowerdir=LOWERDIR the lowerdir of OverlayFS (required)"); puts(" -u, --upperdir=UPPERDIR the upperdir of OverlayFS (required)"); puts(" -m, --mountdir=MOUNTDIR the mountdir of OverlayFS (optional)"); puts(" -L, --lowernew=LOWERNEW the lowerdir of new OverlayFS (optional)"); puts(" -U, --uppernew=UPPERNEW the upperdir of new OverlayFS (optional)"); puts(" -y --yes don't prompt if OverlayFS is still mounted (optional)"); puts(" -v, --verbose with diff action only: when a directory only exists in one version, still list every file of the directory"); puts(" -b, --brief with diff action only: conform to output of diff --brief --recursive --no-dereference"); puts(" -h, --help show this help text"); puts(""); puts("See https://github.com/kmxz/overlayfs-tools/ for warnings and more information."); } bool starts_with(const char *haystack, const char* needle) { return strncmp(needle, haystack, strlen(needle)) == 0; } bool is_mounted(const char *lower, const char *upper) { FILE *f = fopen("/proc/mounts", "r"); if (!f) { fprintf(stderr, "Cannot read /proc/mounts to test whether OverlayFS is mounted.\n"); return true; } char buf[STRING_BUFFER_SIZE]; while (fgets(buf, STRING_BUFFER_SIZE, f)) { if (!starts_with(buf, "overlay")) { continue; } if (strlen(buf) == STRING_BUFFER_SIZE) { fprintf(stderr, "OverlayFS line in /proc/mounts is too long.\n"); return true; } char *m_lower = strstr(buf, "lowerdir="); char *m_upper = strstr(buf, "upperdir="); if (m_lower == NULL || m_upper == NULL) { fprintf(stderr, "Cannot extract information from OverlayFS line in /proc/mounts.\n"); return true; } m_lower = &(m_lower[strlen("lowerdir=")]); m_upper = &(m_upper[strlen("upperdir=")]); if (!(strncmp(lower, m_lower, strlen(lower)) && strncmp(upper, m_upper, strlen(upper)))) { printf("The OverlayFS involved is still mounted.\n"); return true; } } return false; } bool check_mounted(const char *lower, const char *upper) { if (is_mounted(lower, upper) && !yes) { printf("It is strongly recommended to unmount OverlayFS first. Still continue (not recommended)?: \n"); int r = getchar(); if (r != 'Y' && r != 'y') { return true; } } return false; } bool directory_exists(const char *path) { struct stat sb; if (lstat(path, &sb) != 0) { return false; } return (sb.st_mode & S_IFMT) == S_IFDIR; } bool directory_create(const char *name, const char *path) { if (mkdir(path, 0755) == 0 || errno == EEXIST) { return true; } fprintf(stderr, "%s directory '%s' does not exist and cannot be created.\n", name, path); exit(EXIT_FAILURE); } bool real_check_xattr_trusted(const char *tmp_path, int tmp_file) { int ret = fsetxattr(tmp_file, "trusted.overlay.test", "naive", 5, 0); close(tmp_file); if (ret) { return false; } char verify_buffer[10]; if (getxattr(tmp_path, "trusted.overlay.test", verify_buffer, 10) != 5) { return false; } return !strncmp(verify_buffer, "naive", 5); } bool check_xattr_trusted(const char *upper) { char tmp_path[PATH_MAX]; strcpy(tmp_path, upper); strcat(tmp_path, "/.xattr_test_XXXXXX.tmp"); int tmp_file = mkstemps(tmp_path, 4); if (tmp_file < 0) { return false; } bool ret = real_check_xattr_trusted(tmp_path, tmp_file); unlink(tmp_path); return ret; } int main(int argc, char *argv[]) { char *lower = NULL; char *upper = NULL; char *dir, *mnt = NULL; static struct option long_options[] = { { "lowerdir", required_argument, 0, 'l' }, { "upperdir", required_argument, 0, 'u' }, { "mountdir", required_argument, 0, 'm' }, { "lowernew", required_argument, 0, 'L' }, { "uppernew", required_argument, 0, 'U' }, { "yes", no_argument , 0, 'y' }, { "help", no_argument , 0, 'h' }, { "verbose", no_argument , 0, 'v' }, { "brief", no_argument , 0, 'b' }, { 0, 0, 0, 0 } }; int opt = 0; int long_index = 0; while ((opt = getopt_long_only(argc, argv, "l:u:m:L:U:yhvb", long_options, &long_index)) != -1) { switch (opt) { case 'l': lower = realpath(optarg, NULL); if (lower) { vars[LOWERDIR] = lower; } break; case 'u': upper = realpath(optarg, NULL); if (upper) { vars[UPPERDIR] = upper; } break; case 'm': mnt = realpath(optarg, NULL); if (mnt) { vars[MOUNTDIR] = mnt; } break; case 'L': directory_create("New lowerdir", optarg); dir = realpath(optarg, NULL); if (dir) { vars[LOWERNEW] = dir; } break; case 'U': directory_create("New upperdir", optarg); dir = realpath(optarg, NULL); if (dir) { vars[UPPERNEW] = dir; } break; case 'y': yes = true; break; case 'h': print_help(argv[0]); return EXIT_SUCCESS; case 'v': verbose = true; brief = false; break; case 'b': verbose = false; brief = true; break; default: fprintf(stderr, "Option %c is not supported.\n", opt); goto see_help; } } if (!lower) { fprintf(stderr, "Lower directory not specified.\n"); goto see_help; } if (!directory_exists(lower)) { fprintf(stderr, "Lower directory cannot be opened.\n"); goto see_help; } if (!upper) { fprintf(stderr, "Upper directory not specified.\n"); goto see_help; } if (!directory_exists(upper)) { fprintf(stderr, "Upper directory cannot be opened.\n"); goto see_help; } if (!check_xattr_trusted(upper)) { fprintf(stderr, "The program cannot write trusted.* xattr. Try run again as root.\n"); return EXIT_FAILURE; } // Relax check for mounted overlay if we are not going to modify lowerdir/upperdir if ((!vars[LOWERNEW] || !vars[UPPERNEW]) && check_mounted(lower, upper)) { return EXIT_FAILURE; } if (optind == argc - 1) { int out; char filename_template[] = "overlay-tools-XXXXXX.sh"; FILE *script = NULL; if (strcmp(argv[optind], "diff") == 0) { out = diff(lower, upper); } else if (strcmp(argv[optind], "vacuum") == 0) { script = create_shell_script(filename_template); if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } out = vacuum(lower, upper, script); } else if (strcmp(argv[optind], "merge") == 0) { script = create_shell_script(filename_template); if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } out = merge(lower, upper, script); } else if (strcmp(argv[optind], "deref") == 0) { if (!mnt || !vars[UPPERNEW]) { fprintf(stderr, "'deref' command requires --uppernew and --mountdir.\n"); return EXIT_FAILURE; } if (!directory_exists(mnt)) { fprintf(stderr, "OverlayFS mount directory cannot be opened.\n"); goto see_help; } script = create_shell_script(filename_template); if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; } out = deref(mnt, upper, script); } else { fprintf(stderr, "Action not supported.\n"); goto see_help; } if (script != NULL) { printf("The script %s is created. Run the script to do the actual work please. Remember to run it when the OverlayFS is not mounted.\n", filename_template); fclose(script); } if (out) { fprintf(stderr, "Action aborted due to fatal error.\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } fprintf(stderr, "Please specify one action.\n"); see_help: fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return EXIT_FAILURE; }