/* * Copyright (c) 2013, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * tix-execdiff.c * Reports which files have had the executable bit changed between two trees. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" DIR* fdopendupdir(int fd) { int newfd = dup(fd); if ( newfd < 0 ) return NULL; DIR* result = fdopendir(newfd); if ( !result ) return close(newfd), (DIR*) NULL; return result; } int ftestexecutableat(int dirfd, const char* path) { struct stat st; if ( fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0 ) return -1; if ( !S_ISREG(st.st_mode) ) return errno = EISDIR, -1; if ( faccessat(dirfd, path, X_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW) == 0 ) return 1; else if ( errno == EACCES ) return 0; else return -1; } void execdiff(int tree_a, const char* tree_a_path, int tree_b, const char* tree_b_path, const char* relpath) { DIR* dir_b = fdopendupdir(tree_b); if ( !dir_b ) err(1, "fdopendupdir(`%s`)", tree_b_path); struct dirent* entry; while ( (entry = readdir(dir_b)) ) { if ( !strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") ) continue; char* subrelpath = join_paths(relpath, entry->d_name); int diropenflags = O_RDONLY | O_DIRECTORY | O_NOFOLLOW; int subtree_b = openat(tree_b, entry->d_name, diropenflags); if ( 0 <= subtree_b ) { char* subtree_b_path = join_paths(tree_b_path, entry->d_name); int subtree_a = openat(tree_a, entry->d_name, diropenflags); if ( subtree_a < 0 ) { if ( !(errno == ENOTDIR || errno == ELOOP || errno == ENOENT) ) err(1, "`%s/%s`", tree_b_path, entry->d_name); execdiff(-1, NULL, subtree_b, subtree_b_path, subrelpath); free(subtree_b_path); close(subtree_b); free(subrelpath); continue; } char* subtree_a_path = join_paths(tree_a_path, entry->d_name); execdiff(subtree_a, subtree_a_path, subtree_b, subtree_b_path, subrelpath); free(subtree_a_path); close(subtree_a); free(subtree_b_path); close(subtree_b); free(subrelpath); continue; } else if ( !(errno == ENOTDIR || errno == ELOOP) ) err(1, "`%s/%s`", tree_b_path, entry->d_name); int a_executableness = ftestexecutableat(tree_a, entry->d_name); int b_executableness = ftestexecutableat(tree_b, entry->d_name); if ( (a_executableness == 1) && (b_executableness == 0) ) { printf("chmod -x -- '"); for ( size_t i = 0; subrelpath[i]; i++ ) if ( subrelpath[i] == '\'' ) printf("'\\''"); else putchar(subrelpath[i]); printf("'\n"); } if ( (a_executableness != 1) && (b_executableness == 1) ) { printf("chmod +x -- '"); for ( size_t i = 0; subrelpath[i]; i++ ) if ( subrelpath[i] == '\'' ) printf("'\\''"); else putchar(subrelpath[i]); printf("'\n"); } free(subrelpath); continue; } closedir(dir_b); } static void help(FILE* fp, const char* argv0) { fprintf(fp, "Usage: %s [OPTION]... TREE-A TREE-B\n", argv0); fprintf(fp, "Reports which files have had the executable bit changed between two trees.\n"); } static void version(FILE* fp, const char* argv0) { fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); } int main(int argc, char* argv[]) { const char* argv0 = argv[0]; for ( int i = 0; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { default: fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); help(stderr, argv0); exit(1); } } else if ( !strcmp(arg, "--help") ) help(stdout, argv0), exit(0); else if ( !strcmp(arg, "--version") ) version(stdout, argv0), exit(0); else { fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); help(stderr, argv0); exit(1); } } if ( argc == 1 ) { help(stdout, argv0); exit(0); } compact_arguments(&argc, &argv); if ( argc < 3 ) errx(1, "You need to specify two directories"); if ( 3 < argc ) errx(1, "extra operand"); const char* tree_a_path = argv[1]; int tree_a = open(tree_a_path, O_RDONLY | O_DIRECTORY); if ( tree_a < 0 ) err(1, "`%s'", tree_a_path); const char* tree_b_path = argv[2]; int tree_b = open(tree_b_path, O_RDONLY | O_DIRECTORY); if ( tree_b < 0 ) err(1, "`%s'", tree_b_path); execdiff(tree_a, tree_a_path, tree_b, tree_b_path, "."); return 0; }