Project

General

Profile

Download (45 KB) Statistics
| Branch: | Tag: | Revision:
/*
* Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl
*
*/
/* parallel copy of a directory heirarchy */
#define _GNU_SOURCE
#include "inodemap.h"
#include "todo.h"
#include "malloc.h"
#include "putils.h"
#include "vector-of-strings.h"
#include "version.h"
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>

// Don't accidentally use pathcat(). Use pathcatn().
char *pathcat(const char *a, const char *b) __attribute__((deprecated));

static char *
strdupn(const char *a) {
if (a == NULL) return NULL;
else return strdup(a);
}

static char *
pathcatn(const char *a, const char *b) {
if (b == NULL) return strdupn(a);
if (a == NULL) return strdupn(b);
char *result = NULL;
int r = asprintf(&result, "%s/%s", a, b);
assert(r > 0);
return result;
}

enum { default_n_threads = 32 };

// Stuff filled in by parse_args()
static struct parsed_args {
const char *cmd;
size_t n_threads;
// All of the following are set by --restore
int ignore_times;
int one_file_system;
int hard_links;
int preserve_symlinks;
int preserve_perms;
int preserve_times;
int preserve_owner;
int preserve_group;
int preserve_devices;
int preserve_specials;
int delete;

struct vector_of_strings *vs; // the arguments

struct vector_of_strings *exclude_from;
struct vector_of_strings *exclude;
struct vector_of_strings *include_from;
struct vector_of_strings *include;
} pargs = {.n_threads = default_n_threads};

/************************************************************************/
/* Discussion of how it all works.
*
* Note: we don't have a way to delete extraneous files on the destination.
*
* We walk the source directory tree in parallel as follows: We have a global
* todo list, and we put a todo item that identifies the source and dest into
* the todo list.
*
* A worker grabs an item from the todo list. If the item is a directory,
* then use readdir to get all the directory entries. For each directory
* entry we create the destination object, and then if there's any additional
* work to do (to copy the contents), then we add that work to to the todo
* list.
*
* To copy an object from src to destination while obeying hardlinks:
*
* Copying srcname to dstname
*
* Let srcinum be the inode number of the object named by srcname. Look in
* src_inodes to see if it's already been created as filename idstname with
* inode number idstnum.
*
* If it's already been created, then we need to make a hard link from
* dstname to idstname.

* Case 1: There is no object named dstname. Then make the link.
*
* Case 2: There is an object named dstname, and it has the same inumber
* as idstname, then we are done the link is right.
*
* Case 3: There is an object named dstname but it has a different
* inumber, so we must delete the old object (if it's a directory it must
* be an empty directory) and then make the lihnk.
*
*/

typedef const char *magic_t;

/************************************************************************/
/* Keep track of the topology of inodes. */
/************************************************************************/
magic_t src_inode_value_magic;
struct src_inode_value {
magic_t *magic;
char *fname;
dev_t dev;
ino_t ino;
};
static inline struct src_inode_value *
mk_src_inode_value(const char *dst_fname, dev_t dev, ino_t ino) {
struct src_inode_value *MALLOC(result);
*result = (struct src_inode_value){.magic = &src_inode_value_magic,
.fname = strdup(dst_fname),
.dev = dev,
.ino = ino};
return result;
}
static struct src_inode_value *
src_inode_value_destroy(struct src_inode_value *siv) {
assert(siv->magic == &src_inode_value_magic);
FREE(siv->fname);
FREE(siv);
return NULL;
}

enum { INODE_OWNER_ARRAY_SIZE = 32 };

static struct itopology {
pthread_mutex_t map_mutex; // protect the inodemaps during reads and writes.
struct inodemap *src_inodes; // A map from src inumber to the
// destination filename and the dst
// inumber for that destination file.
struct inodemap *dst_inodes; // A map from dst inode to a null value
// (we are using the map to maintain a
// set).
pthread_mutex_t inode_owner_array[INODE_OWNER_ARRAY_SIZE];
// Lock based on a hash of the inumber
// and is held while the object is being
// created. Grab this lock first so
// that we can release the map_mutex
// while we create the object.
} itopology;

static void
destroy_null(void * x) {
assert(x == NULL);
}
static void
destroy_src_inode_value_v(void *xv) {
assert(xv);
struct src_inode_value *x = xv;
assert(x->magic == &src_inode_value_magic);
src_inode_value_destroy(x);
}

static void
init_itopology(void) {
pthread_mutex_init(&itopology.map_mutex, NULL);
itopology.src_inodes = mk_inodemap();
itopology.dst_inodes = mk_inodemap();
for (size_t i = 0; i < INODE_OWNER_ARRAY_SIZE; i++) {
pthread_mutex_init(&itopology.inode_owner_array[i], NULL);
}
}

static void
deinit_itopology(void) {
pthread_mutex_destroy(&itopology.map_mutex);
itopology.src_inodes = inodemap_destroy(itopology.src_inodes,
destroy_src_inode_value_v);
itopology.dst_inodes = inodemap_destroy(itopology.dst_inodes,
destroy_null);
for (size_t i = 0; i < INODE_OWNER_ARRAY_SIZE; i++) {
pthread_mutex_destroy(&itopology.inode_owner_array[i]);
}
}

static void
lock_itopology_ownership(dev_t dev, ino_t ino) {
uint64_t h = inodemap_hash(dev, ino);
pthread_mutex_t *om =
&itopology.inode_owner_array[h%INODE_OWNER_ARRAY_SIZE];
pthread_mutex_lock(om);
}

static void
unlock_itopology_ownership(dev_t dev, ino_t ino) {
uint64_t h = inodemap_hash(dev, ino);
pthread_mutex_t *om =
&itopology.inode_owner_array[h%INODE_OWNER_ARRAY_SIZE];
pthread_mutex_unlock(om);
}

static bool
itopology_lookup_src(dev_t dev, ino_t ino,
struct src_inode_value const ** sivp) {
pthread_mutex_lock(&itopology.map_mutex);
void const *sivv;
bool result = inodemap_lookup(itopology.src_inodes, dev, ino, &sivv);
pthread_mutex_unlock(&itopology.map_mutex);
if (result) {
assert(sivv);
struct src_inode_value const *siv = sivv;
assert(siv->magic == &src_inode_value_magic);
*sivp = siv;
}
return result;
}

static bool
itopology_lookup_dst(dev_t dev, ino_t ino) {
pthread_mutex_lock(&itopology.map_mutex);
void const *np;
bool result = inodemap_lookup(itopology.dst_inodes, dev, ino, &np);
pthread_mutex_unlock(&itopology.map_mutex);
if (result) {
assert(np == NULL);
}
return result;
}
static void
itopology_set(dev_t sdev,
ino_t sino,
dev_t ddev,
ino_t dino,
const char *fname) {
pthread_mutex_lock(&itopology.map_mutex);
inodemap_insert(itopology.dst_inodes, ddev, dino, NULL);
inodemap_insert(itopology.src_inodes, sdev, sino,
mk_src_inode_value(fname, ddev, dino));
pthread_mutex_unlock(&itopology.map_mutex);
}

/******************************************************************/
/* todo_item used to create work items for the scheduler. */
/******************************************************************/

static magic_t todo_item_magic;

enum todo_stage { TODO_ENSURE_EXISTENCE_AND_POPULATE,
TODO_REG_CONTENTS,
TODO_DIR_CONTENTS,
// Do rm -rf on dst
TODO_RM, // only dst defined (not dbuf or src or sbuf)
};

struct todo_item {
magic_t *magic;
enum todo_stage stage;
union {
struct todo_ensure {
const char *src_fprefix;
const char *dst_fprefix;
char *fsuffix;
} ensure;
struct todo_reg_or_dir {
const char *src_fprefix;
const char *dst_fprefix;
char *fsuffix;
struct stat sbuf;
struct stat dbuf;
} reg_or_dir;
struct todo_rm {
const char *dst_fprefix;
char *fsuffix;
char *notdirname; // just the last part of the fsuffix
size_t refcount;
struct todo_item *parent;
} rm;
} u;
};

static struct todo_item *
mk_todo_item_ensure_existence_and_populate(const char *src_fprefix,
const char *dst_fprefix,
char *fsuffix)
// Ownership of fsuffix gets passed to the new object
{
struct todo_item *MALLOC(item);
*item = (struct todo_item){
.magic = &todo_item_magic,
.stage = TODO_ENSURE_EXISTENCE_AND_POPULATE,
.u.ensure = {src_fprefix, dst_fprefix, fsuffix}};
return item;
}

static struct todo_item *
mk_todo_item_dir_contents(const char *src_fprefix,
const char *dst_fprefix,
char *fsuffix,
const struct stat *sbuf,
const struct stat *dbuf)
// Ownership of src and dst get passed to the new object.
{
struct todo_item *MALLOC(item);
*item = (struct todo_item){
.magic = &todo_item_magic,
.stage = TODO_DIR_CONTENTS,
.u.reg_or_dir = {src_fprefix, dst_fprefix, fsuffix, *sbuf, *dbuf}};
return item;
}

static struct todo_item *
mk_todo_item_reg_contents(const char *src_fprefix,
const char *dst_fprefix,
char *fsuffix,
const struct stat *sbuf,
const struct stat *dbuf)
// Ownership of src and dst get passed to the new object.
{
struct todo_item *MALLOC(item);
*item = (struct todo_item){
.magic = &todo_item_magic,
.stage = TODO_REG_CONTENTS,
.u.reg_or_dir = {src_fprefix, dst_fprefix, fsuffix, *sbuf, *dbuf}};
return item;
}

static struct todo_item *
mk_todo_item_rm(const char *dst_fprefix,
char *fsuffix,
char *notdirname,
struct todo_item *parent) {
struct todo_item *MALLOC(item);
*item = (struct todo_item){
.magic = &todo_item_magic,
.stage = TODO_RM,
.u.rm = {dst_fprefix, fsuffix, notdirname, 1ul, parent}};
return item;
}

