cpfast/cpfast.c
Slendi 414decf167
Initial commit.
Signed-off-by: Slendi <slendi@socopon.com>
2023-09-08 01:13:04 +03:00

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;
}