root/software/fss-parallel-tools/parcp.c @ 191d89ea
| 7e8045d8 | David Sorber | /*
|
|
* 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;
|
|||
}
|