static struct todo_item *
todo_item_destroy(struct todo_item *item) {
assert(item->magic == &todo_item_magic);
switch (item->stage) {
case TODO_ENSURE_EXISTENCE_AND_POPULATE: {
struct todo_ensure *e = &item->u.ensure;
FREE(e->fsuffix);
break;
}
case TODO_REG_CONTENTS:
case TODO_DIR_CONTENTS: {
struct todo_reg_or_dir *rd = &item->u.reg_or_dir;
FREE(rd->fsuffix);
break;
}
case TODO_RM: {
struct todo_rm *rm = &item->u.rm;
FREE(rm->fsuffix);
FREE(rm->notdirname);
break;
}
default:
assert(0);
}
FREE(item);
return NULL;
}

/******************************************************************/

static int
matches_an_exclude(const char *pathname, const char *filename) {
if (pathname) {
for (size_t i = 0; i < vector_of_strings_size(pargs.exclude); i++) {
int r = fnmatch(vector_of_strings_fetch(pargs.exclude, i),
pathname,
FNM_PATHNAME);
if (r == 0) return 1;
}
}
for (size_t i = 0; i < vector_of_strings_size(pargs.exclude); i++) {
int r = fnmatch(vector_of_strings_fetch(pargs.exclude, i),
filename,
FNM_PATHNAME);
if (r == 0) return 1;
}
return 0;
}

static int
matches_an_include(const char *pathname, const char *filename) {
// In the absence of --include-from, always include.
if (vector_of_strings_size(pargs.include) == 0) {
return 1;
}
if (pathname) {
for (size_t i = 0; i < vector_of_strings_size(pargs.include); i++) {
int r = fnmatch(vector_of_strings_fetch(pargs.include, i),
pathname,
FNM_PATHNAME);
if (r == 0) return 1;
}
}
for (size_t i = 0; i < vector_of_strings_size(pargs.include); i++) {
int r = fnmatch(vector_of_strings_fetch(pargs.include, i),
filename,
FNM_PATHNAME);
if (r == 0) return 1;
}
return 0;
}
static void
do_ensure_existence_and_populate_item(
struct todo_list *tl,
const struct todo_ensure *item,
struct todo_item *surrounding_item);
static void
do_dir_contents_item(struct todo_list *tl,
const struct todo_reg_or_dir *item,
struct todo_item *surrounding_item);
static void
do_reg_contents_item(struct todo_list *tl,
const struct todo_reg_or_dir *item,
struct todo_item *surrounding_item);
static void
do_rm_item(struct todo_list *tl,
struct todo_rm *item,
struct todo_item *surrounding_item);
// This one isn't const because it messes around with refcount.

static void
do_item(struct todo_list *tl, struct todo_item *item) {
// This function now owns item. It's responsible for freeing it.
// Pass ownership to one of the subfunctions:
switch (item->stage) {
case TODO_ENSURE_EXISTENCE_AND_POPULATE:
do_ensure_existence_and_populate_item(tl, &item->u.ensure, item);
break;
case TODO_REG_CONTENTS:
do_reg_contents_item(tl, &item->u.reg_or_dir, item);
break;
case TODO_DIR_CONTENTS:
do_dir_contents_item(tl, &item->u.reg_or_dir, item);
break;
case TODO_RM:
do_rm_item(tl, &item->u.rm, item);
break;
default:
assert(0); // unreachable
}
}

pthread_mutex_t todo_rm_refcount_mutex = PTHREAD_MUTEX_INITIALIZER;

static void
incr_refcount(struct todo_item *item) {
if (item == NULL) return;
assert(item->magic == &todo_item_magic);
assert(item->stage == TODO_RM);
pthread_mutex_lock(&todo_rm_refcount_mutex);
item->u.rm.refcount++;
pthread_mutex_unlock(&todo_rm_refcount_mutex);
}

static void
decr_refcount(struct todo_item *item) {
if (item == NULL) return;
assert(item->magic == &todo_item_magic);
assert(item->stage == TODO_RM);
size_t count;
pthread_mutex_lock(&todo_rm_refcount_mutex);
assert(item->u.rm.refcount > 0);
count = --item->u.rm.refcount;
pthread_mutex_unlock(&todo_rm_refcount_mutex);
if (count == 0) {
char *dst = pathcatn(item->u.rm.dst_fprefix, item->u.rm.fsuffix);
if (!matches_an_exclude(item->u.rm.fsuffix, item->u.rm.notdirname) &&
matches_an_include(item->u.rm.fsuffix, item->u.rm.notdirname)) {
// Don't worry if this rmdir fails. Sometimes it will fail
// because a file inside was excluded (e.g., with --exclude-from),
// and that's fine.
rmdir(dst);
}
if (item->u.rm.parent) {
decr_refcount(item->u.rm.parent);
}
todo_item_destroy(item);
FREE(dst);
}
}

