Last active
January 8, 2017 23:09
-
-
Save ibaned/de64467ee8ac7896db1df74f92b7c161 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright 2017 Sandia Corporation | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright notice, | |
this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright notice, | |
this list of conditions and the following disclaimer in the documentation | |
and/or other materials provided with the distribution. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR | |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
Author: Dan Ibanez | |
This utility is my replacement for a specific use of rsync, namely | |
making a backup of a directory within the same filesystem. | |
the idea is that only files which are somehow newer in the | |
first directory should be copied to the second. | |
likewise for subdirectories, etc. | |
my problems with rsync are these: | |
1) if I say backup "foo" into "bar" and "bar" already exists, | |
it will backup into "bar/foo". this code doesn't do that. | |
2) it keeps copying files even though they are not newer in the | |
first directory. here we start by comparing mtimes, and | |
equal mtimes means the file is the same. | |
In addition to issues with rsync, the first version of dansync had | |
issues backing up in the reverse direction, because files would always | |
be newer. Thus the call to utimes() here ensures we can do: | |
dansync foo bar | |
dansync bar foo | |
dansync foo bar | |
and that last call will not do anything. | |
The target use case here is I have directories containing research papers | |
or research results, which are replicated across all my computers, | |
and a thumb drive. | |
This lets me sync new files from each computer onto the drive, and from the | |
drive onto the computers. | |
This is not a revision control system, it is an archive synchronization system. | |
This is a POSIX-only program, to compile it simply run: | |
make dansync | |
*/ | |
#include <stdio.h> | |
#include <sys/stat.h> | |
#include <sys/time.h> | |
#include <dirent.h> | |
#include <limits.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <stdlib.h> | |
static void try_write_some(int fd, void const** data, size_t* size) | |
{ | |
char const* p; | |
ssize_t step; | |
p = *data; | |
errno = 0; | |
step = write(fd, p, *size); | |
if (step == -1) { | |
if (errno == EAGAIN) return; | |
fprintf(stderr, "write() failed: %s\n", strerror(errno)); | |
exit(-1); | |
} | |
*size -= (size_t)step; | |
p += step; | |
*data = p; | |
} | |
static void try_write(int fd, void const* data, size_t size) | |
{ | |
while (size) try_write_some(fd, &data, &size); | |
} | |
static size_t try_read(int fd, void* data, size_t bufsize) | |
{ | |
ssize_t step; | |
while (1) { | |
errno = 0; | |
step = read(fd, data, bufsize); | |
if (step == -1) { | |
if (errno == EAGAIN) continue; | |
fprintf(stderr, "write() failed: %s\n", strerror(errno)); | |
exit(-1); | |
} | |
break; | |
} | |
return (size_t) step; | |
} | |
static struct timeval timespec2timeval(struct timespec a) { | |
struct timeval b; | |
b.tv_sec = a.tv_sec; | |
/* sigh. so it seems that the filesystem I'm dealing with will | |
round down to the nearest two seconds when I call utimes() */ | |
if (b.tv_sec % 2) ++b.tv_sec; | |
b.tv_usec = (suseconds_t) (a.tv_nsec / 1000); | |
return b; | |
} | |
static void copy(char const* path0, char const* path1, struct stat path0_stat) { | |
int fd0; | |
int fd1; | |
char buffer[4096]; | |
size_t nbytes; | |
struct timeval times1[2]; | |
fprintf(stdout, "%s -> %s\n", path0, path1); | |
errno = 0; | |
fd0 = open(path0, O_RDONLY); | |
if (fd0 < 0) { | |
fprintf(stderr, "open(%s, O_RDONLY) failed: %s\n", | |
path0, strerror(errno)); | |
exit(-1); | |
} | |
fd1 = open(path1, O_WRONLY | O_CREAT | O_TRUNC, path0_stat.st_mode); | |
if (fd1 < 0) { | |
fprintf(stderr, "open(%s, O_WRONLY | O_CREAT | O_TRUNC) failed: %s\n", | |
path1, strerror(errno)); | |
exit(-1); | |
} | |
while ((nbytes = try_read(fd0, buffer, sizeof(buffer)))) { | |
try_write(fd1, buffer, nbytes); | |
} | |
if (close(fd0)) { | |
fprintf(stderr, "close(%s) failed: %s\n", path0, strerror(errno)); | |
exit(-1); | |
} | |
if (close(fd1)) { | |
fprintf(stderr, "close(%s) failed: %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
times1[0] = timespec2timeval(path0_stat.st_atimespec); | |
printf("overriding atime with %ld.%ldu\n", | |
(long) times1[0].tv_sec, | |
(long) times1[0].tv_usec); | |
times1[1] = timespec2timeval(path0_stat.st_mtimespec); | |
printf("overriding mtime with %ld.%ldu\n", | |
(long) times1[1].tv_sec, | |
(long) times1[1].tv_usec); | |
errno = 0; | |
if (utimes(path1, times1)) { | |
fprintf(stderr, "utimes(%s) failed: %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
} | |
static int is_newer_than(struct timespec this, struct timespec than) { | |
if (this.tv_sec == than.tv_sec) { | |
return this.tv_nsec > than.tv_nsec; | |
} | |
return this.tv_sec > than.tv_sec; | |
} | |
static int is_newer_than2(struct timespec this, struct timespec than) { | |
if (is_newer_than(this, than)) { | |
printf("%ld.%ld newer than %ld.%ld\n", (long) this.tv_sec, | |
(long) this.tv_nsec, (long) than.tv_sec, (long) than.tv_nsec); | |
return 1; | |
} | |
return 0; | |
} | |
static void dansync(char const* path0, char const* path1) { | |
struct stat path0_stat; | |
struct stat path1_stat; | |
DIR* dir0; | |
struct dirent* dirent0; | |
char path0_child[PATH_MAX]; | |
char path1_child[PATH_MAX]; | |
errno = 0; | |
if (stat(path0, &path0_stat)) { | |
fprintf(stderr, "could not stat(%s): %s\n", path0, strerror(errno)); | |
exit(-1); | |
} | |
if (S_ISDIR(path0_stat.st_mode)) { | |
errno = 0; | |
if (mkdir(path1, path0_stat.st_mode)) { | |
if (errno == EEXIST) { | |
errno = 0; | |
if (stat(path1, &path1_stat)) { | |
fprintf(stderr, "could not stat(%s): %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
if (path1_stat.st_mode != path0_stat.st_mode) { | |
errno = 0; | |
if (chmod(path1, path0_stat.st_mode)) { | |
fprintf(stderr, "could not chmod(%s): %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
fprintf(stdout, "mode(%s/) -> mode(%s/)\n", path0, path1); | |
} | |
} else { | |
fprintf(stderr, "could not mkdir(%s): %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
} else { | |
fprintf(stdout, "%s/ -> %s/\n", path0, path1); | |
} | |
errno = 0; | |
if (!(dir0 = opendir(path0))) { | |
fprintf(stderr, "could not opendir(%s): %s\n", path0, strerror(errno)); | |
exit(-1); | |
} | |
errno = 0; | |
while ((dirent0 = readdir(dir0))) { | |
if (strcmp(dirent0->d_name, ".") && | |
strcmp(dirent0->d_name, "..")) { | |
sprintf(path0_child, "%s/%s", path0, dirent0->d_name); | |
sprintf(path1_child, "%s/%s", path1, dirent0->d_name); | |
dansync(path0_child, path1_child); | |
} | |
errno = 0; | |
} | |
if (errno) { | |
fprintf(stderr, "could not readdir(%s): %s\n", path0, strerror(errno)); | |
exit(-1); | |
} | |
if (closedir(dir0)) { | |
fprintf(stderr, "could not closedir(%s): %s\n", path0, strerror(errno)); | |
exit(-1); | |
} | |
} else if (S_ISREG(path0_stat.st_mode)) { | |
errno = 0; | |
if (stat(path1, &path1_stat)) { | |
if (errno == ENOENT) { | |
printf("%s doesn't exist\n", path1); | |
copy(path0, path1, path0_stat); | |
} else { | |
fprintf(stderr, "stat(%s) failed: %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
} else if (is_newer_than2(path0_stat.st_mtimespec, path1_stat.st_mtimespec)) { | |
copy(path0, path1, path0_stat); | |
} else if (path1_stat.st_mode != path0_stat.st_mode) { | |
printf("%o != %o\n", (int) path1_stat.st_mode, (int) path0_stat.st_mode); | |
errno = 0; | |
if (chmod(path1, path0_stat.st_mode)) { | |
fprintf(stderr, "could not chmod(%s): %s\n", path1, strerror(errno)); | |
exit(-1); | |
} | |
fprintf(stdout, "mode(%s) -> mode(%s)\n", path0, path1); | |
} | |
} else { | |
fprintf(stderr, "%s is neither a directory nor a file\n", path0); | |
exit(-1); | |
} | |
} | |
int main(int argc, char** argv) { | |
if (argc != 3) { | |
fprintf(stdout, "usage: %s input/path output/path\n", argv[0]); | |
return -1; | |
} | |
dansync(argv[1], argv[2]); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment