/* $NetBSD: fsu_ecp.c,v 1.11 2009/11/17 15:27:39 stacktic Exp $ */
/*
* Copyright (c) 2008 Arnaud Ysmal. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/queue.h>
#include <sys/stat.h>
#ifdef __NetBSD__
#include <sys/syslimits.h>
#elif !defined(PATH_MAX)
#define PATH_MAX (1024)
#endif
#if HAVE_NBCOMPAT_H
#include <nbcompat.h>
#endif
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <rump/ukfs.h>
#include <fsu_mount.h>
#include "fsu_flist.h"
#define FSU_ECP_NO_COPY_LINK (0x01)
#define FSU_ECP_RECURSIVE (FSU_ECP_NO_COPY_LINK<<1)
#define FSU_ECP_VERBOSE (FSU_ECP_RECURSIVE<<1)
#define FSU_ECP_GET (FSU_ECP_VERBOSE<<1)
#define FSU_ECP_PUT (FSU_ECP_GET<<1)
DECLARE_UKFS(ukfs)
#define BUFSIZE (8192)
static int copy_dir(struct ukfs *, const char *, const char *, int);
static int copy_dir_rec(struct ukfs *, const char *, char *, int);
static int copy_fifo(struct ukfs *, const char *, const char *, int);
static int copy_file(struct ukfs *, const char *, const char *, int);
static int copy_filein(struct ukfs *, const char *, const char *);
static int copy_fileout(const char *, const char *);
static int copy_link(struct ukfs *, const char *, const char *, int);
static int copy_special(struct ukfs *, const char *, const char *, int);
static int copy_to_dir(struct ukfs *, const char *, struct stat *,
const char *, int);
static int copy_to_file(struct ukfs *, const char *, struct stat *,
const char *, int);
static int fsu_ecp(struct ukfs *, const char *, const char *, int);
static int fsu_ecp_parse_arg(int *, char ***);
static void usage(void);
struct hardlink_s {
char *hl_from;
char **hl_to;
int hl_nlink;
LIST_ENTRY(hardlink_s) next;
};
int
main(int argc, char *argv[])
{
size_t len;
int cur_arg, flags, rv;
setprogname(argv[0]);
FSU_MOUNT(argc, argv, ukfs);
flags = fsu_ecp_parse_arg(&argc, &argv);
if (flags == -1 || argc < 2) {
usage();
return -1;
}
for (rv = 0, cur_arg = 0; cur_arg < argc-1; ++cur_arg) {
len = strlen(argv[cur_arg]);
while (len != 1 && argv[cur_arg][len - 1] == '/')
argv[cur_arg][--len] = '\0';
rv |= fsu_ecp(ukfs, argv[cur_arg], argv[argc-1], flags);
}
return rv;
}
static int
fsu_ecp_parse_arg(int *argc, char ***argv)
{
int flags, rv;
const char *progname;
flags = 0;
progname = getprogname();
if (strcmp(progname, "get") == 0 || strcmp(progname, "fsu_get") == 0)
flags |= FSU_ECP_GET;
else if (strcmp(progname, "put") == 0 ||
strcmp(progname, "fsu_put") == 0)
flags |= FSU_ECP_PUT;
while ((rv = getopt(*argc, *argv, "gLpRv")) != -1) {
switch (rv) {
case 'g':
flags |= FSU_ECP_GET;
flags &= ~FSU_ECP_PUT;
break;
case 'L':
flags |= FSU_ECP_NO_COPY_LINK;
break;
case 'p':
flags |= FSU_ECP_PUT;
flags &= ~FSU_ECP_GET;
break;
case 'R':
flags |= FSU_ECP_RECURSIVE;
break;
case 'v':
flags |= FSU_ECP_VERBOSE;
break;
case '?':
default:
return -1;
}
}
*argc -= optind;
*argv += optind;
if ((flags & (FSU_ECP_GET | FSU_ECP_PUT)) == 0) {
warnx("-g or -p should be specified");
return -1;
}
return flags;
}
static int
fsu_ecp(struct ukfs *fs, const char *from, const char *to, int flags)
{
struct stat from_stat, to_stat;
int rv;
if (flags & FSU_ECP_PUT)
rv = lstat(from, &from_stat);
else
rv = ukfs_lstat(fs, from, &from_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
if (S_ISDIR(from_stat.st_mode)) {
if (!(flags & FSU_ECP_RECURSIVE)) {
fprintf(stderr, "%s: is a directory\n", from);
return -1;
}
return copy_dir(fs, from, to, flags);
}
if (flags & FSU_ECP_GET)
rv = stat(to, &to_stat);
else
rv = ukfs_stat(fs, to, &to_stat);
if (rv == 0 && S_ISDIR(to_stat.st_mode))
return copy_to_dir(fs, from, &from_stat, to, flags);
return copy_to_file(fs, from, &from_stat, to, flags);
}
static int
copy_dir(struct ukfs *fs, const char *from, const char *to, int flags)
{
const char *filename;
int rv;
struct stat file_stat;
char to_p[PATH_MAX + 1];
size_t tlen, flen;
tlen = strlen(to);
rv = strlcpy(to_p, to, PATH_MAX + 1);
if (rv != (int)tlen) {
warn("%s", to);
return -1;
}
if (to_p[tlen - 1] != '/')
to_p[tlen++] = '/';
if (flags & FSU_ECP_GET)
rv = lstat(to, &file_stat);
else
rv = ukfs_lstat(fs, to, &file_stat);
if (rv == 0) {
if (S_ISDIR(file_stat.st_mode)) {
filename = strrchr(from, '/');
if (filename == NULL)
filename = from;
else
++filename;
flen = strlen(filename);
rv = strlcat(to_p, filename, PATH_MAX + 1);
if (rv != (int)(flen + tlen + 1)) {
warn("%s/%s", to_p, filename);
return -1;
}
} else {
warnx("%s: not a directory", to);
return -1;
}
}
return copy_dir_rec(fs, from, to_p, flags);
}
static int
copy_dir_rec(struct ukfs *fs, const char *from_p, char *to_p, int flags)
{
FSU_FENT *root, *cur, *cur2, *nextelt;
fsu_flist *flist;
struct stat sb;
size_t len;
int flist_options, res, rv, off, hl_supported, curlink;
struct hardlink_s *new;
char hlfrom[PATH_MAX + 1], hlto[PATH_MAX + 1];
LIST_HEAD(, hardlink_s) hl_l = LIST_HEAD_INITIALIZER(hl_l);
curlink = res = 0;
hl_supported = 1;
if (flags & FSU_ECP_NO_COPY_LINK)
flist_options = 0;
else
flist_options = FSU_FLIST_STATLINK;
if (flags & FSU_ECP_RECURSIVE)
flist_options |= FSU_FLIST_RECURSIVE;
if (flags & FSU_ECP_PUT)
flist_options |= FSU_FLIST_REALFS;
len = strlen(to_p) - 1;
flist = fsu_flist_build(fs, from_p, flist_options);
if (flist == NULL)
return -1;
root = LIST_FIRST(flist);
off = root->pathlen;
LIST_FOREACH(cur, flist, next) {
if (S_ISDIR(cur->sb.st_mode) || cur->sb.st_nlink == 1)
continue;
new = malloc(sizeof(struct hardlink_s));
if (new == NULL) {
warn("malloc");
return -1;
}
new->hl_from = cur->path;
new->hl_nlink = cur->sb.st_nlink - 1;
new->hl_to = malloc(new->hl_nlink * sizeof(char *));
if (new->hl_to == NULL) {
warn("malloc");
free(new);
return -1;
}
memset(new->hl_to, 0, new->hl_nlink * sizeof(char *));
for (curlink = 0, cur2 = cur; LIST_NEXT(cur2, next) != NULL;) {
nextelt = LIST_NEXT(cur2, next);
if (S_ISDIR(nextelt->sb.st_mode) ||
nextelt->sb.st_nlink == 1) {
cur2 = nextelt;
continue;
}
if (flags & FSU_ECP_GET)
ukfs_lstat(fs, nextelt->path, &sb);
else if (flags & FSU_ECP_PUT)
lstat(nextelt->path, &sb);
if (S_ISLNK(sb.st_mode)) {
cur2 = nextelt;
continue;
}
if (cur->sb.st_ino == nextelt->sb.st_ino &&
cur->sb.st_dev == nextelt->sb.st_dev) {
new->hl_to[curlink] = nextelt->path;
if (new->hl_to[curlink] == NULL)
warn("%s", nextelt->path);
else
curlink++;
LIST_REMOVE(nextelt, next);
free(nextelt);
if ((unsigned)(curlink + 1) == cur->sb.st_nlink)
break;
} else
cur2 = nextelt;
}
LIST_INSERT_HEAD(&hl_l, new, next);
}
if (flags & FSU_ECP_GET)
rv = mkdir(to_p, root->sb.st_mode);
else
rv = ukfs_mkdir(fs, to_p, root->sb.st_mode);
if (rv == -1) {
warn("%s", to_p);
goto out;
}
if (!(flags & FSU_ECP_GET)) {
rv = ukfs_chown(fs, to_p, root->sb.st_uid, root->sb.st_gid);
if (rv == -1) {
warn("chown %s", to_p);
}
}
LIST_FOREACH(cur, flist, next) {
if (cur == root)
continue;
rv = strlcat(to_p, cur->path + off, PATH_MAX+1);
if (rv != (int)(len + cur->pathlen - off + 1)) {
warn("%s%s", to_p, cur->path + off);
res = -1;
break;
}
if (S_ISDIR(cur->sb.st_mode)) {
if (flags & FSU_ECP_GET) {
rv = mkdir(to_p, cur->sb.st_mode);
if (rv == -1) {
warn("%s", to_p);
res = -1;
break;
}
} else {
rv = ukfs_mkdir(fs, to_p, cur->sb.st_mode);
if (rv == -1) {
warn("%s", to_p);
res = -1;
break;
}
rv = ukfs_chown(fs, to_p, cur->sb.st_uid,
cur->sb.st_gid);
if (rv == -1) {
warn("chown %s", to_p);
}
}
} else {
res |= copy_to_file(fs, cur->path, &(cur->sb), to_p,
flags);
if (errno == ENOSPC) {
warn(NULL);
goto out;
}
}
to_p[len + 1] = '\0';
}
memcpy(hlfrom, to_p, len + 1);
memcpy(hlto, to_p, len + 1);
while (!LIST_EMPTY(&hl_l)) {
new = LIST_FIRST(&hl_l);
LIST_REMOVE(new, next);
hlfrom[len + 1] = '\0';
strlcat(hlfrom, new->hl_from + off, PATH_MAX+1);
for (curlink = 0;
curlink < new->hl_nlink && new->hl_to[curlink] != NULL;
++curlink) {
hlto[len + 1] = '\0';
strlcat(hlto, new->hl_to[curlink] + off, PATH_MAX+1);
if (hl_supported) {
if (flags & FSU_ECP_GET)
rv = link(hlfrom, hlto);
else
rv = ukfs_link(fs, hlfrom, hlto);
if (rv != 0 && errno == EOPNOTSUPP) {
hl_supported = 0;
if (flags & FSU_ECP_GET)
copy_fileout(hlfrom, hlto);
else
copy_filein(fs, hlfrom, hlto);
}
} else {
if (flags & FSU_ECP_GET)
copy_fileout(hlfrom, hlto);
else
copy_filein(fs, hlfrom, hlto);
}
free(new->hl_to[curlink]);
}
free(new->hl_to);
free(new);
}
out:
fsu_flist_free(flist);
return res;
}
static int
copy_to_dir(struct ukfs *fs, const char *from, struct stat *frstat,
const char *to, int flags)
{
char path[PATH_MAX+1];
const char *filename;
int len;
filename = strrchr(from, '/');
if (filename == NULL)
filename = from;
else
++filename;
len = snprintf(path, PATH_MAX, "%s/%s", to, filename);
path[len] = '\0';
return copy_to_file(fs, from, frstat, path, flags);
}
static int
copy_to_file(struct ukfs *fs, const char *from, struct stat *frstat,
const char *to, int flags)
{
int rv;
switch ((frstat->st_mode & S_IFMT)) {
case S_IFIFO:
rv = copy_fifo(fs, from, to, flags);
break;
case S_IFLNK:
if (!(flags & FSU_ECP_NO_COPY_LINK))
rv = copy_link(fs, from, to, flags);
else
rv = copy_file(fs, from, to, flags);
break;
case S_IFCHR: /* FALLTHROUGH */
case S_IFBLK:
rv = copy_special(fs, from, to, flags);
break;
case S_IFREG:
rv = copy_file(fs, from, to, flags);
break;
default:
return -1;
/* NOTREACHED */
}
if (rv != 0 || flags & FSU_ECP_GET)
return rv;
if (!(flags & FSU_ECP_NO_COPY_LINK))
rv = ukfs_lchown(fs, to, frstat->st_uid, frstat->st_gid);
else
rv = ukfs_chown(fs, to, frstat->st_uid, frstat->st_gid);
if (rv == -1) {
warn("chown %s", to);
}
return 0;
}
static int
copy_file(struct ukfs *fs, const char *from, const char *to, int flags)
{
uint8_t buf[BUFSIZE];
ssize_t rd, wr;
off_t off;
int fd, rv;
struct stat from_stat;
fd = -1;
if (flags & FSU_ECP_VERBOSE)
printf("%s -> %s\n", from, to);
if (flags & FSU_ECP_PUT)
rv = stat(from, &from_stat);
else
rv = ukfs_stat(fs, from, &from_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
if (flags & FSU_ECP_GET) {
rv = open(to, O_WRONLY|O_CREAT|O_EXCL, 0777);
fd = rv;
} else if (flags & FSU_ECP_PUT) {
rv = open(from, O_RDONLY, from_stat.st_mode);
fd = rv;
rv = ukfs_create(fs, to, from_stat.st_mode);
} else {
rv = ukfs_create(fs, to, from_stat.st_mode);
}
if (rv == -1) {
warn("%s", to);
return -1;
}
off = 0;
do {
if (flags & FSU_ECP_PUT)
rd = read(fd, buf, sizeof(buf));
else
rd = ukfs_read(fs, from, off, buf, sizeof(buf));
if (rd == -1) {
warn("%s", from);
rv = -1;
goto out;
}
if (flags & FSU_ECP_GET)
wr = write(fd, buf, rd);
else
wr = ukfs_write(fs, to, off, buf, rd);
if (wr == -1 || wr != rd) {
warn("%s", to);
rv = -1;
goto out;
}
off += rd;
} while (rd > 0);
rv = 0;
out:
if (flags & (FSU_ECP_GET | FSU_ECP_PUT))
close(fd);
return rv;
}
static int
copy_fifo(struct ukfs *fs, const char *from, const char *to, int flags)
{
int rv;
struct stat file_stat;
if (flags & FSU_ECP_VERBOSE)
printf("%s -> %s\n", from, to);
if (flags & FSU_ECP_GET)
rv = lstat(to, &file_stat);
else
rv = ukfs_lstat(fs, to, &file_stat);
if (rv != -1) {
if (flags & FSU_ECP_GET)
rv = remove(to);
else
rv = ukfs_remove(fs, to);
if (rv == -1) {
warn("%s", to);
return -1;
}
}
if (flags & FSU_ECP_PUT)
rv = lstat(from, &file_stat);
else
rv = ukfs_lstat(fs, from, &file_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
if (flags & FSU_ECP_GET)
rv = mkfifo(to, file_stat.st_mode);
else
rv = ukfs_mkfifo(fs, to, file_stat.st_mode);
if (rv == -1) {
warn("%s", to);
return -1;
}
return 0;
}
static int
copy_special(struct ukfs *fs, const char *from, const char *to, int flags)
{
int rv;
struct stat file_stat;
if (flags & FSU_ECP_VERBOSE)
printf("%s -> %s\n", from, to);
if (flags & FSU_ECP_GET)
rv = lstat(to, &file_stat);
else
rv = ukfs_lstat(fs, to, &file_stat);
if (rv != -1) {
rv = ukfs_remove(fs, to);
if (rv == -1) {
warn("%s", to);
return -1;
}
}
if (flags & FSU_ECP_PUT)
rv = lstat(from, &file_stat);
else
rv = ukfs_lstat(fs, from, &file_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
if (flags & FSU_ECP_GET)
rv = mknod(to, file_stat.st_mode, file_stat.st_rdev);
else
rv = ukfs_mknod(fs, to, file_stat.st_mode, file_stat.st_rdev);
if (rv == -1) {
warn("%s", to);
return -1;
}
return 0;
}
static int
copy_link(struct ukfs *fs, const char *from, const char *to, int flags)
{
char target[PATH_MAX+1];
int rv;
struct stat file_stat;
if (flags & FSU_ECP_PUT)
rv = readlink(from, target, PATH_MAX);
else
rv = ukfs_readlink(fs, from, target, PATH_MAX);
if (rv == -1) {
warn("%s", from);
return -1;
}
target[rv] = '\0';
if (flags & FSU_ECP_VERBOSE)
printf("%s -> %s : %s\n", from, to, target);
if (flags & FSU_ECP_GET)
rv = lstat(to, &file_stat);
else
rv = ukfs_lstat(fs, to, &file_stat);
if (rv != -1) {
if (flags & FSU_ECP_GET)
rv = remove(to);
else
rv = ukfs_remove(fs, to);
if (rv == -1) {
warn("%s", to);
return -1;
}
}
if (flags & FSU_ECP_GET)
rv = symlink(target, to);
else
rv = ukfs_symlink(fs, target, to);
if (rv == -1) {
warn("%s", target);
return -1;
}
return 0;
}
static int
copy_filein(struct ukfs *fs, const char *from, const char *to)
{
uint8_t buf[BUFSIZE];
ssize_t rd, wr;
off_t off;
int rv;
struct stat from_stat;
rv = ukfs_stat(fs, from, &from_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
rv = ukfs_create(fs, to, from_stat.st_mode);
if (rv == -1) {
warn("%s", to);
return -1;
}
off = 0;
do {
rd = ukfs_read(fs, from, off, buf, sizeof(buf));
if (rd == -1) {
warn("%s", from);
return -1;
}
wr = ukfs_write(fs, to, off, buf, rd);
if (wr == -1 || wr != rd) {
warn("%s", to);
return -1;
}
off += rd;
} while (rd > 0);
return 0;
}
static int
copy_fileout(const char *from, const char *to)
{
uint8_t buf[BUFSIZE];
ssize_t rd, wr;
int rv, fdfrom, fdto;
struct stat from_stat;
rv = stat(from, &from_stat);
if (rv == -1) {
warn("%s", from);
return -1;
}
fdto = open(to, O_CREAT | O_WRONLY, from_stat.st_mode);
if (fdto == -1) {
warn("%s", to);
return -1;
}
fdfrom = open(to, O_RDONLY, from_stat.st_mode);
if (fdfrom == -1) {
warn("%s", to);
close(fdto);
return -1;
}
do {
rd = read(fdfrom, buf, sizeof(buf));
if (rd == -1) {
warn("%s", from);
rv = -1;
goto out;
}
wr = write(fdto, buf, rd);
if (wr == -1 || wr != rd) {
warn("%s", to);
rv = -1;
goto out;
}
} while (rd > 0);
out:
close(fdfrom);
close(fdto);
return rv;
}
static void
usage(void)
{
fprintf(stderr, "usage: %s %s [-gLpRv] src target\n"
"usage: %s %s [-gLpRv] src... directory\n",
getprogname(), fsu_mount_usage(),
getprogname(), fsu_mount_usage());
exit(EXIT_FAILURE);
}