static void
do_rm_item(struct todo_list *tl,
struct todo_rm *item,
struct todo_item *surrounding_item) {
// Pass in the surrounding item so that we can maintain its reference
// count. Pass in item so that we we minimize the chance of type errors.
assert(surrounding_item->magic == &todo_item_magic);
assert(surrounding_item->stage == TODO_RM);
assert(&surrounding_item->u.rm == item);
struct stat dbuf;
char *dst = pathcatn(item->dst_fprefix, item->fsuffix);
if (!matches_an_exclude(item->fsuffix, item->notdirname) &&
matches_an_include(item->fsuffix, item->notdirname)) {
MAYBE_ERROR(lstat, dst, &dbuf);
if (S_ISDIR(dbuf.st_mode)) {
DIR *d = opendir(dst);
assert(d);
struct dirent *entry;
struct vector_of_strings *fnames = mk_vector_of_strings();
struct vector_of_strings *ndnames = mk_vector_of_strings();
while ((entry = readdir(d))) {
char *dst_suffix = pathcatn(item->fsuffix, entry->d_name);
if (1
&& !matches_an_exclude(dst_suffix, entry->d_name)
&& matches_an_include(dst_suffix, entry->d_name)
&& strcmp(entry->d_name, ".") != 0
&& strcmp(entry->d_name, "..") != 0
&& strcmp(entry->d_name, ".snapshot") != 0)
{
vector_of_strings_push(fnames, dst_suffix);
vector_of_strings_push(ndnames, entry->d_name);
}
FREE(dst_suffix);
}
closedir(d);
while (vector_of_strings_size(fnames)) {
char *dst_suffix = vector_of_strings_pop(fnames);
char *ndname = vector_of_strings_pop(ndnames);
incr_refcount(surrounding_item);
struct todo_item *ti = mk_todo_item_rm(item->dst_fprefix,
dst_suffix,
ndname,
surrounding_item);
todo_list_push(tl, ti);
}
vector_of_strings_destroy(fnames);
vector_of_strings_destroy(ndnames);
} else {
MAYBE_ERROR(unlink, dst);
assert(surrounding_item->magic == &todo_item_magic);
assert(surrounding_item->stage == TODO_RM);
assert(surrounding_item->u.rm.refcount == 1);
}
}
FREE(dst);
decr_refcount(surrounding_item);
// Don't free the item here, because it might be still alive after
// decr_refcount(surrounding_item).
}

static void
do_reg_contents_item(struct todo_list *tl __attribute__((unused)),
const struct todo_reg_or_dir *item,
struct todo_item *surrounding_item)
// Effect: Copy the contents of src to dst, both of which be existing regular
// files. We have the stat results for source and dst. We assume that the
// check for the case where mtime and size match was already done.
{
assert(&todo_item_magic == surrounding_item->magic);
assert(TODO_REG_CONTENTS == surrounding_item->stage);
assert(&surrounding_item->u.reg_or_dir == item);
assert(S_ISREG(item->sbuf.st_mode));
// We copy the contents of the file, truncate it, and set the permissions
// and mtime. (We assume that the the check for the case where mtime and
// size match was done before queuing the item.
char *srcname = pathcatn(item->src_fprefix, item->fsuffix);
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
int srcfd = open(srcname, O_RDONLY);
maybe_error(srcfd<0, "open", srcname, __FILE__, __LINE__);
MAYBE_ERROR(chmod, dstname, 0700);
int dstfd = open(dstname, O_WRONLY);
maybe_error(dstfd<0, "open", dstname, __FILE__, __LINE__);
size_t bufsize=1024*1024;
char *buf = malloc(bufsize);
size_t totalcount = 0;
while (1) {
ssize_t rcount = read(srcfd, buf, bufsize);
if (rcount == 0) break;
maybe_error(rcount<0, "read", srcname, __FILE__, __LINE__);
totalcount += (size_t)rcount;
char *wbuf = buf;
while (rcount > 0) {
ssize_t wcount = write(dstfd, wbuf, (size_t)rcount);
maybe_error(wcount<0, "write", dstname, __FILE__, __LINE__);
rcount -= wcount;
wbuf += (size_t)wcount;
}
}
if (item->dbuf.st_size > (off_t)totalcount) {
maybe_error(ftruncate(dstfd, (off_t)totalcount),
"ftruncate", dstname, __FILE__, __LINE__);
}
// Must do the chown before setting setuid or setgid.
int fchown_ret = fchown(dstfd, item->sbuf.st_uid, item->sbuf.st_gid);
maybe_error(fchown_ret, "fchown", dstname, __FILE__, __LINE__);
int fchmod_ret = fchmod(dstfd, item->sbuf.st_mode & (0777 | S_ISUID | S_ISGID));
maybe_error(fchmod_ret, "fchmod", dstname, __FILE__, __LINE__);
struct timespec times[2] = {item->sbuf.st_atim, item->sbuf.st_mtim};
int futimens_ret = futimens(dstfd, times);
maybe_error(futimens_ret, "futimens", dstname, __FILE__, __LINE__);
maybe_error(close(srcfd), "close", srcname, __FILE__, __LINE__);
maybe_error(close(dstfd), "close", dstname, __FILE__, __LINE__);
FREE(buf);
surrounding_item = todo_item_destroy(surrounding_item);
FREE(srcname);
FREE(dstname);
}

