[BACK]Return to fsu_ecp.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / othersrc / bin / fsu_ecp

File: [cvs.NetBSD.org] / othersrc / bin / fsu_ecp / fsu_ecp.c (download)

Revision 1.11, Tue Nov 17 15:27:39 2009 UTC (12 years, 6 months ago) by stacktic
Branch: MAIN
CVS Tags: HEAD
Changes since 1.10: +8 -16 lines

Fixed fsu_ecp when source is '/'

/* $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);
}