2015-02-10 21:11:15 -05:00
|
|
|
/*
|
|
|
|
* backly - A simple directory cloner
|
|
|
|
* Copyright (C) 2015 L. Bradley LaBoon <me@bradleylaboon.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
#include <dirent.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
void printUsage(FILE *stream)
|
|
|
|
{
|
|
|
|
fprintf(stream, "Usage: backly [options] <srcdir> <destdir>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void printHelp()
|
|
|
|
{
|
|
|
|
printUsage(stdout);
|
|
|
|
printf("Turn destdir into a clone of srcdir.\n\n");
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
printf("Options:\n");
|
2017-05-10 20:31:40 -04:00
|
|
|
printf(" --test\t\tRun in test mode\n");
|
|
|
|
printf(" --noremove\tDon't remove any files in destdir\n");
|
|
|
|
printf(" --help\t\tPrint this help and quit\n\n");
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 21:11:15 -05:00
|
|
|
printf("Report bugs to L. Bradley LaBoon <me@bradleylaboon.com>\n");
|
|
|
|
printf("backly home page: <http://git.bradleylaboon.com/backly.git>\n");
|
2015-02-10 19:09:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void removeMissing(char *src, int srcPrefix, char *dest, int destPrefix, int testMode)
|
|
|
|
{
|
|
|
|
int srcLen = 0, destLen = 0;
|
|
|
|
while (src[srcLen])
|
|
|
|
srcLen++;
|
|
|
|
while (dest[destLen])
|
|
|
|
destLen++;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
DIR *destDir = opendir(dest);
|
|
|
|
if (destDir == NULL) {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", dest, strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// If the entire directory doesn't exist in src, remove it from dest
|
|
|
|
DIR *srcDir = opendir(src);
|
|
|
|
if (srcDir == NULL) {
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
printf("- %s", dest + destPrefix);
|
|
|
|
fflush(stdout);
|
|
|
|
if (testMode == 0) {
|
|
|
|
int pid = fork();
|
|
|
|
if (pid == -1) {
|
|
|
|
fprintf(stderr, "Could not fork: %s\n", strerror(errno));
|
|
|
|
closedir(destDir);
|
|
|
|
return;
|
|
|
|
} else if (pid == 0) {
|
|
|
|
execlp("rm", "rm", "-rf", dest, (char *) NULL);
|
|
|
|
} else {
|
|
|
|
wait(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
closedir(destDir);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", src, strerror(errno));
|
|
|
|
closedir(destDir);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(srcDir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Look at each item in the folder
|
|
|
|
struct dirent *item;
|
|
|
|
while ((item = readdir(destDir)) != NULL) {
|
|
|
|
// Ignore . and .. references
|
|
|
|
if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0)
|
|
|
|
continue;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
int itemLen = 0;
|
|
|
|
while (item->d_name[itemLen])
|
|
|
|
itemLen++;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// +2 because a trailing slash might be added
|
|
|
|
char itemSrc[srcLen + itemLen + 2];
|
|
|
|
char itemDest[destLen + itemLen + 2];
|
|
|
|
sprintf(itemSrc, "%s%s", src, item->d_name);
|
|
|
|
sprintf(itemDest, "%s%s", dest, item->d_name);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Test if item is a directory
|
|
|
|
int itemIsDir = 1;
|
|
|
|
DIR *testDir = opendir(itemDest);
|
|
|
|
if (testDir == NULL && errno == ENOTDIR)
|
|
|
|
itemIsDir = 0;
|
|
|
|
else if (testDir != NULL)
|
|
|
|
closedir(testDir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (itemIsDir == 1) {
|
|
|
|
// If it's a directory, append trailing slashes and recurse
|
|
|
|
itemSrc[srcLen + itemLen] = '/';
|
|
|
|
itemSrc[srcLen + itemLen + 1] = '\0';
|
|
|
|
itemDest[destLen + itemLen] = '/';
|
|
|
|
itemDest[destLen + itemLen + 1] = '\0';
|
|
|
|
removeMissing(itemSrc, srcPrefix, itemDest, destPrefix, testMode);
|
|
|
|
} else {
|
|
|
|
// If the file doesn't exist in src, remove it from dest
|
|
|
|
FILE *srcFile = fopen(itemSrc, "r");
|
|
|
|
if (srcFile == NULL) {
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
printf("- %s", itemDest + destPrefix);
|
|
|
|
fflush(stdout);
|
|
|
|
if (testMode == 0) {
|
|
|
|
int pid = fork();
|
|
|
|
if (pid == -1) {
|
|
|
|
fprintf(stderr, "Could not fork: %s\n", strerror(errno));
|
|
|
|
continue;
|
|
|
|
} else if (pid == 0) {
|
|
|
|
execlp("rm", "rm", "-f", itemDest, (char *) NULL);
|
|
|
|
} else {
|
|
|
|
wait(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", itemSrc, strerror(errno));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(srcFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
closedir(destDir);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void copyNew(char *src, int srcPrefix, char *dest, int destPrefix, int testMode)
|
|
|
|
{
|
|
|
|
int srcLen = 0, destLen = 0;
|
|
|
|
while (src[srcLen])
|
|
|
|
srcLen++;
|
|
|
|
while(dest[destLen])
|
|
|
|
destLen++;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
DIR *srcDir = opendir(src);
|
|
|
|
if (srcDir == NULL) {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", src, strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// If the dest directory doesn't exist, create it
|
|
|
|
DIR *destDir = opendir(dest);
|
|
|
|
if (destDir == NULL) {
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
printf("+ %s", dest + destPrefix);
|
|
|
|
fflush(stdout);
|
|
|
|
if (testMode == 0) {
|
|
|
|
int pid = fork();
|
|
|
|
if (pid == -1) {
|
|
|
|
fprintf(stderr, "Could not fork: %s:\n", strerror(errno));
|
|
|
|
closedir(srcDir);
|
|
|
|
return;
|
|
|
|
} else if (pid == 0) {
|
|
|
|
execlp("mkdir", "mkdir", dest, (char *) NULL);
|
|
|
|
} else {
|
|
|
|
wait(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", dest, strerror(errno));
|
|
|
|
closedir(srcDir);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(destDir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Look at each item in the folder
|
|
|
|
struct dirent *item;
|
|
|
|
while ((item = readdir(srcDir)) != NULL) {
|
|
|
|
if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0)
|
|
|
|
continue;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
int itemLen = 0;
|
|
|
|
while (item->d_name[itemLen])
|
|
|
|
itemLen++;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
char itemSrc[srcLen + itemLen + 2];
|
|
|
|
char itemDest[destLen + itemLen + 2];
|
|
|
|
sprintf(itemSrc, "%s%s", src, item->d_name);
|
|
|
|
sprintf(itemDest, "%s%s", dest, item->d_name);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Test if item is a directory
|
|
|
|
int itemIsDir = 1;
|
|
|
|
DIR *testDir = opendir(itemSrc);
|
|
|
|
if (testDir == NULL && errno == ENOTDIR)
|
|
|
|
itemIsDir = 0;
|
|
|
|
else if (testDir != NULL)
|
|
|
|
closedir(testDir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (itemIsDir == 1) {
|
|
|
|
// If it's a directory, recurse
|
|
|
|
itemSrc[srcLen + itemLen] = '/';
|
|
|
|
itemSrc[srcLen + itemLen + 1] = '\0';
|
|
|
|
itemDest[destLen + itemLen] = '/';
|
|
|
|
itemDest[destLen + itemLen + 1] = '\0';
|
|
|
|
copyNew(itemSrc, srcPrefix, itemDest, destPrefix, testMode);
|
|
|
|
} else {
|
|
|
|
// Check if file exists in dest
|
|
|
|
int needToCopy = 0;
|
|
|
|
FILE *destFile = fopen(itemDest, "r");
|
|
|
|
if (destFile == NULL) {
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
needToCopy = 1;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Could not open %s: %s\n", itemDest, strerror(errno));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(destFile);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Check if file size or modified time is different
|
|
|
|
struct stat srcStat, destStat;
|
|
|
|
if (stat(itemSrc, &srcStat) == -1) {
|
|
|
|
fprintf(stderr, "Could not stat %s\n", itemSrc);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (stat(itemDest, &destStat) == -1) {
|
|
|
|
fprintf(stderr, "Could not stat %s\n", itemDest);
|
|
|
|
continue;
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (srcStat.st_size != destStat.st_size)
|
|
|
|
needToCopy = 1;
|
|
|
|
else if (srcStat.st_mtime > destStat.st_mtime)
|
|
|
|
needToCopy = 1;
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Only copy file if it doesn't exist or has changed
|
|
|
|
if (needToCopy == 1) {
|
2015-06-11 23:02:52 -04:00
|
|
|
printf("+ %s ", itemSrc + srcPrefix);
|
2015-02-10 19:09:16 -05:00
|
|
|
fflush(stdout);
|
|
|
|
if (testMode == 0) {
|
|
|
|
int pid = fork();
|
|
|
|
if (pid == -1) {
|
|
|
|
fprintf(stderr, "Could not fork: %s\n", strerror(errno));
|
|
|
|
continue;
|
|
|
|
} else if (pid == 0) {
|
2017-01-19 20:11:09 -05:00
|
|
|
execlp("cp", "cp", "-f", "-P", "--preserve=timestamps,links", itemSrc, itemDest, (char *) NULL);
|
2015-02-10 19:09:16 -05:00
|
|
|
} else {
|
|
|
|
struct stat srcStat, destStat;
|
|
|
|
double pDone = 0;
|
2015-06-11 23:02:52 -04:00
|
|
|
int numPrinted = 0;
|
2015-02-10 19:09:16 -05:00
|
|
|
if (stat(itemSrc, &srcStat) != -1) {
|
|
|
|
while (waitpid(pid, NULL, WNOHANG) == 0) {
|
|
|
|
if (stat(itemDest, &destStat) == -1)
|
|
|
|
continue;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
pDone = (double)destStat.st_size / (double)srcStat.st_size;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
for (int i = 0; i < numPrinted; i++)
|
|
|
|
printf("\b \b");
|
|
|
|
numPrinted = printf("%.2lf%%", pDone * 100);
|
|
|
|
fflush(stdout);
|
2020-03-24 17:01:48 -04:00
|
|
|
usleep(50000);
|
2015-02-10 19:09:16 -05:00
|
|
|
}
|
|
|
|
}
|
2015-06-11 23:02:52 -04:00
|
|
|
for (int i = 0; i < numPrinted; i++)
|
|
|
|
printf("\b \b");
|
|
|
|
printf("100.00%%");
|
|
|
|
fflush(stdout);
|
2015-02-10 19:09:16 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
closedir(srcDir);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
int srcArg = 0, destArg = 0;
|
2017-05-10 20:31:40 -04:00
|
|
|
int testMode = 0, remove = 1;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Parse arguments
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
if (strcmp(argv[i], "--help") == 0) {
|
|
|
|
printHelp();
|
|
|
|
exit(0);
|
|
|
|
} else if (strcmp(argv[i], "--test") == 0) {
|
|
|
|
testMode = 1;
|
|
|
|
printf("***Operating in test mode.***\n");
|
2017-05-10 20:31:40 -04:00
|
|
|
} else if (strcmp(argv[i], "--noremove") == 0) {
|
|
|
|
remove = 0;
|
2015-02-10 19:09:16 -05:00
|
|
|
} else if (srcArg == 0) {
|
|
|
|
srcArg = i;
|
|
|
|
} else {
|
|
|
|
destArg = i;
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (srcArg == 0 || destArg == 0) {
|
|
|
|
printUsage(stderr);
|
|
|
|
fprintf(stderr, "Run 'backly --help' for more information.\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Determine argument lengths
|
|
|
|
int srcLen = 0, destLen = 0;
|
|
|
|
while (argv[srcArg][srcLen])
|
|
|
|
srcLen++;
|
|
|
|
while (argv[destArg][destLen])
|
|
|
|
destLen++;
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Add trailing slashes to directory arguments if necessary
|
|
|
|
char srcDir[srcLen + 2], destDir[destLen + 2];
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (argv[srcArg][srcLen - 1] == '/')
|
|
|
|
sprintf(srcDir, "%s", argv[srcArg]);
|
|
|
|
else
|
|
|
|
sprintf(srcDir, "%s/", argv[srcArg]);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
if (argv[destArg][destLen - 1] == '/')
|
|
|
|
sprintf(destDir, "%s", argv[destArg]);
|
|
|
|
else
|
|
|
|
sprintf(destDir, "%s/", argv[destArg]);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Check if directories exist and can be accessed
|
|
|
|
DIR *dir = opendir(srcDir);
|
|
|
|
if (dir == NULL) {
|
|
|
|
fprintf(stderr, "Could not open source: %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
closedir(dir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
dir = opendir(destDir);
|
|
|
|
if (dir == NULL) {
|
|
|
|
fprintf(stderr, "Could not open destination: %s\n", strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
closedir(dir);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Remove files from the destination that don't exist in the source
|
2017-05-10 20:31:40 -04:00
|
|
|
if (remove)
|
|
|
|
removeMissing(srcDir, strlen(srcDir), destDir, strlen(destDir), testMode);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
// Copy new files and overwrite existing files if different
|
|
|
|
copyNew(srcDir, strlen(srcDir), destDir, strlen(destDir), testMode);
|
2017-01-19 20:07:35 -05:00
|
|
|
|
2015-02-10 19:09:16 -05:00
|
|
|
return 0;
|
|
|
|
}
|