version 1.4, 2019/02/11 19:18:36 |
version 1.5, 2019/02/11 21:41:22 |
|
|
struct flist *new; |
struct flist *new; |
struct flist *f, *fnext; |
struct flist *f, *fnext; |
|
|
if (0 == *sz) |
if (*sz == 0) |
return 1; |
return 1; |
|
|
/* Create a new buffer, "new", and copy. */ |
/* Create a new buffer, "new", and copy. */ |
|
|
new = calloc(*sz, sizeof(struct flist)); |
new = calloc(*sz, sizeof(struct flist)); |
if (NULL == new) { |
if (new == NULL) { |
ERR(sess, "calloc"); |
ERR(sess, "calloc"); |
return 0; |
return 0; |
} |
} |
|
|
* destination side. |
* destination side. |
*/ |
*/ |
|
|
if (0 == strcmp(f->path, fnext->path)) { |
if (strcmp(f->path, fnext->path) == 0) { |
new[j++] = *f; |
new[j++] = *f; |
i++; |
i++; |
WARNX(sess, "%s: duplicate path: %s", |
WARNX(sess, "%s: duplicate path: %s", |
|
|
if (!S_ISDIR(fl[i].st.mode)) |
if (!S_ISDIR(fl[i].st.mode)) |
continue; |
continue; |
cp = strchr(fl[i].wpath, '/'); |
cp = strchr(fl[i].wpath, '/'); |
if (NULL != cp && '\0' != cp[1]) |
if (cp != NULL && cp[1] != '\0') |
continue; |
continue; |
fl[i].st.flags |= FLSTAT_TOP_DIR; |
fl[i].st.flags |= FLSTAT_TOP_DIR; |
LOG4(sess, "%s: top-level", fl[i].wpath); |
LOG4(sess, "%s: top-level", fl[i].wpath); |
|
|
flist_fts_check(struct sess *sess, FTSENT *ent) |
flist_fts_check(struct sess *sess, FTSENT *ent) |
{ |
{ |
|
|
if (FTS_F == ent->fts_info || |
if (ent->fts_info == FTS_F || |
FTS_D == ent->fts_info || |
ent->fts_info == FTS_D || |
FTS_SL == ent->fts_info || |
ent->fts_info == FTS_SL || |
FTS_SLNONE == ent->fts_info) |
ent->fts_info == FTS_SLNONE) |
return 1; |
return 1; |
|
|
if (FTS_DC == ent->fts_info) { |
if (ent->fts_info == FTS_DC) { |
WARNX(sess, "%s: directory cycle", ent->fts_path); |
WARNX(sess, "%s: directory cycle", ent->fts_path); |
} else if (FTS_DNR == ent->fts_info) { |
} else if (ent->fts_info == FTS_DNR) { |
errno = ent->fts_errno; |
errno = ent->fts_errno; |
WARN(sess, "%s: unreadable directory", ent->fts_path); |
WARN(sess, "%s: unreadable directory", ent->fts_path); |
} else if (FTS_DOT == ent->fts_info) { |
} else if (ent->fts_info == FTS_DOT) { |
WARNX(sess, "%s: skipping dot-file", ent->fts_path); |
WARNX(sess, "%s: skipping dot-file", ent->fts_path); |
} else if (FTS_ERR == ent->fts_info) { |
} else if (ent->fts_info == FTS_ERR) { |
errno = ent->fts_errno; |
errno = ent->fts_errno; |
WARN(sess, "%s", ent->fts_path); |
WARN(sess, "%s", ent->fts_path); |
} else if (FTS_DEFAULT == ent->fts_info) { |
} else if (ent->fts_info == FTS_DEFAULT) { |
WARNX(sess, "%s: skipping special", ent->fts_path); |
WARNX(sess, "%s: skipping special", ent->fts_path); |
} else if (FTS_NS == ent->fts_info) { |
} else if (ent->fts_info == FTS_NS) { |
errno = ent->fts_errno; |
errno = ent->fts_errno; |
WARN(sess, "%s: could not stat", ent->fts_path); |
WARN(sess, "%s: could not stat", ent->fts_path); |
} |
} |
|
|
{ |
{ |
size_t i; |
size_t i; |
|
|
if (NULL == f) |
if (f == NULL) |
return; |
return; |
|
|
for (i = 0; i < sz; i++) { |
for (i = 0; i < sz; i++) { |
|
|
/* Allocate our full filename length. */ |
/* Allocate our full filename length. */ |
/* FIXME: maximum pathname length. */ |
/* FIXME: maximum pathname length. */ |
|
|
if (0 == (len = pathlen + partial)) { |
if ((len = pathlen + partial) == 0) { |
ERRX(sess, "security violation: " |
ERRX(sess, "security violation: " |
"zero-length pathname"); |
"zero-length pathname"); |
return 0; |
return 0; |
} |
} |
|
|
if (NULL == (f->path = malloc(len + 1))) { |
if ((f->path = malloc(len + 1)) == NULL) { |
ERR(sess, "malloc"); |
ERR(sess, "malloc"); |
return 0; |
return 0; |
} |
} |
|
|
return 0; |
return 0; |
} |
} |
|
|
if ('/' == f->path[0]) { |
if (f->path[0] == '/') { |
ERRX(sess, "security violation: " |
ERRX(sess, "security violation: " |
"absolute pathname: %s", f->path); |
"absolute pathname: %s", f->path); |
return 0; |
return 0; |
} |
} |
|
|
if (NULL != strstr(f->path, "/../") || |
if (strstr(f->path, "/../") != NULL || |
(len > 2 && 0 == strcmp(f->path + len - 3, "/..")) || |
(len > 2 && strcmp(f->path + len - 3, "/..") == 0) || |
(len > 2 && 0 == strncmp(f->path, "../", 3)) || |
(len > 2 && strncmp(f->path, "../", 3) == 0) || |
0 == strcmp(f->path, "..")) { |
strcmp(f->path, "..") == 0) { |
ERRX(sess, "%s: security violation: " |
ERRX(sess, "%s: security violation: " |
"backtracking pathname", f->path); |
"backtracking pathname", f->path); |
return 0; |
return 0; |
|
|
|
|
pp = recallocarray(*fl, *max, |
pp = recallocarray(*fl, *max, |
*max + FLIST_CHUNK_SIZE, sizeof(struct flist)); |
*max + FLIST_CHUNK_SIZE, sizeof(struct flist)); |
if (NULL == pp) { |
if (pp == NULL) { |
ERR(sess, "recallocarray"); |
ERR(sess, "recallocarray"); |
return 0; |
return 0; |
} |
} |
|
|
* only the filename part for the receiver. |
* only the filename part for the receiver. |
*/ |
*/ |
|
|
if (NULL == (f->path = strdup(path))) { |
if ((f->path = strdup(path)) == NULL) { |
ERR(sess, "strdup"); |
ERR(sess, "strdup"); |
return 0; |
return 0; |
} |
} |
|
|
if (NULL == (f->wpath = strrchr(f->path, '/'))) |
if ((f->wpath = strrchr(f->path, '/')) == NULL) |
f->wpath = f->path; |
f->wpath = f->path; |
else |
else |
f->wpath++; |
f->wpath++; |
|
|
|
|
if (S_ISLNK(st->st_mode)) { |
if (S_ISLNK(st->st_mode)) { |
f->link = symlink_read(sess, f->path); |
f->link = symlink_read(sess, f->path); |
if (NULL == f->link) { |
if (f->link == NULL) { |
ERRX1(sess, "symlink_read"); |
ERRX1(sess, "symlink_read"); |
return 0; |
return 0; |
} |
} |
|
|
if (!io_read_byte(sess, fd, &flag)) { |
if (!io_read_byte(sess, fd, &flag)) { |
ERRX1(sess, "io_read_byte"); |
ERRX1(sess, "io_read_byte"); |
goto out; |
goto out; |
} else if (0 == flag) |
} else if (flag == 0) |
break; |
break; |
|
|
if (!flist_realloc(sess, &fl, &flsz, &flmax)) { |
if (!flist_realloc(sess, &fl, &flsz, &flmax)) { |
|
|
goto out; |
goto out; |
} |
} |
ff->st.mtime = ival; |
ff->st.mtime = ival; |
} else if (NULL == fflast) { |
} else if (fflast == NULL) { |
ERRX(sess, "same time without last entry"); |
ERRX(sess, "same time without last entry"); |
goto out; |
goto out; |
} else |
} else |
|
|
goto out; |
goto out; |
} |
} |
ff->st.mode = ival; |
ff->st.mode = ival; |
} else if (NULL == fflast) { |
} else if (fflast == NULL) { |
ERRX(sess, "same mode without last entry"); |
ERRX(sess, "same mode without last entry"); |
goto out; |
goto out; |
} else |
} else |
|
|
if (!io_read_size(sess, fd, &lsz)) { |
if (!io_read_size(sess, fd, &lsz)) { |
ERRX1(sess, "io_read_size"); |
ERRX1(sess, "io_read_size"); |
goto out; |
goto out; |
} else if (0 == lsz) { |
} else if (lsz == 0) { |
ERRX(sess, "empty link name"); |
ERRX(sess, "empty link name"); |
goto out; |
goto out; |
} |
} |
ff->link = calloc(lsz + 1, 1); |
ff->link = calloc(lsz + 1, 1); |
if (NULL == ff->link) { |
if (ff->link == NULL) { |
ERR(sess, "calloc"); |
ERR(sess, "calloc"); |
goto out; |
goto out; |
} |
} |
|
|
* the non-recursive scan. |
* the non-recursive scan. |
*/ |
*/ |
|
|
if (-1 == lstat(root, &st)) { |
if (lstat(root, &st) == -1) { |
ERR(sess, "%s: lstat", root); |
ERR(sess, "%s: lstat", root); |
return 0; |
return 0; |
} else if (S_ISREG(st.st_mode)) { |
} else if (S_ISREG(st.st_mode)) { |
|
|
return 0; |
return 0; |
} |
} |
f = &(*fl)[(*sz) - 1]; |
f = &(*fl)[(*sz) - 1]; |
assert(NULL != f); |
assert(f != NULL); |
|
|
if (!flist_append(sess, f, &st, root)) { |
if (!flist_append(sess, f, &st, root)) { |
ERRX1(sess, "flist_append"); |
ERRX1(sess, "flist_append"); |
return 0; |
return 0; |
} else if (-1 == unveil(root, "r")) { |
} else if (unveil(root, "r") == -1) { |
ERR(sess, "%s: unveil", root); |
ERR(sess, "%s: unveil", root); |
return 0; |
return 0; |
} |
} |
|
|
return 0; |
return 0; |
} |
} |
f = &(*fl)[(*sz) - 1]; |
f = &(*fl)[(*sz) - 1]; |
assert(NULL != f); |
assert(f != NULL); |
|
|
if (!flist_append(sess, f, &st, root)) { |
if (!flist_append(sess, f, &st, root)) { |
ERRX1(sess, "flist_append"); |
ERRX1(sess, "flist_append"); |
return 0; |
return 0; |
} else if (-1 == unveil(root, "r")) { |
} else if (unveil(root, "r") == -1) { |
ERR(sess, "%s: unveil", root); |
ERR(sess, "%s: unveil", root); |
return 0; |
return 0; |
} |
} |
|
|
|
|
stripdir = strlen(root); |
stripdir = strlen(root); |
assert(stripdir > 0); |
assert(stripdir > 0); |
if ('/' != root[stripdir - 1]) |
if (root[stripdir - 1] != '/') |
stripdir = 0; |
stripdir = 0; |
|
|
/* |
/* |
|
|
* last directory component. |
* last directory component. |
*/ |
*/ |
|
|
if (0 == stripdir) |
if (stripdir == 0) |
if (NULL != (cp = strrchr(root, '/'))) |
if ((cp = strrchr(root, '/')) != NULL) |
stripdir = cp - root + 1; |
stripdir = cp - root + 1; |
|
|
/* |
/* |
|
|
* We'll make sense of it in flist_send. |
* We'll make sense of it in flist_send. |
*/ |
*/ |
|
|
if (NULL == (fts = fts_open(cargv, FTS_PHYSICAL, NULL))) { |
if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) { |
ERR(sess, "fts_open"); |
ERR(sess, "fts_open"); |
return 0; |
return 0; |
} |
} |
|
|
errno = 0; |
errno = 0; |
while (NULL != (ent = fts_read(fts))) { |
while ((ent = fts_read(fts)) != NULL) { |
if (!flist_fts_check(sess, ent)) { |
if (!flist_fts_check(sess, ent)) { |
errno = 0; |
errno = 0; |
continue; |
continue; |
|
|
|
|
/* We don't allow symlinks without -l. */ |
/* We don't allow symlinks without -l. */ |
|
|
assert(NULL != ent->fts_statp); |
assert(ent->fts_statp != NULL); |
if (S_ISLNK(ent->fts_statp->st_mode) && |
if (S_ISLNK(ent->fts_statp->st_mode) && |
!sess->opts->preserve_links) { |
!sess->opts->preserve_links) { |
WARNX(sess, "%s: skipping " |
WARNX(sess, "%s: skipping " |
|
|
goto out; |
goto out; |
} |
} |
} else { |
} else { |
if (NULL == (f->path = strdup(ent->fts_path))) { |
if ((f->path = strdup(ent->fts_path)) == NULL) { |
ERR(sess, "strdup"); |
ERR(sess, "strdup"); |
goto out; |
goto out; |
} |
} |
|
|
|
|
if (S_ISLNK(ent->fts_statp->st_mode)) { |
if (S_ISLNK(ent->fts_statp->st_mode)) { |
f->link = symlink_read(sess, f->path); |
f->link = symlink_read(sess, f->path); |
if (NULL == f->link) { |
if (f->link == NULL) { |
ERRX1(sess, "symlink_read"); |
ERRX1(sess, "symlink_read"); |
goto out; |
goto out; |
} |
} |
|
|
if (errno) { |
if (errno) { |
ERR(sess, "fts_read"); |
ERR(sess, "fts_read"); |
goto out; |
goto out; |
} else if (-1 == unveil(root, "r")) { |
} else if (unveil(root, "r") == -1) { |
ERR(sess, "%s: unveil", root); |
ERR(sess, "%s: unveil", root); |
goto out; |
goto out; |
} |
} |
|
|
|
|
assert(argc); |
assert(argc); |
|
|
if (NULL == (fl = calloc(argc, sizeof(struct flist)))) { |
if ((fl = calloc(argc, sizeof(struct flist))) == NULL) { |
ERR(sess, "calloc"); |
ERR(sess, "calloc"); |
return 0; |
return 0; |
} |
} |
|
|
for (i = 0; i < argc; i++) { |
for (i = 0; i < argc; i++) { |
if ('\0' == argv[i][0]) |
if ('\0' == argv[i][0]) |
continue; |
continue; |
if (-1 == lstat(argv[i], &st)) { |
if (lstat(argv[i], &st) == -1) { |
ERR(sess, "%s: lstat", argv[i]); |
ERR(sess, "%s: lstat", argv[i]); |
goto out; |
goto out; |
} |
} |
|
|
|
|
|
|
f = &fl[flsz++]; |
f = &fl[flsz++]; |
assert(NULL != f); |
assert(f != NULL); |
|
|
/* Add this file to our file-system worldview. */ |
/* Add this file to our file-system worldview. */ |
|
|
if (-1 == unveil(argv[i], "r")) { |
if (unveil(argv[i], "r") == -1) { |
ERR(sess, "%s: unveil", argv[i]); |
ERR(sess, "%s: unveil", argv[i]); |
goto out; |
goto out; |
} else if (!flist_append(sess, f, &st, argv[i])) { |
} else if (!flist_append(sess, f, &st, argv[i])) { |
|
|
|
|
/* After scanning, lock our file-system view. */ |
/* After scanning, lock our file-system view. */ |
|
|
if (-1 == unveil(NULL, NULL)) { |
if (unveil(NULL, NULL) == -1) { |
ERR(sess, "unveil"); |
ERR(sess, "unveil"); |
return 0; |
return 0; |
} else if (!rc) |
} else if (!rc) |
|
|
for (i = 0; i < wflsz; i++) |
for (i = 0; i < wflsz; i++) |
if (FLSTAT_TOP_DIR & wfl[i].st.flags) |
if (FLSTAT_TOP_DIR & wfl[i].st.flags) |
cargvs++; |
cargvs++; |
if (0 == cargvs) |
if (cargvs == 0) |
return 1; |
return 1; |
|
|
if (NULL == (cargv = calloc(cargvs + 1, sizeof(char *)))) { |
if ((cargv = calloc(cargvs + 1, sizeof(char *))) == NULL) { |
ERR(sess, "calloc"); |
ERR(sess, "calloc"); |
return 0; |
return 0; |
} |
} |
|
|
* Otherwise, look through all top-levels. |
* Otherwise, look through all top-levels. |
*/ |
*/ |
|
|
if (wflsz && 0 == strcmp(wfl[0].wpath, ".")) { |
if (wflsz && strcmp(wfl[0].wpath, ".") == 0) { |
assert(1 == cargvs); |
assert(cargvs == 1); |
assert(S_ISDIR(wfl[0].st.mode)); |
assert(S_ISDIR(wfl[0].st.mode)); |
if (asprintf(&cargv[0], "%s/", root) < 0) { |
if (asprintf(&cargv[0], "%s/", root) < 0) { |
ERR(sess, "asprintf"); |
ERR(sess, "asprintf"); |
|
|
continue; |
continue; |
assert(S_ISDIR(wfl[i].st.mode)); |
assert(S_ISDIR(wfl[i].st.mode)); |
assert(strcmp(wfl[i].wpath, ".")); |
assert(strcmp(wfl[i].wpath, ".")); |
c = asprintf(&cargv[j], |
c = asprintf(&cargv[j], "%s/%s", root, wfl[i].wpath); |
"%s/%s", root, wfl[i].wpath); |
|
if (c < 0) { |
if (c < 0) { |
ERR(sess, "asprintf"); |
ERR(sess, "asprintf"); |
cargv[j] = NULL; |
cargv[j] = NULL; |
|
|
|
|
for (i = 0; i < wflsz; i++) { |
for (i = 0; i < wflsz; i++) { |
memset(&hent, 0, sizeof(ENTRY)); |
memset(&hent, 0, sizeof(ENTRY)); |
if (NULL == (hent.key = strdup(wfl[i].wpath))) { |
if ((hent.key = strdup(wfl[i].wpath)) == NULL) { |
ERR(sess, "strdup"); |
ERR(sess, "strdup"); |
goto out; |
goto out; |
} |
} |
if (NULL == (hentp = hsearch(hent, ENTER))) { |
if ((hentp = hsearch(hent, ENTER)) == NULL) { |
ERR(sess, "hsearch"); |
ERR(sess, "hsearch"); |
goto out; |
goto out; |
} else if (hentp->key != hent.key) { |
} else if (hentp->key != hent.key) { |
|
|
* If the directories don't exist, it's ok. |
* If the directories don't exist, it's ok. |
*/ |
*/ |
|
|
if (NULL == (fts = fts_open(cargv, FTS_PHYSICAL, NULL))) { |
if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) { |
ERR(sess, "fts_open"); |
ERR(sess, "fts_open"); |
goto out; |
goto out; |
} |
} |
|
|
stripdir = strlen(root) + 1; |
stripdir = strlen(root) + 1; |
errno = 0; |
errno = 0; |
while (NULL != (ent = fts_read(fts))) { |
while ((ent = fts_read(fts)) != NULL) { |
if (FTS_NS == ent->fts_info) |
if (ent->fts_info == FTS_NS) |
continue; |
continue; |
if (!flist_fts_check(sess, ent)) { |
if (!flist_fts_check(sess, ent)) { |
errno = 0; |
errno = 0; |
|
|
|
|
memset(&hent, 0, sizeof(ENTRY)); |
memset(&hent, 0, sizeof(ENTRY)); |
hent.key = ent->fts_path + stripdir; |
hent.key = ent->fts_path + stripdir; |
if (NULL != hsearch(hent, FIND)) |
if (hsearch(hent, FIND) != NULL) |
continue; |
continue; |
|
|
/* Not found: we'll delete it. */ |
/* Not found: we'll delete it. */ |
|
|
} |
} |
f = &(*fl)[*sz - 1]; |
f = &(*fl)[*sz - 1]; |
|
|
if (NULL == (f->path = strdup(ent->fts_path))) { |
if ((f->path = strdup(ent->fts_path)) == NULL) { |
ERR(sess, "strdup"); |
ERR(sess, "strdup"); |
goto out; |
goto out; |
} |
} |
f->wpath = f->path + stripdir; |
f->wpath = f->path + stripdir; |
assert(NULL != ent->fts_statp); |
assert(ent->fts_statp != NULL); |
flist_copy_stat(f, ent->fts_statp); |
flist_copy_stat(f, ent->fts_statp); |
errno = 0; |
errno = 0; |
} |
} |
|
|
qsort(*fl, *sz, sizeof(struct flist), flist_cmp); |
qsort(*fl, *sz, sizeof(struct flist), flist_cmp); |
rc = 1; |
rc = 1; |
out: |
out: |
if (NULL != fts) |
if (fts != NULL) |
fts_close(fts); |
fts_close(fts); |
for (i = 0; i < cargvs; i++) |
for (i = 0; i < cargvs; i++) |
free(cargv[i]); |
free(cargv[i]); |
|
|
ssize_t i; |
ssize_t i; |
int flag; |
int flag; |
|
|
if (0 == flsz) |
if (flsz == 0) |
return 1; |
return 1; |
|
|
assert(sess->opts->del); |
assert(sess->opts->del); |
|
|
LOG1(sess, "%s: deleting", fl[i].wpath); |
LOG1(sess, "%s: deleting", fl[i].wpath); |
if (sess->opts->dry_run) |
if (sess->opts->dry_run) |
continue; |
continue; |
assert(-1 != root); |
assert(root != -1); |
flag = S_ISDIR(fl[i].st.mode) ? AT_REMOVEDIR : 0; |
flag = S_ISDIR(fl[i].st.mode) ? AT_REMOVEDIR : 0; |
if (-1 == unlinkat(root, fl[i].wpath, flag) && |
if (unlinkat(root, fl[i].wpath, flag) == -1 && |
ENOENT != errno) { |
errno != ENOENT) { |
ERR(sess, "%s: unlinkat", fl[i].wpath); |
ERR(sess, "%s: unlinkat", fl[i].wpath); |
return 0; |
return 0; |
} |
} |