static void
do_dir_contents_item(struct todo_list *tl,
const struct todo_reg_or_dir *item,
struct todo_item *surrounding_item)
// Effect: Copy the contents of src to dst, both of which be existing
// directories.
{
assert(&todo_item_magic == surrounding_item->magic);
assert(TODO_DIR_CONTENTS == surrounding_item->stage);
assert(&surrounding_item->u.reg_or_dir == item);
size_t n_dirents = 0;
size_t dirents_limit = 2;
struct dirent *MALLOC_N(dirents, dirents_limit);
char *srcname = pathcatn(item->src_fprefix, item->fsuffix);
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
{
DIR *d = opendir(srcname);
if (d == NULL) fprintf(stderr, "Couldn't open %s\n", srcname);
assert(d);
struct dirent *entry;
while ((entry = readdir(d))) {
if (matches_an_exclude(item->fsuffix, entry->d_name) ||
!matches_an_include(item->fsuffix, entry->d_name)) continue;
if (strcmp(entry->d_name, ".") == 0) continue;
if (strcmp(entry->d_name, "..") == 0) continue;
if (strcmp(entry->d_name, ".snapshot") == 0) continue;
if (n_dirents >= dirents_limit) {
dirents_limit*=2;
REALLOC(dirents, dirents_limit);
}
dirents[n_dirents++] = *entry;
}
closedir(d);
}
if (pargs.delete) {
//qsort(dirents, n_dirents, sizeof(*dirents), dirent_sorter);
DIR *d = opendir(dstname);
if (!d) {
fprintf(stderr, "%s:%d Could not opendir(\"%s\")\n",
__FILE__, __LINE__, dstname);
}
assert(d);
struct dirent *entry;
while ((entry = readdir(d))) {
char *new_suffix = pathcatn(item->fsuffix, entry->d_name);
if (1
&& !matches_an_exclude(new_suffix, entry->d_name)
&& matches_an_include(new_suffix, entry->d_name)
&& strcmp(entry->d_name, ".") != 0
&& strcmp(entry->d_name, "..") != 0
&& strcmp(entry->d_name, ".snapshot") != 0)
{
for (size_t i = 0; i < n_dirents; i++) {
if (strcmp(entry->d_name, dirents[i].d_name) == 0) {
FREE(new_suffix);
goto keep_it;
}
}
todo_list_push(tl,
mk_todo_item_rm(
item->dst_fprefix,
new_suffix,
strdup(entry->d_name),
NULL));
} else {
FREE(new_suffix);
}
keep_it: ;/* nothing */
}
closedir(d);
}
for (size_t i = 0; i < n_dirents; i++) {
char *new_suffix = pathcatn(item->fsuffix, dirents[i].d_name);
// Ensure the existence immediately. If needed, this will create an
// additional task to populate the item.
if (!matches_an_exclude(new_suffix, dirents[i].d_name) &&
matches_an_include(new_suffix, dirents[i].d_name)) {
do_item(tl,
mk_todo_item_ensure_existence_and_populate(
item->src_fprefix,
item->dst_fprefix,
new_suffix));
} else {
FREE(new_suffix);
}
}
// now set the permissions on the dir.
struct stat sbuf;
MAYBE_ERROR(lstat, srcname, &sbuf);
int chown_ret = chown(dstname, sbuf.st_uid, sbuf.st_gid);
maybe_error(chown_ret, "chown", dstname, __FILE__, __LINE__);
int chmod_ret = chmod(dstname, sbuf.st_mode & (0777 | S_ISUID | S_ISGID));
maybe_error(chmod_ret, "chmod", dstname, __FILE__, __LINE__);
struct timespec times[2] = {sbuf.st_atim, sbuf.st_mtim};
int utimensat_ret = utimensat(AT_FDCWD, dstname, times, 0);
maybe_error(utimensat_ret, "utimensat", dstname, __FILE__, __LINE__);
FREE(dirents);
todo_item_destroy(surrounding_item);
FREE(srcname);
FREE(dstname);
}

static void
do_ensure_dir(struct todo_list *tl,
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
const struct stat *dbuf);
static void
do_ensure_symlink(struct todo_list *tl,
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
const struct stat *dbuf);
static void
do_ensure_nod(struct todo_list *tl,
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
struct stat *dbuf);
void
do_create_item_obj(struct todo_list *tl,
struct todo_item *item,
bool dexists);

static void
do_ensure_existence_and_populate_item(
struct todo_list *tl,
const struct todo_ensure *item,
struct todo_item *surrounding_item)
// Effect: Ensure that the dst is created, and then if it is a directory or
// regular file enqueue an item to copy the contents.
{
assert(&todo_item_magic == surrounding_item->magic);
assert(TODO_ENSURE_EXISTENCE_AND_POPULATE == surrounding_item->stage);
assert(&surrounding_item->u.ensure == item);
struct stat sbuf;
char *srcname = pathcatn(item->src_fprefix, item->fsuffix);
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
int er = lstat(srcname, &sbuf);
if ( er != 0 && errno == ENOENT) {
fprintf(stderr, "%s:%d Warning: Could not lstat(\"%s\"). errno=%d (%s)\n",
__FILE__, __LINE__, srcname, errno, strerror(errno));
surrounding_item = todo_item_destroy(surrounding_item);
FREE(srcname);
FREE(dstname);
return;
}

bool dexists;
struct stat dbuf;
{
int r = lstat(dstname, &dbuf);
if (r != 0 && errno == ENOENT) {
dexists = false;
} else if (r == 0) {
dexists = true;
} else {
maybe_error(r, "lstat", dstname, __FILE__, __LINE__);
assert(0); // unreachable
}
}
if (S_ISDIR(sbuf.st_mode)) {
do_ensure_dir(tl, item, &sbuf, dexists, &dbuf);
} else if (S_ISLNK(sbuf.st_mode)) {
do_ensure_symlink(tl, item, &sbuf, dexists, &dbuf);
} else if (0
|| S_ISREG(sbuf.st_mode)
|| S_ISCHR(sbuf.st_mode)
|| S_ISBLK(sbuf.st_mode)
|| S_ISFIFO(sbuf.st_mode)
|| S_ISSOCK(sbuf.st_mode)) {
do_ensure_nod(tl, item, &sbuf, dexists, &dbuf);
} else {
assert(0); // unreachable
}
surrounding_item = todo_item_destroy(surrounding_item);
FREE(srcname);
FREE(dstname);
}

