#include "file_utils.h" #include #include #include #include #include #include #include #include "error.h" #include "remove_all.h" extern char * trim_trailing_slashes(char * path) { int len = strlen(path); while (len > 1 && path[len-1] == '/') { path[len-1] = '\0'; --len; } return path; } static const char * custom_rm = NULL; int (*mytouch)(const char *filename) = NULL; int (*mydelete)(const char *filename) = NULL; int (*mychmod)(const char *filename, mode_t mode) = NULL; int (*mychown)(const char *filename, const char *owner, const char *group) = NULL; int (*mymove)(const char *filename, const char *newname) = NULL; int (*mycopy)(const char *filename, const char *newname) = NULL; move_data_t (*mytempmove)(const char *filename, const char *newname) = NULL; static int dry_touch(const char * filename); static int dry_delete(const char * filename); static int dry_chmod(const char * filename, mode_t mode); static int dry_chown(const char * filename, const char * owner, const char * group); static int dry_move(const char * filename, const char * newname); static int dry_copy(const char * filename, const char * newname); static move_data_t dry_mytempmove(const char *filename, const char *newname); static int moist_touch(const char * filename); static int moist_delete(const char * filename); static int moist_chmod(const char * filename, mode_t mode); static int moist_chown(const char * filename, const char * owner, const char * group); static int moist_move(const char * filename, const char * newname); static int moist_copy(const char * filename, const char * newname); static move_data_t moist_mytempmove(const char *filename, const char *newname); int init_file_utils(bool is_dry_run, const char * custom_rm_) { custom_rm = custom_rm_; if (is_dry_run) { mytouch = dry_touch; mydelete = dry_delete; mychmod = dry_chmod; mychown = dry_chown; mymove = dry_move; mycopy = dry_copy; mytempmove = dry_mytempmove; } else { mytouch = moist_touch; mydelete = moist_delete; mychmod = moist_chmod; mychown = moist_chown; mymove = moist_move; mycopy = moist_copy; mytempmove = moist_mytempmove; } return 0; } int deinit_file_utis() { mytouch = NULL; mydelete = NULL; mychmod = NULL; mychown = NULL; mymove = NULL; custom_rm = NULL; return 0; } char mode_type_to_char(mode_t m) { switch (m & S_IFMT) { case S_IFREG: return '-'; // regular file case S_IFDIR: return 'd'; // directory case S_IFCHR: return 'c'; // character device case S_IFBLK: return 'b'; // block device case S_IFIFO: return 'p'; // fifo (pipe) case S_IFLNK: return 'l'; // symbolic link case S_IFSOCK: return 's'; // socket default: return '?'; // unknown } } mode_t char_to_mode_type(const char c) { switch (c) { case '-': return S_IFREG; // regular file case 'd': return S_IFDIR; // directory case 'c': return S_IFCHR; // character device case 'b': return S_IFBLK; // block device case 'p': return S_IFIFO; // fifo (pipe) case 'l': return S_IFLNK; // symbolic link case 's': return S_IFSOCK; // socket default: return 0; // unknown } } char * mode_to_str(mode_t mode, char * buffer) { buffer[0] = mode_type_to_char(mode); buffer[1] = (mode & S_IRUSR) ? 'r' : '-'; buffer[2] = (mode & S_IWUSR) ? 'w' : '-'; buffer[3] = (mode & S_IXUSR) ? 'x' : '-'; buffer[4] = (mode & S_IRGRP) ? 'r' : '-'; buffer[5] = (mode & S_IWGRP) ? 'w' : '-'; buffer[6] = (mode & S_IXGRP) ? 'x' : '-'; buffer[7] = (mode & S_IROTH) ? 'r' : '-'; buffer[8] = (mode & S_IWOTH) ? 'w' : '-'; buffer[9] = (mode & S_IXOTH) ? 'x' : '-'; buffer[10] = '\0'; return buffer; } mode_t str_to_mode(const char *permissions) { mode_t mode = 0; mode |= char_to_mode_type(permissions[0]); mode |= (permissions[1] == 'r') ? S_IRUSR : 0; mode |= (permissions[2] == 'w') ? S_IWUSR : 0; mode |= (permissions[3] == 'x') ? S_IXUSR : 0; mode |= (permissions[4] == 'r') ? S_IRGRP : 0; mode |= (permissions[5] == 'w') ? S_IWGRP : 0; mode |= (permissions[6] == 'x') ? S_IXGRP : 0; mode |= (permissions[7] == 'r') ? S_IROTH : 0; mode |= (permissions[8] == 'w') ? S_IWOTH : 0; mode |= (permissions[9] == 'x') ? S_IXOTH : 0; return mode; } // --- Dry implementations static int dry_touch(const char * filename) { size_t len = strlen(filename); if (filename[len-1] != '/' ) { notice("touch '%s' (dry; subsequent stats will fail)", filename); } else { notice("mkdir '%s' (dry; subsequent stats will fail)", filename); } return 0; } static int dry_delete(const char * filename) { notice("delete '%s'", filename); return 0; } static int dry_chmod(const char * filename, mode_t mode) { char buf[11]; notice("chmod '%s' (%s)", filename, mode_to_str(mode, buf)); return 0; } static int dry_chown(const char * filename, const char * owner, const char * group) { notice("chown '%s' (%s:%s)", filename, owner, group); return 0; } static int dry_move(const char * filename, const char * newname) { notice("rename '%s' (-> '%s')", filename, newname); return 0; } static int dry_copy(const char * filename, const char * newname) { notice("copy '%s' (as '%s')", filename, newname); return 0; } static move_data_t dry_mytempmove(const char * filename, const char * newname) { notice("swap detected in a dry-run ('%s' <-> '%s'); the following logs will be inaccurate", filename, newname); return (move_data_t) { .orig_name = strdup(filename), .curt_name = strdup(filename), .dest_name = strdup(newname), }; } // --- Moist implementations static int moist_touch(const char * filename) { size_t len = strlen(filename); if (filename[len-1] != '/' ) { FILE * f = fopen(filename, "w"); CHECK_OPEN(f, filename, return 1); fclose(f); } else { mkdir(filename, 0777); } return 0; } static int moist_delete(const char * filename) { /* Theres the situation where the user attempts * to delete a recursively listed directory. * He would delete all references to the directory * (otherwise it would be a stat error too). * Since the entry came somewhere, its reasonably safe to assume the file should exist, * and if it does not, its not an actual error. * Therefor, we simply make deletes on a missing file a nop. */ if (access(filename, F_OK)) { return 0; } if (custom_rm) { size_t cmd_len = strlen(custom_rm) + sizeof(' ') + sizeof('\'')*2 + strlen(filename) + 1 ; char cmd[cmd_len]; snprintf(cmd, cmd_len, "%s '%s'", custom_rm, filename); int result = system(cmd); if (result == 127 || result == -1 || (WIFEXITED(result) && WEXITSTATUS(result) != 0)) { errorn(E_FILE_DELETE, filename); return 1; } } else { if (remove_all(filename)) { errorn(E_FILE_DELETE, filename); return 1; } } return 0; } static int moist_chmod(const char * filename, mode_t mode) { if (chmod(filename, mode) != 0) { errorn(E_FILE_ACCESS, filename); return 1; } return 0; } static int moist_chown(const char * filename, const char * owner, const char * group) { uid_t uid = -1; gid_t gid = -1; struct passwd * usr = getpwnam(owner); if (!usr) { errorn(E_NO_USER, owner); return 1; } uid = usr->pw_uid; struct group * grp = getgrnam(group); if (!grp) { errorn(E_NO_GROUP, group); return 1; } gid = grp->gr_gid; if (chown(filename, uid, gid)) { errorn(E_FILE_CHOWN, filename); return 1; } return 0; } static int moist_move(const char * filename, const char * newname) { if (rename(filename, newname) != 0) { errorn(E_FILE_MOVE, filename, newname); return 1; } return 0; } static int moist_copy(const char * filename, const char * newname) { // Is using system for copying terrible? yes. // Do I have know a better solution thats not filled with footguns? no. size_t cmd_len = strlen("cp -a") + sizeof(' ') + sizeof('\'')*2 + strlen(filename) + sizeof(' ') + sizeof('\'')*2 + strlen(newname) + 1 ; char cmd[cmd_len]; snprintf(cmd, cmd_len, "cp -a '%s' '%s'", filename, newname); int result = system(cmd); if (result == 127 || result == -1 || (WIFEXITED(result) && WEXITSTATUS(result) != 0)) { errorn(E_FILE_COPY, filename, newname); return 1; } return 0; } static move_data_t moist_mytempmove(const char * filename, const char * newname) { move_data_t r = { .orig_name = NULL, .curt_name = NULL, .dest_name = NULL, }; const int COLISION_DIGITS = 3; const size_t buf_size = strlen(filename) + COLISION_DIGITS + sizeof("~"); char buffer[buf_size]; unsigned n = 0; do { snprintf(buffer, buf_size, "%s~%d", filename, n++); if (n > 10 * COLISION_DIGITS) { goto end; } } while (!access(buffer, F_OK)); if (mymove(filename, buffer)) { goto end; } r.orig_name = strdup(filename); r.curt_name = strdup(buffer); r.dest_name = strdup(newname); end: return r; }