← index
skip-cp.c
Source: C2x.Examples/src/skip-cp.c
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static int in_output_dir(const char path[static 1], const char output_dir[static 1]) {
    size_t n = strlen(output_dir);
    if (strncmp(path, output_dir, n) != 0)
        return 0;
    return path[n] == '\0' || path[n] == '/';
}

size_t cp_dir(size_t *counter, const char input[static 1], const char output[static 1], const char output_dir[static 1], const char input_dir[static 1], const char output_name[static 1]) {

    if (strcmp(input, output) == 0)
        return *counter;

    if (mkdir(output, 0755) == -1 && errno != EEXIST) {
        fprintf(stderr, "Error creating directory: %s\n", output);
        exit(EXIT_FAILURE);
    }
    DIR *dir = opendir(input);
    if (dir == nullptr) {
        fprintf(stderr, "Error opening directory: %s\n", input);
        return *counter;
    }
    struct dirent *e;
    while ((e = readdir(dir)) != nullptr) {
        if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0) {
            continue;
        }
        if (strcmp(input, input_dir) == 0 && strcmp(e->d_name, output_name) == 0) {
            continue;
        }
        char *full = nullptr;
        if (asprintf(&full, "%s/%s", input, e->d_name) == -1)
            continue;

        if (e->d_type == DT_DIR) {
            if (!in_output_dir(full, output_dir)) {
                int same_dir = 0;
                struct stat st_in, st_out;
                if (stat(full, &st_in) == 0 && stat(output_dir, &st_out) == 0) {
                    if (st_in.st_dev == st_out.st_dev && st_in.st_ino == st_out.st_ino)
                        same_dir = 1;
                }

                if (!same_dir) {
                    char *out = nullptr;
                    if (asprintf(&out, "%s/%s", output, e->d_name) != -1) {
                        if (strcmp(full, out) != 0)
                            cp_dir(counter, full, out, output_dir, input_dir, output_name);
                        free(out);
                    }
                }
            }
        } else {
            char *fout = nullptr;
            if (asprintf(&fout, "%s/%s", output, e->d_name) != -1) {
                if (access(fout, F_OK) != 0) {
                    ++(*counter);
                    int source_fd = open(full, O_RDONLY);
                    if (source_fd < 0) {
                        fprintf(stderr, "could not open input file: %s\n", full);
                    } else {
                        int dest_fd = open(fout, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                        if (dest_fd < 0) {
                            fprintf(stderr, "could not open output file: %s \n", fout);
                            close(source_fd);
                        } else {
                            struct stat dst;
                            fstat(source_fd, &dst);
                            sendfile(dest_fd, source_fd, nullptr, (size_t)dst.st_size);
                            close(source_fd);
                            close(dest_fd);
                            printf("%s -> %s\n", full, fout);
                        }
                    }
                }
                free(fout);
            }
        }
        free(full);
    }
    closedir(dir);
    return *counter;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "Error invalid args use: %s <input> <output>\n", argv[0]);
        return EXIT_FAILURE;
    }

    size_t counter = 0;
    int created = 0;
    int err = mkdir(argv[2], 0755);
    if (err == -1 && errno != EEXIST) {
        fprintf(stderr, "Error on creation of directory\n");
        return EXIT_FAILURE;
    }
    if (err == 0)
        created = 1;

    char *real_in = realpath(argv[1], nullptr);
    char *real_out = realpath(argv[2], nullptr);
    if (real_in == nullptr || real_out == nullptr) {
        fprintf(stderr, "Error resolving path\n");
        free(real_in);
        free(real_out);
        return EXIT_FAILURE;
    }

    if (in_output_dir(real_out, real_in)) {
        fprintf(stderr, "Error: Destination is inside Source!\n");
        if (created)
            rmdir(argv[2]);
        free(real_in);
        free(real_out);
        return EXIT_FAILURE;
    }

    const char *output_name = strrchr(real_out, '/');
    if (output_name == nullptr)
        output_name = real_out;
    else
        ++output_name;

    counter = cp_dir(&counter, real_in, real_out, real_out, real_in, output_name);
    free(real_in);
    free(real_out);
    printf("%ld files\n", counter);
    return EXIT_SUCCESS;
}