271 lines
6.5 KiB
C
271 lines
6.5 KiB
C
#include <dirent.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysinfo.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
|
|
#include <fcntl.h>
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
# include <copyfile.h>
|
|
#else
|
|
# include <sys/sendfile.h>
|
|
#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;
|
|
}
|