One of the features missing in sftp is the ability to transfer a symlink. This patch adds a new command to sftp which performs this transfer. Note that it uses messages that already exist in the protocol between client and server. This diff is based on OpenSSH 3.4p1. *** sftp-client.c@@\main\1 Tue Oct 1 17:26:20 2002 --- sftp-client.c Wed Oct 23 15:57:34 2002 *************** *** 666,672 **** status = get_status(conn->fd_in, id); if (status != SSH2_FX_OK) ! error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); return(status); --- 666,672 ---- status = get_status(conn->fd_in, id); if (status != SSH2_FX_OK) ! error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, newpath, fx2txt(status)); return(status); *************** *** 673,679 **** } char * ! do_readlink(struct sftp_conn *conn, char *path) { Buffer msg; u_int type, expected_id, count, id; --- 673,679 ---- } char * ! do_readlink(struct sftp_conn *conn, char *path, Attrib *attrib) { Buffer msg; u_int type, expected_id, count, id; *************** *** 712,717 **** --- 712,720 ---- debug3("SSH_FXP_READLINK %s -> %s", path, filename); + if (attrib != NULL) + *attrib = *a; + xfree(longname); buffer_free(&msg); *************** *** 719,724 **** --- 722,774 ---- return(filename); } + int + do_getlink(struct sftp_conn *conn, char *path) + { + char *dest; + u_int status = 0; + int ret; + struct stat statb; + char *filename; + Attrib *a; + Attrib attrib; + + a = do_lstat(conn, path, 0); + if (a == NULL || !S_ISLNK(a->perm)) { + if (a != NULL) + error("%s is not a symlink", path); + return(-1); + } + + dest = do_readlink(conn, path, &attrib); + if (dest == NULL) + return(-1); + filename = strrchr(path, '/'); + if (filename == NULL) + filename = path; + else + filename += 1; + if (lstat(filename, &statb) == 0) { + error("Name \"%s\" already exists", filename); + return(-1); + } + else { + ret = symlink(dest, filename); + status = (ret == -1) ? errno : 0; + if (status != 0) + error("Couldn't create symlink \"%s\": %s", filename, + strerror(status)); + if (getuid() == 0 || geteuid() == 0) { + ret = lchown(filename, attrib.uid, attrib.gid); + status = (ret == -1) ? errno : 0; + if (status != 0) + error("Couldn't set ownership on symlink \"%s\": %s", filename, + strerror(status)); + } + } + return(status); + } + static void send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len, char *handle, u_int handle_len) *** sftp.1@@\main\1 Tue Oct 1 17:26:04 2002 --- sftp.1 Wed Oct 2 07:59:00 2002 *************** *** 185,190 **** --- 185,199 ---- .Fl P flag is specified, then the file's full permission and access time are copied too. + .It Xo Ic getlink + .Ar remote-path + .Xc + Retrieve the + .Ar remote-path + and store it on the local machine. This is used to retrieve a symlink + rather than the target of the symlink. Since a local + path name is not specified, the symlink is given the same name it has on the + remote machine. .It Ic help Display help text. .It Ic lls Op Ar ls-options Op Ar path *** sftp-int.c@@\main\1 Tue Oct 1 17:27:02 2002 --- sftp-int.c Wed Oct 2 06:21:10 2002 *************** *** 74,79 **** --- 74,80 ---- #define I_SHELL 20 #define I_SYMLINK 21 #define I_VERSION 22 + #define I_GETLINK 23 struct CMD { const char *c; *************** *** 91,96 **** --- 92,98 ---- { "exit", I_QUIT }, { "get", I_GET }, { "mget", I_GET }, + { "getlink", I_GETLINK }, { "help", I_HELP }, { "lcd", I_LCHDIR }, { "lchdir", I_LCHDIR }, *************** *** 126,131 **** --- 128,134 ---- printf("chown own path Change owner of file 'path' to 'own'\n"); printf("help Display this help text\n"); printf("get remote-path [local-path] Download file\n"); + printf("getlink remote-path Download symlink\n"); printf("lls [ls-options [path]] Display local directory listing\n"); printf("ln oldpath newpath Symlink remote file\n"); printf("lmkdir path Create local directory\n"); *************** *** 582,587 **** --- 585,591 ---- case I_CHDIR: case I_LCHDIR: case I_LMKDIR: + case I_GETLINK: /* Get pathname (mandatory) */ if (get_pathname(&cp, path1)) return(-1); *************** *** 682,687 **** --- 686,695 ---- case I_SYMLINK: path2 = make_absolute(path2, *pwd); err = do_symlink(conn, path1, path2); + break; + case I_GETLINK: + path1 = make_absolute(path1, *pwd); + err = do_getlink(conn, path1); break; case I_RM: path1 = make_absolute(path1, *pwd); *** sftp-client.h@@\main\1 Tue Oct 1 17:26:26 2002 --- sftp-client.h Wed Oct 2 06:11:40 2002 *************** *** 90,97 **** /* Rename 'oldpath' to 'newpath' */ int do_symlink(struct sftp_conn *, char *, char *); /* Return target of symlink 'path' - caller must free result */ ! char *do_readlink(struct sftp_conn *, char *); /* XXX: add callbacks to do_download/do_upload so we can do progress meter */ --- 90,100 ---- /* Rename 'oldpath' to 'newpath' */ int do_symlink(struct sftp_conn *, char *, char *); + /* Download symlink 'path' */ + int do_getlink(struct sftp_conn *, char *); + /* Return target of symlink 'path' - caller must free result */ ! char *do_readlink(struct sftp_conn *, char *, Attrib *); /* XXX: add callbacks to do_download/do_upload so we can do progress meter */ *** sftp-server.c@@\main\1 Tue Oct 1 17:27:26 2002 --- sftp-server.c Tue Nov 5 10:07:54 2002 *************** *** 604,610 **** status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { ! ret = chown(name, a->uid, a->gid); if (ret == -1) status = errno_to_portable(errno); } --- 618,624 ---- status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { ! ret = lchown(name, a->uid, a->gid); if (ret == -1) status = errno_to_portable(errno); } *************** *** 907,915 **** send_status(id, errno_to_portable(errno)); else { Stat s; ! link[len] = '\0'; attrib_clear(&s.attrib); s.name = s.long_name = link; send_names(id, 1, &s); } --- 941,958 ---- send_status(id, errno_to_portable(errno)); else { Stat s; ! struct stat st; ! int status; link[len] = '\0'; attrib_clear(&s.attrib); + + status = lstat(path, &st); + if (status == 0) { + stat_to_attrib(&st, &s.attrib); + } + else { + send_status(id, errno_to_portable(errno)); + } s.name = s.long_name = link; send_names(id, 1, &s); }