#include #include #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(__FreeBSD__) # include #else # include #endif #include "thpool.h" #include "vec.h" #define CHMOD 0666 typedef enum _OperationStatus { SENDFILE_FAIL = -4, STAT_FAIL = -3, SOURCE_OPEN_FAIL = -2, DESTINATION_OPEN_FAIL = -1, SUCCESS = 0, } OperationStatus; typedef enum _QueueType { FILE_COPY, MKDIR, } QueueType; typedef struct _QueueItem { char *source, *destination; QueueType type; bool done; size_t requirement; } QueueItem; typedef struct { pthread_t thread_id; bool is_valid; } ThreadInfo; struct { t_vec file_queue; bool error; } g_state; void generate_queue(char *source, char *destination) { static size_t last_dir = -1; DIR *dir; struct dirent *entry; if (!(dir = opendir(source))) return; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_DIR) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char source_path[1024]; char dest_path[1024]; snprintf(source_path, sizeof(source_path), "%s/%s", source, entry->d_name); snprintf(dest_path, sizeof(dest_path), "%s/%s", destination, entry->d_name); QueueItem item; item.source = strdup(source_path); item.destination = strdup(dest_path); item.type = MKDIR; item.done = false; item.requirement = last_dir; last_dir = g_state.file_queue.len; vec_push(&g_state.file_queue, &item); generate_queue(source_path, dest_path); } else { char source_path[1024]; char dest_path[1024]; snprintf(source_path, sizeof(source_path), "%s/%s", source, entry->d_name); snprintf(dest_path, sizeof(dest_path), "%s/%s", destination, entry->d_name); QueueItem item; item.source = strdup(source_path); item.destination = strdup(dest_path); item.type = FILE_COPY; item.done = false; item.requirement = last_dir; vec_push(&g_state.file_queue, &item); } } closedir(dir); } void print_operation_status(OperationStatus status) { switch (status) { case SENDFILE_FAIL: perror("Failed sendfile()"); break; case STAT_FAIL: perror("Failed stat()"); break; case SOURCE_OPEN_FAIL: perror("Failed source open"); break; case DESTINATION_OPEN_FAIL: perror("Failed destination open"); break; case SUCCESS: puts("Done!"); break; } } #define FAILED(x) ((x) < 0) #define OK(x) ((x) >= 0) #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define BYTE_TO_BINARY(byte) \ ((byte)&0x80 ? '1' : '0'), ((byte)&0x40 ? '1' : '0'), ((byte)&0x20 ? '1' : '0'), \ ((byte)&0x10 ? '1' : '0'), ((byte)&0x08 ? '1' : '0'), ((byte)&0x04 ? '1' : '0'), \ ((byte)&0x02 ? '1' : '0'), ((byte)&0x01 ? '1' : '0') OperationStatus copy_file(const char *source, const char *dest, bool preserve_mode, bool preserve_time, bool preserve_guid, bool overwrite) { struct stat source_stats; mode_t mode = CHMOD; int input_fd, output_fd; if (FAILED(input_fd = open(source, O_RDONLY))) return SOURCE_OPEN_FAIL; if (FAILED( output_fd = open(dest, O_WRONLY | O_CREAT | (overwrite ? 0 : O_EXCL), 0666))) { close(input_fd); return DESTINATION_OPEN_FAIL; } if (FAILED(fstat(input_fd, &source_stats))) { close(input_fd); close(output_fd); return STAT_FAIL; } if (preserve_mode) mode = source_stats.st_mode; #if defined(__APPLE__) || defined(__FreeBSD__) int result = fcopyfile(input, output, 0, COPYFILE_ALL); #else off_t copied_bytes = 0; int result = sendfile(output_fd, input_fd, &copied_bytes, source_stats.st_size); if (FAILED(result)) { printf("Failed sneedfile(%d, %d, %p (%ld), %ld) on file %s -> %s\n", output_fd, input_fd, &copied_bytes, copied_bytes, source_stats.st_size, source, dest); } #endif close(input_fd); close(output_fd); if (result < 0) return SENDFILE_FAIL; if (preserve_time) { struct utimbuf new_time; new_time.actime = source_stats.st_atime; new_time.modtime = source_stats.st_mtime; utime(dest, &new_time); } return 0; } int is_regular_file(const char *path) { struct stat path_stat; stat(path, &path_stat); return S_ISREG(path_stat.st_mode); } int handle_queue_item(QueueItem *item) { int ret = 0; if (item->type == FILE_COPY) { struct stat st; stat(item->source, &st); if (S_ISLNK(st.st_mode)) { char link_target[1024]; ssize_t link_size = readlink(item->source, link_target, sizeof(link_target)); if (link_size == -1) { perror("Failed to read symbolic link"); return -1; } link_target[link_size] = '\0'; if (symlink(link_target, item->destination) == -1) { perror("Failed to create symbolic link"); return -1; } } else if (S_ISREG(st.st_mode)) { ret = copy_file(item->source, item->destination, false, false, false, true); if (FAILED(ret)) print_operation_status(ret); } else if (S_ISFIFO(st.st_mode)) { if (mkfifo(item->destination, st.st_mode) == -1) { perror("Failed to create FIFO"); return -1; } } } else ret = mkdir(item->destination, 0777); if (OK(ret)) item->done = true; if (FAILED(ret)) { printf("Failed with %d\n", ret); } return ret; } void thread_function(void *arg) { QueueItem *j = arg; if (j->requirement != -1) while (!((QueueItem *)vec_get(&g_state.file_queue, j->requirement))->done && !g_state.error) usleep(0); if (g_state.error) return; // FIXME: Show this if -v // printf("%s -> %s [%d]\n", j->source, j->destination, j->type); int queue_item_ret = handle_queue_item(j); if (FAILED(queue_item_ret)) { printf("Queue item ret: %d\n", queue_item_ret); perror("handle_queue_item"); g_state.error = true; } return; } int main(int argc, char **argv) { if (argc < 3) { fputs("First argument is source, second argument is destination.", stderr); return EXIT_FAILURE; } if (FAILED(vec_new(&g_state.file_queue, 20, sizeof(QueueItem)))) { fputs("Failed creating queue.", stderr); return EXIT_FAILURE; } g_state.error = false; if (is_regular_file(argv[1])) { print_operation_status(copy_file(argv[1], argv[2], false, false, false, false)); return 0; } generate_queue(argv[1], argv[2]); mkdir(argv[2], 0777); unsigned num_cores = get_nprocs(); threadpool tp = thpool_init(num_cores); for (unsigned i = 0; i < g_state.file_queue.len; i++) thpool_add_work(tp, thread_function, vec_get(&g_state.file_queue, i)); thpool_wait(tp); if (g_state.error) { return EXIT_FAILURE; } thpool_destroy(tp); return EXIT_SUCCESS; }