#define _GNU_SOURCE #define _NO_PASARAN #include #include #include #include #include #include #include #include #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif void print_usage() { printf("Usage: backup source_dir destination_dir\n"); } int f_copy(char *src_file, char *dest_file, mode_t mode) { char tail[9]; char middle_file[PATH_MAX + 1]; int i; for (i = 0; i < 8; ++i) { tail[i] = (char)(rand() % 10) + '0'; } tail[8] = '\0'; int res = snprintf(middle_file, PATH_MAX, "%s%s", dest_file, tail); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while concatenating intermediate file '%s%s'; omitted\n", \ dest_file, tail); return 666; } FILE *src_FILE = fopen(src_file, "r"); FILE *dest_FILE = fopen(middle_file, "w"); if (!src_FILE) { return 1; } if (!dest_FILE) { return 2; } int ch; while (1) { ch = getc(src_FILE); if (ch == EOF) { break; } putc(ch, dest_FILE); } fclose(src_FILE); fclose(dest_FILE); if (chmod(middle_file, mode)) { fprintf(stderr, "backup: unable to chmod '%s': ", dest_file); perror(NULL); } unlink(dest_file); rename(middle_file, dest_file); return 0; } int f_case_copy(char *src_file, char *dest_file) { struct stat src_stat, dest_stat; if (stat(src_file, &src_stat)) { fprintf(stderr, "backup: failed to get file status on '%s': ", src_file); perror(NULL); return -1; } int dest_stat_res; dest_stat_res = stat(dest_file, &dest_stat); if (dest_stat_res || (!S_ISDIR(dest_stat.st_mode) && dest_stat.st_mtime < src_stat.st_mtime)) { int res = f_copy(src_file, dest_file, src_stat.st_mode); switch (res) { case 1: fprintf(stderr, "backup: failed to open file '%s' for reading: ", src_file); perror(NULL); return 1; break; case 2: fprintf(stderr, "backup: failed to open file '%s' for writing: ", dest_file); perror(NULL); return 2; break; } } else { if (S_ISDIR(dest_stat.st_mode)) { fprintf(stderr, "backup: unable to replace directory '%s' by file '%s'", dest_file, src_file); perror(NULL); return 3; } } return 0; } void rec_copy(char *src_dir_s, char *dest_dir_s) { DIR *src_dir; struct stat src_dir_stat, dest_dir_stat; if (!(src_dir = opendir(src_dir_s))) { fprintf(stderr, "backup: skipping directory '%s': ", src_dir_s); perror(NULL); return; } if (stat(src_dir_s, &src_dir_stat)) { fprintf(stderr, "backup: failed to get directory status on '%s': ", src_dir_s); perror(NULL); return; } if (stat(dest_dir_s, &dest_dir_stat)) { if (mkdir(dest_dir_s, src_dir_stat.st_mode)) { fprintf(stderr, "backup: failed to get status on '%s' and create such a directory at the same time: ", dest_dir_s); perror(NULL); return; } else { if (chmod(dest_dir_s, src_dir_stat.st_mode)) { fprintf(stderr, "backup: unable to chmod '%s': ", dest_dir_s); perror(NULL); } } } else { if (!S_ISDIR(dest_dir_stat.st_mode)) { fprintf(stderr, "backup: unable to copy to '%s', which exists and is not a directory: ", dest_dir_s); perror(NULL); return; } } struct dirent *src_dirent; while ((src_dirent = readdir(src_dir)) != NULL) { struct stat f_stat; char name_s[PATH_MAX + 1]; int res = snprintf(name_s, PATH_MAX, "%s/%s", src_dir_s, src_dirent->d_name); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while concatenating '%s/%s'; omitted\n", \ src_dir_s, src_dirent->d_name); continue; } stat(name_s, &f_stat); if (S_ISDIR(f_stat.st_mode)) { if (strcmp(src_dirent->d_name, ".") && strcmp(src_dirent->d_name, "..")){ char name2_s[PATH_MAX + 1]; int res = snprintf(name2_s, PATH_MAX, "%s/%s", dest_dir_s, src_dirent->d_name); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while concatenating '%s/%s'; omitted\n", \ dest_dir_s, src_dirent->d_name); continue; } rec_copy(name_s, name2_s); } } else { char name2_s[PATH_MAX + 1]; int res = snprintf(name2_s, PATH_MAX, "%s/%s", dest_dir_s, src_dirent->d_name); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while concatenating '%s/%s'; omitted\n", \ dest_dir_s, src_dirent->d_name); continue; } f_case_copy(name_s, name2_s); } } closedir(src_dir); } int main(int argc, char **argv) { if (argc != 3) { print_usage(); exit(-1); } char src_dir[PATH_MAX + 1]; char dest_dir[PATH_MAX + 1]; if (!realpath(argv[1], src_dir)) { fprintf(stderr, "backup: '%s': ", src_dir); perror(NULL); exit(-1); } if (argv[2][0] != '/') { char *cwd = get_current_dir_name(); int res = snprintf(dest_dir, PATH_MAX, "%s/%s", cwd, argv[2]); free(cwd); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while concatenating '%s/%s'; omitted\n", \ cwd, argv[2]); return -1; } } else { int res = snprintf(dest_dir, PATH_MAX, "%s", argv[2]); if (res < 0 || res >= PATH_MAX) { fprintf(stderr, \ "backup: path length limit exceeded while dealing with '%s'; omitted\n", \ argv[2]); return -1; } } rec_copy(src_dir, dest_dir); return 0; }