static void
do_ensure_dir(struct todo_list *tl,
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
const struct stat *dbuf) {
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
if (!dexists) {
// Dest doesn't exist. Create it and copy contents (which will
// also fix the permissions of the directory).
mkdir(dstname, 0770);
} else if (S_ISDIR(dbuf->st_mode)) {
// Dest exists as a dir. Make it writeable, copy contents (and
// fix permissions).
MAYBE_ERROR(chmod, dstname, 0770);
} else {
// Dest exists, but it's not a directory. Get rid of it, make the
// dir, and copy contents.
MAYBE_ERROR(unlink, dstname);
mkdir(dstname, 0770);
}
// We could try to reuse the item, but that's not very clean.
todo_list_push(tl, mk_todo_item_dir_contents(item->src_fprefix,
item->dst_fprefix,
strdupn(item->fsuffix),
sbuf,
dbuf));
FREE(dstname);
}

static void
unlink_and_symlink(const struct todo_ensure *item, const char *target) {
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
MAYBE_ERROR(unlink, dstname);
MAYBE_ERROR2(symlink, target, dstname);
FREE(dstname);
}

static void
do_ensure_symlink(struct todo_list *tl __attribute__((unused)),
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
const struct stat *dbuf) {
char *srcname = pathcatn(item->src_fprefix, item->fsuffix);
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
size_t ssize = (size_t)sbuf->st_size;
char *slinkval = malloc(ssize + 1);
ssize_t sgot = readlink(srcname, slinkval, ssize+1);
maybe_error(sgot < 0 || (size_t)sgot != ssize,
"readlink", srcname,
__FILE__, __LINE__);
slinkval[ssize]=0;
if (!dexists) {
MAYBE_ERROR2(symlink, slinkval, dstname);
} else if (S_ISDIR(dbuf->st_mode)) {
delete_dir_recursively(dstname);
MAYBE_ERROR2(symlink, slinkval, dstname);
} else if (S_ISLNK(dbuf->st_mode)) {
if (dbuf->st_size != sbuf->st_size) {
unlink_and_symlink(item, slinkval);
} else {
size_t size = (size_t)dbuf->st_size;
char *dlinkval = malloc(size + 1);

ssize_t dgot = readlink(dstname, dlinkval, size+1);
maybe_error(dgot < 0 || (size_t)dgot != size,
"readlink", dstname,
__FILE__, __LINE__);
int cmp = memcmp(dlinkval, slinkval, size);
FREE(dlinkval);
if (cmp != 0) {
unlink_and_symlink(item, slinkval);
} else {
// The existing symlink is what we want.
/*nothing*/;
}
}
} else {
unlink_and_symlink(item, slinkval);
}
FREE(srcname);
FREE(dstname);
FREE(slinkval);
}

