Skip to content

Instantly share code, notes, and snippets.

@ibaned
Last active January 8, 2017 23:09
Show Gist options
  • Save ibaned/de64467ee8ac7896db1df74f92b7c161 to your computer and use it in GitHub Desktop.
Save ibaned/de64467ee8ac7896db1df74f92b7c161 to your computer and use it in GitHub Desktop.
/* 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