/* * QA Remote Copy - client side * * Copy a file to, or from, server. * * Copyright © 2005-2006 Red Hat, Inc. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions of the * GNU General Public License v.2. This program is distributed in the hope * that it will be useful, but WITHOUT ANY WARRANTY expressed or implied, * including the implied warranties of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat * trademarks that are incorporated in the source code or documentation are not * subject to the GNU General Public License and may only be used or replicated * with the express permission of Red Hat, Inc. * * Red Hat Author(s): Nathan Straz * Dean Jansa * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sockutil.h" #include "qarsh_packet.h" #define QARSHD_CONTROL_PORT 5008 #define QARSH_MINPORT 5010 /* Globals */ int qacp_fd = -1; /* The control connection to qacpd */ short quiet = 0; void usage() { /* printf("usage: qacp: [-r] [[user@]host1:]file1 [...] " */ /* "[[user@]host2:]file2\n"); */ /* For now, only simple cmdlines */ fprintf(stderr, "usage:\n\t%s\n\t%s\n", "qacp [ ...] [user@]host:", "qacp [user@]host: [[user@]host: ...] "); } void set_remote_user(char *user, char *group) { struct qa_packet *qp; qp = make_qp_setuser(user, group); qp->qp_seq = 1; send_packet(qacp_fd, qp); qpfree(qp); qp = recv_packet(qacp_fd); if (qp && qp->qp_type == QP_RETURNCODE && qp->qp_returncode.qp_rc == -1) { fprintf(stderr, "Remote side failed, %s\n", qp->qp_returncode.qp_strerror); close(qacp_fd); exit(125); } } struct qp_rstat_pkt * qacp_rstat(const char *rmtpath, int *rstaterrno) { struct qa_packet *qp; struct qp_rstat_pkt *rstatp; qp = make_qp_rstat(rmtpath, NULL); send_packet(qacp_fd, qp); qpfree(qp); qp = recv_packet(qacp_fd); if (qp) { if (qp->qp_type == QP_RSTAT) { rstatp = malloc(sizeof *rstatp); rstatp->qp_path = strdup(qp->qp_rstat.qp_path); rstatp->qp_st_mode = qp->qp_rstat.qp_st_mode; rstatp->qp_st_uid = qp->qp_rstat.qp_st_uid; rstatp->qp_st_gid = qp->qp_rstat.qp_st_gid; rstatp->qp_st_size = qp->qp_rstat.qp_st_size; *rstaterrno = 0; } else if (qp->qp_type == QP_RETURNCODE) { rstatp = NULL; *rstaterrno = qp->qp_returncode.qp_errno; } qpfree(qp); } return rstatp; } void qacp_sendonefile(const char *host, const char *srcfile, const char *destfile) { struct qa_packet *qp; struct sockaddr_in daddr; socklen_t dlen; int fd; int sd; int outsd; int port; ssize_t nbytes; off_t offset = 0; struct stat sb; char *fallbackbuf; int bufsize; if ((fd = open(srcfile, O_RDONLY)) <0) { fprintf(stderr, "Could not open %s: %s\n", srcfile, strerror(errno)); close(qacp_fd); exit(errno); } if (fstat(fd, &sb) < 0) { fprintf(stderr, "Could not stat %s: %s\n", srcfile, strerror(errno)); close(qacp_fd); exit(errno); } sd = bind_any(QARSH_MINPORT); port = getsockport(sd); /* Recall that the packet types are qarshd-centric, so if we want * to send a file to the host running qarshd we have to tell * qarshd to recv a file. */ qp = make_qp_recvfile(destfile, port, sb.st_size, sb.st_mode); qp->qp_seq = 1; send_packet(qacp_fd, qp); qpfree(qp); dlen = sizeof daddr; outsd = accept(sd, (struct sockaddr *) &daddr, &dlen); do { nbytes = sendfile(outsd, fd, &offset, sb.st_size); } while (nbytes >= 0 && offset < sb.st_size); if (nbytes == -1 && errno == EINVAL) { fprintf(stderr, "Falling back to reads\n"); if (sb.st_size > 1024000) { bufsize = 8192; } else { bufsize = 1024000; } fallbackbuf = malloc(bufsize); if (fallbackbuf == NULL) { fprintf(stderr, "Could not allocate transfer buffer\n"); goto sendone_failure; } do { nbytes = read(fd, fallbackbuf, bufsize); if (nbytes < 0) { fprintf(stderr, "read() error: %s\n", strerror(errno)); goto sendone_failure; } else if (nbytes == 0) { /* EOF */ break; } write(outsd, fallbackbuf, nbytes); } while (nbytes > 0); } else if (nbytes == -1) { fprintf(stderr, "error: %s\n", strerror(errno)); } sendone_failure: close(sd); close(outsd); close(fd); /* Await our return code from qarshd */ qp = recv_packet(qacp_fd); if (qp && qp->qp_type == QP_RETURNCODE && qp->qp_returncode.qp_rc == -1) { fprintf(stderr, "Remote side failed, %s\n", qp->qp_returncode.qp_strerror); close(qacp_fd); exit(125); } if (!quiet) { printf("%-30.30s -> %s:%s\n", srcfile, host, destfile); } return; } void qacp_recvonefile(const char *host, const char *srcfile, const char *destfile) { struct qa_packet *qp; struct sockaddr_in daddr; socklen_t dlen; int sd; int port; int insd; int outfd; char buf[BUFSIZ]; ssize_t nread, nwrote; off_t nleft; struct qp_rstat_pkt *rstatp; int rstaterrno; rstatp = qacp_rstat(srcfile, &rstaterrno); if (!rstatp) { fprintf(stderr, "%s: %s\n", srcfile, strerror(rstaterrno)); return; } if ((outfd = open(destfile, O_TRUNC|O_CREAT|O_WRONLY)) <0) { fprintf(stderr, "Could not open %s: %s\n", destfile, strerror(errno)); close(qacp_fd); exit(errno); } fchmod(outfd, rstatp->qp_st_mode); sd = bind_any(QARSH_MINPORT); port = getsockport(sd); /* Recall that the packet types are qarshd-centric, so if we want * to recv a file from the host running qarshd we have to tell * qarshd to send a file. */ qp = make_qp_sendfile(srcfile, port); qp->qp_seq = 1; send_packet(qacp_fd, qp); qpfree(qp); dlen = sizeof daddr; insd = accept(sd, (struct sockaddr *) &daddr, &dlen); /* Read/write file */ nleft = rstatp->qp_st_size; while (nleft > 0) { nread = read(insd, buf, BUFSIZ); if (nread < 0) { fprintf(stderr, "read() error: %s\n", strerror(errno)); break; } else if (nread == 0) { /* EOF */ break; } nwrote = write(outfd, buf, nread); if (nwrote < 0) { fprintf(stderr, "write() error: %s\n", strerror(errno)); break; } nleft -= nread; } if (nleft != 0) { unlink(destfile); fprintf(stderr, "Short file transfer of %s, " "%lld bytes lost, wanted %lld\n", srcfile, (long long int)nleft, (long long int)rstatp->qp_st_size); } close(sd); close(insd); close(outfd); /* Await our return code from qarshd */ qp = recv_packet(qacp_fd); if (qp && qp->qp_type == QP_RETURNCODE && qp->qp_returncode.qp_rc == -1) { fprintf(stderr, "Remote side failed, %s\n", qp->qp_returncode.qp_strerror); close(qacp_fd); exit(125); } if (!quiet) { printf("%s:%s -> %30.30s\n", host, srcfile, destfile); } return; } int recvfiles(char **argv, int argc, int fileidx, short recursive) { char *cp; char *rhost = NULL; char *ruser = NULL; char *rgrp = NULL; struct passwd *pw; int file; char *rmtpath; char *destpath; int destisdir; char *tmpstr; char *ldnp; /* local path dirname */ char *rbnp; /* remote path basename */ struct qp_rstat_pkt *rstatp; struct stat sb; int rstaterrno; if (strchr(argv[argc-1], ':') != NULL) { fprintf(stderr, "%s is not a valid local file string\n", argv[argc-1]); return -1; } destpath = strdup(argv[argc-1]); /* Figure out if our destpath is just a dir. * If so we need to set the flag to tack on the remote * path basename onto our destpath */ if (stat(destpath, &sb) < 0) { /* stat couldn't file the file, check if the dirname exists, * if so we need to reset our destpath to the local dirname * if that directory exists. */ tmpstr = strdup(destpath); ldnp = strdup(dirname(tmpstr)); free(tmpstr); if (stat(ldnp, &sb) < 0) { fprintf(stderr, "%s: %s\n", ldnp, strerror(errno)); return -1; } if (S_ISDIR(sb.st_mode)) { /* No, we don't want to set destisdir, as our dest is really * a full path, we striped off the filename above to check * the rest of the path exists. */ destisdir = 0; } else { fprintf(stderr, "%s not a directory\n", ldnp); return -1; } } else { if (S_ISDIR(sb.st_mode)) { destisdir = 1; } else if (S_ISREG(sb.st_mode)) { destisdir = 0; } else { fprintf(stderr, "%s not a file or directory\n", destpath); return -1; } } for (file = fileidx; file < (argc - 1); file++) { /* For each file in remote file list (fileidx to argc-2) */ /* Rstat the file, if it is a dir, error */ /* Otherwise copy it to local location */ if (strchr(argv[file], ':') == NULL) { fprintf(stderr, "%s is not a valid remote host:file string\n", argv[file]); return -1; } rhost = strdup(argv[file]); cp = strchr(rhost, ':'); *cp = '\0'; /* Grab any user/group info */ if ((cp = strchr(rhost, '@'))) { ruser = rhost; rhost = cp+1; *cp = '\0'; } if (ruser && (cp = strchr(ruser, '.'))) { rgrp = cp+1; *cp = '\0'; } if (!(pw = getpwuid(getuid()))) { fprintf(stderr, "qacp: unknown user id.\n"); return -1; } if (ruser == NULL) { ruser = strdup(pw->pw_name); } qacp_fd = connect_to_host(rhost, QARSHD_CONTROL_PORT); if (qacp_fd == -1) { if (errno == 0) { fprintf(stderr, "Could not connect to %s:%d, %d: %s\n", rhost, QARSHD_CONTROL_PORT, h_errno, hstrerror(h_errno)); } else { fprintf(stderr, "Could not connect to %s:%d, %d: %s\n", rhost, QARSHD_CONTROL_PORT, errno, strerror(errno)); } return -1; } set_remote_user(ruser, rgrp); cp = strdup(argv[file]); cp = strchr(argv[file], ':'); cp++; rmtpath = strdup(cp); rstatp = qacp_rstat(rmtpath, &rstaterrno); if (rstatp) { if (S_ISREG(rstatp->qp_st_mode)) { if (destisdir) { cp = strdup(rmtpath); rbnp = strdup(basename(cp)); free(cp); cp = malloc(strlen(destpath) + strlen(rbnp) + 2); strcpy(cp, destpath); strcat(cp, "/"); strcat(cp, rbnp); qacp_recvonefile(rhost, rmtpath, cp); free(cp); free(rbnp); } else { qacp_recvonefile(rhost, rmtpath, destpath); } } if (S_ISDIR(rstatp->qp_st_mode)) { fprintf(stderr, "%s: Not a regular file\n", argv[file]); return -1; } } else { fprintf(stderr, "%s: %s\n", argv[file], strerror(rstaterrno)); return -1; } close(qacp_fd); } return 0; } int sendfiles(char **argv, int argc, int fileidx, short recursive) { char *cp; char *rhost = NULL; char *ruser = NULL; char *rgrp = NULL; struct passwd *pw; int file; char *rmtpath; char *destpath; char *tmpstr; char *lbnp; /* local file basename */ char *rdnp; /* remote path dirname */ struct qp_rstat_pkt *rstatp; int rstaterrno; if (strchr(argv[argc-1], ':') == NULL) { fprintf(stderr, "%s is not a valid remote host:file string\n", argv[argc-1]); return -1; } rhost = strdup(argv[argc-1]); cp = strchr(rhost, ':'); *cp = '\0'; /* Grab any user/group info */ if ((cp = strchr(rhost, '@'))) { ruser = rhost; rhost = cp+1; *cp = '\0'; } if (ruser && (cp = strchr(ruser, '.'))) { rgrp = cp+1; *cp = '\0'; } if (!(pw = getpwuid(getuid()))) { fprintf(stderr, "qacp: unknown user id.\n"); return -1; } if (ruser == NULL) { ruser = strdup(pw->pw_name); } qacp_fd = connect_to_host(rhost, QARSHD_CONTROL_PORT); if (qacp_fd == -1) { if (errno == 0) { fprintf(stderr, "Could not connect to %s:%d, %d: %s\n", rhost, QARSHD_CONTROL_PORT, h_errno, hstrerror(h_errno)); } else { fprintf(stderr, "Could not connect to %s:%d, %d: %s\n", rhost, QARSHD_CONTROL_PORT, errno, strerror(errno)); } return -1; } set_remote_user(ruser, rgrp); cp = strdup(argv[argc-1]); cp = strchr(argv[argc-1], ':'); cp++; rmtpath = strdup(cp); rstatp = qacp_rstat(rmtpath, &rstaterrno); for (file = fileidx; file < (argc - 1); file++) { tmpstr = strdup(argv[file]); lbnp = strdup(basename(tmpstr)); free(tmpstr); if (rstatp) { /* * If rmtpath is a dir, then we tack on the * local files basename. */ if (S_ISDIR(rstatp->qp_st_mode)) { destpath = malloc(strlen(rmtpath) + strlen(lbnp) + 2); strcpy(destpath, rmtpath); strcat(destpath, "/"); strcat(destpath, lbnp); } /* It rmtpath is a file, then we leave it as is */ if (S_ISREG(rstatp->qp_st_mode)) { destpath = strdup(rmtpath); } } else { /* rmtpath does not exist, check if the dirname does */ tmpstr = strdup(rmtpath); rdnp = strdup(dirname(tmpstr)); free(tmpstr); rstatp = qacp_rstat(rdnp, &rstaterrno); if (rstatp) { /* Ok, the dir exists, i.e. rmtpath is /tmp/foo * and the first rstat was an error. Now we know * that /tmp exists, we leave destpath == rmtpath. */ destpath = strdup(rmtpath); } else { fprintf(stderr, "%s:%s - %s\n", rhost, rmtpath, strerror(rstaterrno)); return -1; } } qacp_sendonefile(rhost, argv[file], destpath); free(lbnp); free(destpath); } return 0; } int main(int argc, char *argv[]) { int c; short recursive=0; int eflag; while ((c = getopt(argc, argv, "+rq")) != -1) { switch (c) { case 'r': fprintf(stderr, "-r not supported\n"); exit(2); recursive = 1; break; case 'q': quiet = 1; break; case '?': default: printf("Unknown option %c\n", (char)optopt); usage(); exit(1); } } if (argv[optind] == NULL) { usage(); exit(2); } eflag = 0; /* If last arg has a ':' then we are sending files */ if (strchr(argv[argc-1], ':') != NULL) { if (sendfiles(argv, argc, optind, recursive) < 0) { eflag++; } } else { if (recvfiles(argv, argc, optind, recursive) < 0) { eflag++; } } close(qacp_fd); if (eflag) { exit(1); } exit(0); }