static void
do_ensure_nod(struct todo_list *tl,
const struct todo_ensure *item,
const struct stat *sbuf,
bool dexists,
struct stat *dbuf)
// Effect: Make sure that a (nondir) file object exists. If it's a regular
// file then add a todo item to populate it.
{
char *srcname = pathcatn(item->src_fprefix, item->fsuffix);
char *dstname = pathcatn(item->dst_fprefix, item->fsuffix);
// Here we have to do some extra work to make the topology of the inodes
// match in the source and destionation. The first question is whether
// the source inode has a corresponding destination inode already.
lock_itopology_ownership(sbuf->st_dev, sbuf->st_ino);
struct src_inode_value const * siv;
bool already_copied = itopology_lookup_src(sbuf->st_dev,
sbuf->st_ino,
&siv);
// The om is still held, so no one else can copy the inode until we
// resolve this.

if (already_copied) {
// The file has already been copied, so all we need is a link. It's
// resolved so we can release om.
unlock_itopology_ownership(sbuf->st_dev, sbuf->st_ino);
if (dexists) {
// The destination already exists.
if (S_ISDIR(dbuf->st_mode)) {
// The destination exists, but it's a dir. Must rmdir the
// directory, then make a link to the object.
MAYBE_ERROR(rmdir, dstname);
goto dest_doesnt_exist;
} else if (1
&& siv->dev == dbuf->st_dev
&& siv->ino == dbuf->st_ino) {
// The link exists to the right thing, so we are done.
/*nothing*/ ;
} else {
// Something exists there, but it's the wrong thing.
MAYBE_ERROR(unlink, dstname);
goto dest_doesnt_exist;
}
} else {
// The destination doesn't exist.
dest_doesnt_exist:
MAYBE_ERROR2(link, siv->fname, dstname);
}
} else {
// The file has not been copied, so we need to make sure the
// destination exists and isn't an inode that *has* been previously
// mapped.
if (dexists) {
// The destination exists.
if (S_ISDIR(dbuf->st_mode)) {
// The destination exists, but it's a dir. Must rmdir the
// directory, then make a link to the object.
MAYBE_ERROR(rmdir, dstname);
goto destination_doesnt_exist;
} else if (0
|| ((dbuf->st_mode & S_IFMT)
!= (sbuf->st_mode & S_IFMT))
|| itopology_lookup_dst(dbuf->st_dev, dbuf->st_ino)) {
// It's a different file type or the dev and inode already has
// been mapped, so we cannot use the current destination.
MAYBE_ERROR(unlink, dstname);
goto destination_doesnt_exist;
} else {
// dest exists, and we can reuse it.
// Todo: If we don't have write permission, make it writeable.
/*nothing*/ ;
}
} else {
// The destination doesn't exist.
destination_doesnt_exist:
MAYBE_ERROR(mknod,
dstname,
sbuf->st_mode | 0700,
sbuf->st_dev);
MAYBE_ERROR(lstat, dstname, dbuf);
}
// Now we have the right destination inode.
itopology_set(sbuf->st_dev, sbuf->st_ino,
dbuf->st_dev, dbuf->st_ino,
dstname);
if (S_ISREG(sbuf->st_mode)
&& (0
|| sbuf->st_size != dbuf->st_size
|| sbuf->st_mtim.tv_sec != dbuf->st_mtim.tv_sec
|| sbuf->st_mtim.tv_nsec != dbuf->st_mtim.tv_nsec))
{
todo_list_push(tl, mk_todo_item_reg_contents(item->src_fprefix,
item->dst_fprefix,
strdupn(item->fsuffix),
sbuf,
dbuf));
}
unlock_itopology_ownership(sbuf->st_dev, sbuf->st_ino);
}
FREE(srcname);
FREE(dstname);
}

static void do_cp(const char *src, const char *dst) {
struct todo_item *item = mk_todo_item_ensure_existence_and_populate(
src, dst, NULL);
struct todo_list *tl = mk_todo_list();
todo_list_run(tl, pargs.n_threads, item, do_item);
todo_list_destroy(tl);
}

void help(int exitcode) {
FILE *out = exitcode ? stderr : stdout;
fprintf(out, "Usage: %s [OPTION] source dest\n", pargs.cmd);
fprintf(out, "Recursive copy of a directory hierarchy, done in parallel for performance.\n");
fprintf(out, " OPTION can be\n");
fprintf(out, " -P P (capital P) use P-fold parallelism (default %d threads).\n", default_n_threads);
fprintf(out, " --restore Recurse, put stop at file system boundaries, preserve hard links, symlinks permissions, modification times, group, owners, and special files such as named sockets and fifos. Similar to `-arxH` in rsync. Note: This does not include --delete.\n");
fprintf(out, " --delete Delete files that appear in dst but not src.\n");
fprintf(out, " --exclude-from=FILE FILE contains a list of glob(3)-style patterns, which will not be copied.\n");
fprintf(out, " --include-from=FILE FILE contains a list of glob(3)-style patterns, which will be copied. Everything else will be omitted.\n");
fprintf(out, " -- remaining arguments are files even if they start with '-'.\n");
fprintf(out, " -h, --help print help.\n");
fprintf(out, " --version print program version\n");
fprintf(out, " --history print a brief history of the versions\n");
fprintf(out, "This is version %s gitrev %s compiled %s %s.\n",
VERSION, gitrev, __DATE__, __TIME__);
fprintf(out, " Report bugs to parallel-tools-support_ww_grp@oracle.com\n");
exit(exitcode);
}

static int prefix_matches(const char *prefix, const char *string, char const **suffix) {
while (*prefix == *string) {
prefix++; string++;
}
if (*prefix == 0) {
*suffix = string;
return 1;
} else {
return 0;
}
}

