123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- #include <u.h>
- #include <libc.h>
- #include <ctype.h>
- int
- isatty(int fd)
- {
- int8_t buf[64];
- buf[0] = '\0';
- fd2path(fd, buf, sizeof buf);
- if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0)
- return 1;
- return 0;
- }
- #define OK 0x00
- #define ERROR 0x01
- #define FATAL 0x02
- int8_t *progname;
- int dflag;
- int fflag;
- int iflag;
- int pflag;
- int rflag;
- int tflag;
- int vflag;
- int remote;
- int8_t *exitflag = nil;
- void scperror(int, int8_t*, ...);
- void mustbedir(int8_t*);
- void receive(int8_t*);
- int8_t *fileaftercolon(int8_t*);
- void destislocal(int8_t *cmd, int argc, int8_t *argv[], int8_t *dest);
- void destisremote(int8_t *cmd, int argc, int8_t *argv[],
- int8_t *host, int8_t *dest);
- int remotessh(int8_t *host, int8_t *cmd);
- void send(int8_t*);
- void senddir(int8_t*, int, Dir*);
- int getresponse(void);
- int8_t theuser[32];
- int8_t ssh[] = "/bin/ssh";
- int remotefd0;
- int remotefd1;
- int
- runcommand(int8_t *cmd)
- {
- Waitmsg *w;
- int pid;
- int8_t *argv[4];
- if (cmd == nil)
- return -1;
- switch(pid = fork()){
- case -1:
- return -1;
- case 0:
- argv[0] = "rc";
- argv[1] = "-c";
- argv[2] = cmd;
- argv[3] = nil;
- exec("/bin/rc", argv);
- exits("exec failed");
- }
- for(;;){
- w = wait();
- if(w == nil)
- return -1;
- if(w->pid == pid)
- break;
- free(w);
- }
- if(w->msg[0]){
- free(w);
- return -1;
- }
- free(w);
- return 1;
- }
- void
- vprint(int8_t *fmt, ...)
- {
- int8_t buf[1024];
- va_list arg;
- static int8_t *name;
- if(vflag == 0)
- return;
- va_start(arg, fmt);
- vseprint(buf, buf+sizeof(buf), fmt, arg);
- va_end(arg);
- if(name == nil){
- name = sysname();
- if(name == nil)
- name = "<unknown>";
- }
- fprint(2, "%s: %s\n", name, buf);
- }
- void
- usage(void)
- {
- fprint(2, "Usage: scp [-Iidfprtv] source ... destination\n");
- exits("usage");
- }
- #pragma varargck type "F" int
- #pragma varargck type "V" char*
- static int flag;
- /* flag: if integer flag, take following char *value */
- int
- flagfmt(Fmt *f)
- {
- flag = va_arg(f->args, int);
- return 0;
- }
- /* flag: if previous integer flag, take char *value */
- int
- valfmt(Fmt *f)
- {
- int8_t *value;
- value = va_arg(f->args, int8_t*);
- if(flag)
- return fmtprint(f, " %s", value);
- return 0;
- }
- void
- sendokresponse(void)
- {
- int8_t ok = OK;
- write(remotefd1, &ok, 1);
- }
- void
- main(int argc, char *argv[])
- {
- int i, fd;
- char cmd[32];
- char *p;
- progname = argv[0];
- fmtinstall('F', flagfmt);
- fmtinstall('V', valfmt);
- iflag = -1;
- ARGBEGIN {
- case 'I':
- iflag = 0;
- break;
- case 'i':
- iflag = 1;
- break;
- case 'd':
- dflag++;
- break;
- case 'f':
- fflag++;
- remote++;
- break;
- case 'p':
- pflag++;
- break;
- case 'r':
- rflag++;
- break;
- case 't':
- tflag++;
- remote++;
- break;
- case 'v':
- vflag++;
- break;
- default:
- scperror(1, "unknown option %c", ARGC());
- } ARGEND
- if(iflag == -1)
- iflag = isatty(0);
- remotefd0 = 0;
- remotefd1 = 1;
- if(fflag){
- getresponse();
- for(i=0; i<argc; i++)
- send(argv[i]);
- exits(0);
- }
- if(tflag){
- if(argc != 1)
- usage();
- receive(argv[0]);
- exits(0);
- }
- if (argc < 2)
- usage();
- if (argc > 2)
- dflag = 1;
- i = 0;
- fd = open("/dev/user", OREAD);
- if(fd >= 0){
- i = read(fd, theuser, sizeof theuser - 1);
- close(fd);
- }
- if(i <= 0)
- scperror(1, "can't read /dev/user: %r");
- remotefd0 = -1;
- remotefd1 = -1;
- snprint(cmd, sizeof cmd, "scp%F%V%F%V%F%V%F%V",
- dflag, "-d",
- pflag, "-p",
- rflag, "-r",
- vflag, "-v");
- p = fileaftercolon(argv[argc-1]);
- if(p != nil) /* send to remote machine. */
- destisremote(cmd, argc-1, argv, argv[argc-1], p);
- else{
- if(dflag)
- mustbedir(argv[argc-1]);
- destislocal(cmd, argc-1, argv, argv[argc-1]);
- }
- exits(exitflag);
- }
- void
- destislocal(int8_t *cmd, int argc, int8_t *argv[], int8_t *dst)
- {
- int i;
- int8_t *src;
- int8_t buf[4096];
- for(i = 0; i<argc; i++){
- src = fileaftercolon(argv[i]);
- if(src == nil){
- /* local file; no network */
- snprint(buf, sizeof buf, "exec cp%F%V%F%V %s %s",
- rflag, "-r",
- pflag, "-p",
- argv[i], dst);
- vprint("remotetolocal: %s", buf);
- if(runcommand(buf) < 0)
- exitflag = "local cp exec";
- }else{
- /* remote file; use network */
- snprint(buf, sizeof buf, "%s -f %s", cmd, src);
- if(remotessh(argv[i], buf) < 0)
- exitflag = "remote ssh exec";
- else{
- receive(dst);
- close(remotefd0);
- remotefd0 = -1;
- remotefd1 = -1;
- }
- }
- }
- }
- void
- destisremote(int8_t *cmd, int argc, int8_t *argv[], int8_t *host,
- int8_t *dest)
- {
- int i;
- int8_t *src;
- int8_t buf[4096];
- for(i = 0; i < argc; i++){
- vprint("remote destination: send %s to %s:%s", argv[i], host, dest);
- /* destination is remote, but source may be local */
- src = fileaftercolon(argv[i]);
- if(src != nil){
- /* remote to remote */
- snprint(buf, sizeof buf, "exec %s%F%V%F%V %s %s %s '%s:%s'",
- ssh,
- iflag, " -i",
- vflag, "-v",
- argv[i], cmd, src,
- host, dest);
- vprint("localtoremote: %s", buf);
- runcommand(buf);
- }else{
- /* local to remote */
- if(remotefd0 == -1){
- snprint(buf, sizeof buf, "%s -t %s", cmd, dest);
- if(remotessh(host, buf) < 0)
- exits("remotessh");
- if(getresponse() < 0)
- exits("bad response");
- }
- send(argv[i]);
- }
- }
- }
- void
- readhdr(int8_t *p, int n)
- {
- int i;
- for(i=0; i<n; i++){
- if(read(remotefd0, &p[i], 1) != 1)
- break;
- if(p[i] == '\n'){
- p[i] = '\0';
- return;
- }
- }
- /* if at beginning, this is regular EOF */
- if(i == 0)
- exits(nil);
- scperror(1, "read error on receive header: %r");
- }
- Dir *
- receivedir(int8_t *dir, int exists, Dir *d, int settimes, uint32_t atime,
- uint32_t mtime, uint32_t mode)
- {
- Dir nd;
- int setmodes;
- int fd;
- setmodes = pflag;
- if(exists){
- if(!(d->qid.type & QTDIR)) {
- scperror(0, "%s: protocol botch: directory requrest for non-directory", dir);
- return d;
- }
- }else{
- /* create it writeable; will fix later */
- setmodes = 1;
- fd = create(dir, OREAD, DMDIR|mode|0700);
- if (fd < 0){
- scperror(0, "%s: can't create: %r", dir);
- return d;
- }
- d = dirfstat(fd);
- close(fd);
- if(d == nil){
- scperror(0, "%s: can't stat: %r", dir);
- return d;
- }
- }
- receive(dir);
- if(settimes || setmodes){
- nulldir(&nd);
- if(settimes){
- nd.atime = atime;
- nd.mtime = mtime;
- d->atime = nd.atime;
- d->mtime = nd.mtime;
- }
- if(setmodes){
- nd.mode = DMDIR | (mode & 0777);
- d->mode = nd.mode;
- }
- if(dirwstat(dir, &nd) < 0){
- scperror(0, "can't wstat %s: %r", dir);
- free(d);
- return nil;
- }
- }
- return d;
- }
- void
- receive(int8_t *dest)
- {
- int isdir, settimes, mode;
- int exists, n, i, fd, m;
- int errors;
- uint32_t atime, mtime, size;
- int8_t buf[8192], *p;
- int8_t name[1024];
- Dir *d;
- Dir nd;
- mtime = 0L;
- atime = 0L;
- settimes = 0;
- isdir = 0;
- if ((d = dirstat(dest)) && (d->qid.type & QTDIR)) {
- isdir = 1;
- }
- if(dflag && !isdir)
- scperror(1, "%s: not a directory: %r", dest);
- sendokresponse();
- for (;;) {
- readhdr(buf, sizeof buf);
- switch(buf[0]){
- case ERROR:
- case FATAL:
- if(!remote)
- fprint(2, "%s\n", buf+1);
- exitflag = "bad receive";
- if(buf[0] == FATAL)
- exits(exitflag);
- continue;
- case 'E':
- sendokresponse();
- return;
- case 'T':
- settimes = 1;
- p = buf + 1;
- mtime = strtol(p, &p, 10);
- if(*p++ != ' '){
- Badtime:
- scperror(1, "bad time format: %s", buf+1);
- }
- strtol(p, &p, 10);
- if(*p++ != ' ')
- goto Badtime;
- atime = strtol(p, &p, 10);
- if(*p++ != ' ')
- goto Badtime;
- strtol(p, &p, 10);
- if(*p++ != 0)
- goto Badtime;
- sendokresponse();
- continue;
- case 'D':
- case 'C':
- p = buf + 1;
- mode = strtol(p, &p, 8);
- if (*p++ != ' '){
- Badmode:
- scperror(1, "bad mode/size format: %s", buf+1);
- }
- size = strtoll(p, &p, 10);
- if(*p++ != ' ')
- goto Badmode;
- if(isdir){
- if(dest[0] == '\0')
- snprint(name, sizeof name, "%s", p);
- else
- snprint(name, sizeof name, "%s/%s", dest, p);
- }else
- snprint(name, sizeof name, "%s", dest);
- if(strlen(name) > sizeof name-UTFmax)
- scperror(1, "file name too long: %s", dest);
- exists = 1;
- free(d);
- if((d = dirstat(name)) == nil)
- exists = 0;
- if(buf[0] == 'D'){
- vprint("receive directory %s", name);
- d = receivedir(name, exists, d, settimes, atime, mtime, mode);
- settimes = 0;
- continue;
- }
- vprint("receive file %s by %s", name, getuser());
- fd = create(name, OWRITE, mode);
- if(fd < 0){
- scperror(0, "can't create %s: %r", name);
- continue;
- }
- sendokresponse();
- /*
- * Committed to receive size bytes
- */
- errors = 0;
- for(i = 0; i < size; i += m){
- n = sizeof buf;
- if(n > size - i)
- n = size - i;
- m = readn(remotefd0, buf, n);
- if(m <= 0)
- scperror(1, "read error on connection: %r");
- if(errors == 0){
- n = write(fd, buf, m);
- if(n != m)
- errors = 1;
- }
- }
- /* if file exists, modes could be wrong */
- if(errors)
- scperror(0, "%s: write error: %r", name);
- else if(settimes || (exists && (d->mode&0777) != (mode&0777))){
- nulldir(&nd);
- if(settimes){
- settimes = 0;
- nd.atime = atime;
- nd.mtime = mtime;
- }
- if(exists && (d->mode&0777) != (mode&0777))
- nd.mode = (d->mode & ~0777) | (mode&0777);
- if(dirwstat(name, &nd) < 0)
- scperror(0, "can't wstat %s: %r", name);
- }
- free(d);
- d = nil;
- close(fd);
- getresponse();
- if(errors)
- exits("write error");
- sendokresponse();
- break;
- default:
- scperror(0, "unrecognized header type char %c", buf[0]);
- scperror(1, "input line: %s", buf);
- }
- }
- }
- /*
- * Lastelem is called when we have a Dir with the final element, but if the file
- * has been bound, we want the original name that was used rather than
- * the contents of the stat buffer, so do this lexically.
- */
- int8_t*
- lastelem(int8_t *file)
- {
- int8_t *elem;
- elem = strrchr(file, '/');
- if(elem == nil)
- return file;
- return elem+1;
- }
- void
- send(int8_t *file)
- {
- Dir *d;
- uint32_t i;
- int m, n, fd;
- int8_t buf[8192];
- if((fd = open(file, OREAD)) < 0){
- scperror(0, "can't open %s: %r", file);
- return;
- }
- if((d = dirfstat(fd)) == nil){
- scperror(0, "can't fstat %s: %r", file);
- goto Return;
- }
- if(d->qid.type & QTDIR){
- if(rflag)
- senddir(file, fd, d);
- else
- scperror(0, "%s: is a directory", file);
- goto Return;
- }
- if(pflag){
- fprint(remotefd1, "T%lud 0 %lud 0\n", d->mtime, d->atime);
- if(getresponse() < 0)
- goto Return;
- }
- fprint(remotefd1, "C%.4luo %lld %s\n", d->mode&0777, d->length, lastelem(file));
- if(getresponse() < 0)
- goto Return;
- /*
- * We are now committed to send d.length bytes, regardless
- */
- for(i=0; i<d->length; i+=m){
- n = sizeof buf;
- if(n > d->length - i)
- n = d->length - i;
- m = readn(fd, buf, n);
- if(m <= 0)
- break;
- write(remotefd1, buf, m);
- }
- if(i == d->length)
- sendokresponse();
- else{
- /* continue to send gibberish up to d.length */
- for(; i<d->length; i+=n){
- n = sizeof buf;
- if(n > d->length - i)
- n = d->length - i;
- write(remotefd1, buf, n);
- }
- scperror(0, "%s: %r", file);
- }
-
- getresponse();
- Return:
- free(d);
- close(fd);
- }
- int
- getresponse(void)
- {
- uint8_t first, byte, buf[256];
- int i;
- if (read(remotefd0, &first, 1) != 1)
- scperror(1, "lost connection");
- if(first == 0)
- return 0;
- i = 0;
- if(first > FATAL){
- fprint(2, "scp: unexpected response character 0x%.2ux\n", first);
- buf[i++] = first;
- }
- /* read error message up to newline */
- for(;;){
- if(read(remotefd0, &byte, 1) != 1)
- scperror(1, "response: dropped connection");
- if(byte == '\n')
- break;
- if(i < sizeof buf)
- buf[i++] = byte;
- }
- exitflag = "bad response";
- if(!remote)
- fprint(2, "%.*s\n", utfnlen((int8_t*)buf, i), (int8_t*)buf);
- if (first == ERROR)
- return -1;
- exits(exitflag);
- return 0; /* not reached */
- }
- void
- senddir(int8_t *name, int fd, Dir *dirp)
- {
- Dir *d, *dir;
- int n;
- int8_t file[256];
- if(pflag){
- fprint(remotefd1, "T%lud 0 %lud 0\n", dirp->mtime, dirp->atime);
- if(getresponse() < 0)
- return;
- }
- vprint("directory %s mode: D%.4lo %d %.1024s", name, dirp->mode&0777, 0, lastelem(name));
- fprint(remotefd1, "D%.4lo %d %.1024s\n", dirp->mode&0777, 0, dirp->name);
- if(getresponse() < 0)
- return;
- n = dirreadall(fd, &dir);
- for(d = dir; d < &dir[n]; d++){
- /* shouldn't happen with plan 9, but worth checking anyway */
- if(strcmp(d->name, ".")==0 || strcmp(d->name, "..")==0)
- continue;
- if(snprint(file, sizeof file, "%s/%s", name, d->name) > sizeof file-UTFmax){
- scperror(0, "%.20s.../%s: name too long; skipping file", file, d->name);
- continue;
- }
- send(file);
- }
- free(dir);
- fprint(remotefd1, "E\n");
- getresponse();
- }
- int
- remotessh(int8_t *host, int8_t *cmd)
- {
- int i, p[2];
- int8_t *arg[32];
- vprint("remotessh: %s: %s", host, cmd);
- if(pipe(p) < 0)
- scperror(1, "pipe: %r");
- switch(fork()){
- case -1:
- scperror(1, "fork: %r");
- case 0:
- /* child */
- close(p[0]);
- dup(p[1], 0);
- dup(p[1], 1);
- for (i = 3; i < 100; i++)
- close(i);
-
- i = 0;
- arg[i++] = ssh;
- arg[i++] = "-x";
- arg[i++] = "-a";
- arg[i++] = "-m";
- if(iflag)
- arg[i++] = "-i";
- if(vflag)
- arg[i++] = "-v";
- arg[i++] = host;
- arg[i++] = cmd;
- arg[i] = nil;
-
- exec(ssh, arg);
- exits("exec failed");
- default:
- /* parent */
- close(p[1]);
- remotefd0 = p[0];
- remotefd1 = p[0];
- }
- return 0;
- }
- void
- scperror(int exit, int8_t *fmt, ...)
- {
- int8_t buf[2048];
- va_list arg;
- va_start(arg, fmt);
- vseprint(buf, buf+sizeof(buf), fmt, arg);
- va_end(arg);
- fprint(remotefd1, "%cscp: %s\n", ERROR, buf);
- if (!remote)
- fprint(2, "scp: %s\n", buf);
- exitflag = buf;
- if(exit)
- exits(exitflag);
- }
- int8_t *
- fileaftercolon(int8_t *file)
- {
- int8_t *c, *s;
- c = utfrune(file, ':');
- if(c == nil)
- return nil;
- /* colon must be in middle of name to be a separator */
- if(c == file)
- return nil;
- /* does slash occur before colon? */
- s = utfrune(file, '/');
- if(s != nil && s < c)
- return nil;
- *c++ = '\0';
- if(*c == '\0')
- return ".";
- return c;
- }
- void
- mustbedir(int8_t *file)
- {
- Dir *d;
- if((d = dirstat(file)) == nil){
- scperror(1, "%s: %r", file);
- return;
- }
- if(!(d->qid.type & QTDIR))
- scperror(1, "%s: Not a directory", file);
- free(d);
- }
|