#define _GNU_SOURCE
#define _NO_PASARAN
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#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;
}