static void
parse_args(int argc, const char *argv[], struct vector_of_strings *args) {
assert(argc>0);
pargs.cmd = argv[0];
argc--; argv++;
while (argc) {
char const *suffix;
if (strcmp(argv[0], "-P") == 0) {
argc--; argv++;
if (argc == 0) help(1);
pargs.n_threads = parse_number_or_help(argv[0]);
} else if (strcmp(argv[0], "--delete") == 0) {
pargs.delete = 1;
} else if (strcmp(argv[0], "--restore") == 0) {
pargs.ignore_times = 1;
pargs.one_file_system = 1;
pargs.hard_links = 1;
pargs.preserve_symlinks = 1;
pargs.preserve_perms = 1;
pargs.preserve_times = 1;
pargs.preserve_owner = 1;
pargs.preserve_group = 1;
pargs.preserve_devices = 1;
pargs.preserve_specials = 1;
} else if (prefix_matches("--exclude-from=", argv[0], &suffix)) {
if (vector_of_strings_size(pargs.include_from)) {
fprintf(stderr, "%s\n",
"Options --exclude-from and --include-from are mutually exclusive.");
help(1);
}
vector_of_strings_push(pargs.exclude_from, suffix);
} else if (prefix_matches("--include-from=", argv[0], &suffix)) {
if (vector_of_strings_size(pargs.exclude_from)) {
fprintf(stderr, "%s\n",
"Options --exclude-from and --include-from are mutually exclusive.");
help(1);
}
vector_of_strings_push(pargs.include_from, suffix);
} else if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0) {
help(0);
} else if (strcmp(argv[0], "--version") == 0) {
printf("%s %s\n"
"Copyright (C) 2019 Oracle.\n"
"Written by Bradley C. Kuszmaul\n",
pargs.cmd, VERSION);
exit(0);
} else if (strcmp(argv[0], "--history") == 0) {
fprintf(stderr, "%s", VERSION_HISTORY);
exit(0);
} else if (strcmp(argv[0], "--") == 0) {
while (1) {
argc--; argv++;
vector_of_strings_push(args, argv[0]);
if (argc==1) break;
}
} else {
vector_of_strings_push(args, argv[0]);
}
argc--; argv++;
}
if (vector_of_strings_size(args) != 2) {
fprintf(stderr, "Need two arguments for copying, got %zu\n",
vector_of_strings_size(args));
help(1);
}
{
char *line = NULL;
size_t len = 0;
ssize_t nread = 0;
for (size_t i = 0; i < vector_of_strings_size(pargs.exclude_from); i++) {
char const *fname = vector_of_strings_fetch(pargs.exclude_from, i);
FILE *f = fopen(fname, "r");
if (f == NULL) {
fprintf(stderr, "Could not open file named %s, "
"mentioned as an --exclude-from\n",
fname);
FREE(line);
exit(1);
}
while ((nread = getline(&line, &len, f)) != -1) {
size_t llen = strlen(line);
if (llen == 0) continue;
if (';' == line[0]) continue;
if ('#' == line[0]) continue;
if (line[llen-1] == '\n') line[llen-1]=0;
vector_of_strings_push(pargs.exclude, line);
}
fclose(f);
}
for (size_t i = 0; i < vector_of_strings_size(pargs.include_from); i++) {
char const *fname = vector_of_strings_fetch(pargs.include_from, i);
FILE *f = fopen(fname, "r");
if (f == NULL) {
fprintf(stderr, "Could not open file named %s, "
"mentioned as an --include-from\n",
fname);
FREE(line);
exit(1);
}
while ((nread = getline(&line, &len, f)) != -1) {
size_t llen = strlen(line);
if (llen == 0) continue;
if (';' == line[0]) continue;
if ('#' == line[0]) continue;
if (line[llen-1] == '\n') line[llen-1]=0;
vector_of_strings_push(pargs.include, line);
}
fclose(f);
}
FREE(line);
}
}

static void on_exit_destroy_parcp(int status __attribute__((unused)),
void *ignore __attribute__((unused))) {
vector_of_strings_destroy(pargs.exclude_from);
vector_of_strings_destroy(pargs.exclude);
vector_of_strings_destroy(pargs.include_from);
vector_of_strings_destroy(pargs.include);
vector_of_strings_destroy(pargs.vs);
deinit_itopology();
}

int main (int argc, const char *argv[]) {
pargs.vs = mk_vector_of_strings();
pargs.exclude_from = mk_vector_of_strings();
pargs.exclude = mk_vector_of_strings();
pargs.include_from = mk_vector_of_strings();
pargs.include = mk_vector_of_strings();
init_itopology();
on_exit(on_exit_destroy_parcp, NULL);
parse_args(argc, argv, pargs.vs);
assert(vector_of_strings_size(pargs.vs) == 2);
char *dst = vector_of_strings_pop(pargs.vs);
char *src = vector_of_strings_pop(pargs.vs);
struct stat statb;
{
int r = lstat(src, &statb);
if (r != 0) {
fprintf(stderr, "Could not lstat(\"%s\") src. errno=%d (%s)\n",
src, errno, strerror(errno));
FREE(dst);
FREE(src);
exit(1);
}
if (!S_ISDIR(statb.st_mode)) {
fprintf(stderr, "%s is not a directory.\n", src);
fprintf(stderr, "parcp operates only on directories. To copy a single file, use cp\n");
FREE(dst);
FREE(src);
exit(1);
}
}
{
int r = mkdir(dst, S_IRUSR|S_IWUSR|S_IXUSR);
if (r != 0 && errno != EEXIST) {
fprintf(stderr, "Could not mkdir(\"%s\") dst. errno=%d (%s)\n",
dst, errno, strerror(errno));
FREE(dst);
FREE(src);
exit(1);
}
}
// Creating a copy of src to pass to basename function
// as it may modify the input.
char *src_copy = strdup(src);

// Using POSIX version of basename routine(picked from '<libgen.h>')
// instead of GNU verison(picked from '<string.h>').
// Including '<libgen.h>' either before or after including '<string.h>',
// will override the GNU variant of basename().
char *src_basename = basename(src_copy);
if (src_basename == NULL) {
fprintf(stderr,"Could not extract basename of src \"%s\"", src_basename);
FREE(dst);
FREE(src);
exit(1);
}
char *old_dest = dst;
dst = pathcatn(old_dest, src_basename);
free(old_dest);

do_cp(src, dst);
free(dst);
free(src);
free(src_copy);
return 0;
}
(13-13/29)