/* $NetBSD: fsu_ecp.c,v 1.10 2009/11/06 11:47:41 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 #include #ifdef __NetBSD__ #include #elif !defined(PATH_MAX) #define PATH_MAX (1024) #endif #if HAVE_NBCOMPAT_H #include #endif #include #include #include #include #include #include #include #include #include #include #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 (argv[cur_arg][--len] == '/') 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 (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; if (to_p[tlen - 1] == '/') --tlen; else if (to_p[tlen - 1] != '/' && filename[0] != '/') { to_p[tlen] = '/'; to_p[tlen + 1] = '\0'; } 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) return -1; if (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("Copying fifo %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", 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); }