diff options
author | Darren Tucker <dtucker@zip.com.au> | 2010-12-05 09:02:47 +1100 |
---|---|---|
committer | Darren Tucker <dtucker@zip.com.au> | 2010-12-05 09:02:47 +1100 |
commit | af1f90925494deba97a4b877798cf250f7dc75cf (patch) | |
tree | c6e44e388c2bd4ca0caad6ce9f422576fe650b57 | |
parent | adab6f12992c522e1208fa2bdf89ce572840ccf8 (diff) |
- djm@cvs.openbsd.org 2010/12/04 00:18:01
[sftp-server.c sftp.1 sftp-client.h sftp.c PROTOCOL sftp-client.c]
add a protocol extension to support a hard link operation. It is
available through the "ln" command in the client. The old "ln"
behaviour of creating a symlink is available using its "-s" option
or through the preexisting "symlink" command; based on a patch from
miklos AT szeredi.hu in bz#1555; ok markus@
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | PROTOCOL | 18 | ||||
-rw-r--r-- | sftp-client.c | 42 | ||||
-rw-r--r-- | sftp-client.h | 5 | ||||
-rw-r--r-- | sftp-server.c | 28 | ||||
-rw-r--r-- | sftp.1 | 18 | ||||
-rw-r--r-- | sftp.c | 53 |
7 files changed, 151 insertions, 20 deletions
@@ -11,6 +11,13 @@ | |||
11 | [auth-rsa.c] | 11 | [auth-rsa.c] |
12 | move check for revoked keys to run earlier (in auth_rsa_key_allowed) | 12 | move check for revoked keys to run earlier (in auth_rsa_key_allowed) |
13 | bz#1829; patch from ldv AT altlinux.org; ok markus@ | 13 | bz#1829; patch from ldv AT altlinux.org; ok markus@ |
14 | - djm@cvs.openbsd.org 2010/12/04 00:18:01 | ||
15 | [sftp-server.c sftp.1 sftp-client.h sftp.c PROTOCOL sftp-client.c] | ||
16 | add a protocol extension to support a hard link operation. It is | ||
17 | available through the "ln" command in the client. The old "ln" | ||
18 | behaviour of creating a symlink is available using its "-s" option | ||
19 | or through the preexisting "symlink" command; based on a patch from | ||
20 | miklos AT szeredi.hu in bz#1555; ok markus@ | ||
14 | 21 | ||
15 | 20101204 | 22 | 20101204 |
16 | - (djm) [openbsd-compat/bindresvport.c] Use arc4random_uniform(range) | 23 | - (djm) [openbsd-compat/bindresvport.c] Use arc4random_uniform(range) |
@@ -275,4 +275,20 @@ The values of the f_flag bitmask are as follows: | |||
275 | Both the "statvfs@openssh.com" and "fstatvfs@openssh.com" extensions are | 275 | Both the "statvfs@openssh.com" and "fstatvfs@openssh.com" extensions are |
276 | advertised in the SSH_FXP_VERSION hello with version "2". | 276 | advertised in the SSH_FXP_VERSION hello with version "2". |
277 | 277 | ||
278 | $OpenBSD: PROTOCOL,v 1.16 2010/08/31 11:54:45 djm Exp $ | 278 | 10. sftp: Extension request "hardlink@openssh.com" |
279 | |||
280 | This request is for creating a hard link to a regular file. This | ||
281 | request is implemented as a SSH_FXP_EXTENDED request with the | ||
282 | following format: | ||
283 | |||
284 | uint32 id | ||
285 | string "hardlink@openssh.com" | ||
286 | string oldpath | ||
287 | string newpath | ||
288 | |||
289 | On receiving this request the server will perform the operation | ||
290 | link(oldpath, newpath) and will respond with a SSH_FXP_STATUS message. | ||
291 | This extension is advertised in the SSH_FXP_VERSION hello with version | ||
292 | "1". | ||
293 | |||
294 | $OpenBSD: PROTOCOL,v 1.17 2010/12/04 00:18:01 djm Exp $ | ||
diff --git a/sftp-client.c b/sftp-client.c index 4e009ef25..caa384b4e 100644 --- a/sftp-client.c +++ b/sftp-client.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-client.c,v 1.93 2010/09/22 22:58:51 djm Exp $ */ | 1 | /* $OpenBSD: sftp-client.c,v 1.94 2010/12/04 00:18:01 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -75,6 +75,7 @@ struct sftp_conn { | |||
75 | #define SFTP_EXT_POSIX_RENAME 0x00000001 | 75 | #define SFTP_EXT_POSIX_RENAME 0x00000001 |
76 | #define SFTP_EXT_STATVFS 0x00000002 | 76 | #define SFTP_EXT_STATVFS 0x00000002 |
77 | #define SFTP_EXT_FSTATVFS 0x00000004 | 77 | #define SFTP_EXT_FSTATVFS 0x00000004 |
78 | #define SFTP_EXT_HARDLINK 0x00000008 | ||
78 | u_int exts; | 79 | u_int exts; |
79 | u_int64_t limit_kbps; | 80 | u_int64_t limit_kbps; |
80 | struct bwlimit bwlimit_in, bwlimit_out; | 81 | struct bwlimit bwlimit_in, bwlimit_out; |
@@ -378,10 +379,14 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, | |||
378 | strcmp(value, "2") == 0) { | 379 | strcmp(value, "2") == 0) { |
379 | ret->exts |= SFTP_EXT_STATVFS; | 380 | ret->exts |= SFTP_EXT_STATVFS; |
380 | known = 1; | 381 | known = 1; |
381 | } if (strcmp(name, "fstatvfs@openssh.com") == 0 && | 382 | } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && |
382 | strcmp(value, "2") == 0) { | 383 | strcmp(value, "2") == 0) { |
383 | ret->exts |= SFTP_EXT_FSTATVFS; | 384 | ret->exts |= SFTP_EXT_FSTATVFS; |
384 | known = 1; | 385 | known = 1; |
386 | } else if (strcmp(name, "hardlink@openssh.com") == 0 && | ||
387 | strcmp(value, "1") == 0) { | ||
388 | ret->exts |= SFTP_EXT_HARDLINK; | ||
389 | known = 1; | ||
385 | } | 390 | } |
386 | if (known) { | 391 | if (known) { |
387 | debug2("Server supports extension \"%s\" revision %s", | 392 | debug2("Server supports extension \"%s\" revision %s", |
@@ -795,6 +800,39 @@ do_rename(struct sftp_conn *conn, char *oldpath, char *newpath) | |||
795 | } | 800 | } |
796 | 801 | ||
797 | int | 802 | int |
803 | do_hardlink(struct sftp_conn *conn, char *oldpath, char *newpath) | ||
804 | { | ||
805 | Buffer msg; | ||
806 | u_int status, id; | ||
807 | |||
808 | buffer_init(&msg); | ||
809 | |||
810 | /* Send link request */ | ||
811 | id = conn->msg_id++; | ||
812 | if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { | ||
813 | error("Server does not support hardlink@openssh.com extension"); | ||
814 | return -1; | ||
815 | } | ||
816 | |||
817 | buffer_put_char(&msg, SSH2_FXP_EXTENDED); | ||
818 | buffer_put_int(&msg, id); | ||
819 | buffer_put_cstring(&msg, "hardlink@openssh.com"); | ||
820 | buffer_put_cstring(&msg, oldpath); | ||
821 | buffer_put_cstring(&msg, newpath); | ||
822 | send_msg(conn, &msg); | ||
823 | debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", | ||
824 | oldpath, newpath); | ||
825 | buffer_free(&msg); | ||
826 | |||
827 | status = get_status(conn, id); | ||
828 | if (status != SSH2_FX_OK) | ||
829 | error("Couldn't link file \"%s\" to \"%s\": %s", oldpath, | ||
830 | newpath, fx2txt(status)); | ||
831 | |||
832 | return(status); | ||
833 | } | ||
834 | |||
835 | int | ||
798 | do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath) | 836 | do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath) |
799 | { | 837 | { |
800 | Buffer msg; | 838 | Buffer msg; |
diff --git a/sftp-client.h b/sftp-client.h index 145fc38ee..aef54ef49 100644 --- a/sftp-client.h +++ b/sftp-client.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-client.h,v 1.19 2010/09/22 22:58:51 djm Exp $ */ | 1 | /* $OpenBSD: sftp-client.h,v 1.20 2010/12/04 00:18:01 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 4 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
@@ -94,6 +94,9 @@ int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int); | |||
94 | /* Rename 'oldpath' to 'newpath' */ | 94 | /* Rename 'oldpath' to 'newpath' */ |
95 | int do_rename(struct sftp_conn *, char *, char *); | 95 | int do_rename(struct sftp_conn *, char *, char *); |
96 | 96 | ||
97 | /* Link 'oldpath' to 'newpath' */ | ||
98 | int do_hardlink(struct sftp_conn *, char *, char *); | ||
99 | |||
97 | /* Rename 'oldpath' to 'newpath' */ | 100 | /* Rename 'oldpath' to 'newpath' */ |
98 | int do_symlink(struct sftp_conn *, char *, char *); | 101 | int do_symlink(struct sftp_conn *, char *, char *); |
99 | 102 | ||
diff --git a/sftp-server.c b/sftp-server.c index 47edcd0aa..b268d0883 100644 --- a/sftp-server.c +++ b/sftp-server.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-server.c,v 1.92 2010/11/04 02:45:34 djm Exp $ */ | 1 | /* $OpenBSD: sftp-server.c,v 1.93 2010/12/04 00:18:01 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -535,6 +535,9 @@ process_init(void) | |||
535 | /* fstatvfs extension */ | 535 | /* fstatvfs extension */ |
536 | buffer_put_cstring(&msg, "fstatvfs@openssh.com"); | 536 | buffer_put_cstring(&msg, "fstatvfs@openssh.com"); |
537 | buffer_put_cstring(&msg, "2"); /* version */ | 537 | buffer_put_cstring(&msg, "2"); /* version */ |
538 | /* hardlink extension */ | ||
539 | buffer_put_cstring(&msg, "hardlink@openssh.com"); | ||
540 | buffer_put_cstring(&msg, "1"); /* version */ | ||
538 | send_msg(&msg); | 541 | send_msg(&msg); |
539 | buffer_free(&msg); | 542 | buffer_free(&msg); |
540 | } | 543 | } |
@@ -1223,6 +1226,27 @@ process_extended_fstatvfs(u_int32_t id) | |||
1223 | } | 1226 | } |
1224 | 1227 | ||
1225 | static void | 1228 | static void |
1229 | process_extended_hardlink(u_int32_t id) | ||
1230 | { | ||
1231 | char *oldpath, *newpath; | ||
1232 | int ret, status; | ||
1233 | |||
1234 | oldpath = get_string(NULL); | ||
1235 | newpath = get_string(NULL); | ||
1236 | debug3("request %u: hardlink", id); | ||
1237 | logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath); | ||
1238 | if (readonly) | ||
1239 | status = SSH2_FX_PERMISSION_DENIED; | ||
1240 | else { | ||
1241 | ret = link(oldpath, newpath); | ||
1242 | status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; | ||
1243 | } | ||
1244 | send_status(id, status); | ||
1245 | xfree(oldpath); | ||
1246 | xfree(newpath); | ||
1247 | } | ||
1248 | |||
1249 | static void | ||
1226 | process_extended(void) | 1250 | process_extended(void) |
1227 | { | 1251 | { |
1228 | u_int32_t id; | 1252 | u_int32_t id; |
@@ -1236,6 +1260,8 @@ process_extended(void) | |||
1236 | process_extended_statvfs(id); | 1260 | process_extended_statvfs(id); |
1237 | else if (strcmp(request, "fstatvfs@openssh.com") == 0) | 1261 | else if (strcmp(request, "fstatvfs@openssh.com") == 0) |
1238 | process_extended_fstatvfs(id); | 1262 | process_extended_fstatvfs(id); |
1263 | else if (strcmp(request, "hardlink@openssh.com") == 0) | ||
1264 | process_extended_hardlink(id); | ||
1239 | else | 1265 | else |
1240 | send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ | 1266 | send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ |
1241 | xfree(request); | 1267 | xfree(request); |
@@ -1,4 +1,4 @@ | |||
1 | .\" $OpenBSD: sftp.1,v 1.87 2010/11/18 15:01:00 jmc Exp $ | 1 | .\" $OpenBSD: sftp.1,v 1.88 2010/12/04 00:18:01 djm Exp $ |
2 | .\" | 2 | .\" |
3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. | 3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. |
4 | .\" | 4 | .\" |
@@ -22,7 +22,7 @@ | |||
22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | .\" | 24 | .\" |
25 | .Dd $Mdocdate: November 18 2010 $ | 25 | .Dd $Mdocdate: December 4 2010 $ |
26 | .Dt SFTP 1 | 26 | .Dt SFTP 1 |
27 | .Os | 27 | .Os |
28 | .Sh NAME | 28 | .Sh NAME |
@@ -128,7 +128,7 @@ commands fail: | |||
128 | .Ic get , put , rename , ln , | 128 | .Ic get , put , rename , ln , |
129 | .Ic rm , mkdir , chdir , ls , | 129 | .Ic rm , mkdir , chdir , ls , |
130 | .Ic lchdir , chmod , chown , | 130 | .Ic lchdir , chmod , chown , |
131 | .Ic chgrp , lpwd , df , | 131 | .Ic chgrp , lpwd , df , symlink , |
132 | and | 132 | and |
133 | .Ic lmkdir . | 133 | .Ic lmkdir . |
134 | Termination on error can be suppressed on a command by command basis by | 134 | Termination on error can be suppressed on a command by command basis by |
@@ -392,11 +392,19 @@ characters and may match multiple files. | |||
392 | .It Ic lmkdir Ar path | 392 | .It Ic lmkdir Ar path |
393 | Create local directory specified by | 393 | Create local directory specified by |
394 | .Ar path . | 394 | .Ar path . |
395 | .It Ic ln Ar oldpath Ar newpath | 395 | .It Xo Ic ln |
396 | Create a symbolic link from | 396 | .Op Fl s |
397 | .Ar oldpath | ||
398 | .Ar newpath | ||
399 | .Xc | ||
400 | Create a link from | ||
397 | .Ar oldpath | 401 | .Ar oldpath |
398 | to | 402 | to |
399 | .Ar newpath . | 403 | .Ar newpath . |
404 | If the | ||
405 | .Fl s | ||
406 | flag is specified the created link is a symbolic link, otherwise it is | ||
407 | a hard link. | ||
400 | .It Ic lpwd | 408 | .It Ic lpwd |
401 | Print local working directory. | 409 | Print local working directory. |
402 | .It Xo Ic ls | 410 | .It Xo Ic ls |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp.c,v 1.131 2010/10/23 22:06:12 sthen Exp $ */ | 1 | /* $OpenBSD: sftp.c,v 1.132 2010/12/04 00:18:01 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -132,6 +132,7 @@ extern char *__progname; | |||
132 | #define I_GET 5 | 132 | #define I_GET 5 |
133 | #define I_HELP 6 | 133 | #define I_HELP 6 |
134 | #define I_LCHDIR 7 | 134 | #define I_LCHDIR 7 |
135 | #define I_LINK 25 | ||
135 | #define I_LLS 8 | 136 | #define I_LLS 8 |
136 | #define I_LMKDIR 9 | 137 | #define I_LMKDIR 9 |
137 | #define I_LPWD 10 | 138 | #define I_LPWD 10 |
@@ -176,7 +177,7 @@ static const struct CMD cmds[] = { | |||
176 | { "lchdir", I_LCHDIR, LOCAL }, | 177 | { "lchdir", I_LCHDIR, LOCAL }, |
177 | { "lls", I_LLS, LOCAL }, | 178 | { "lls", I_LLS, LOCAL }, |
178 | { "lmkdir", I_LMKDIR, LOCAL }, | 179 | { "lmkdir", I_LMKDIR, LOCAL }, |
179 | { "ln", I_SYMLINK, REMOTE }, | 180 | { "ln", I_LINK, REMOTE }, |
180 | { "lpwd", I_LPWD, LOCAL }, | 181 | { "lpwd", I_LPWD, LOCAL }, |
181 | { "ls", I_LS, REMOTE }, | 182 | { "ls", I_LS, REMOTE }, |
182 | { "lumask", I_LUMASK, NOARGS }, | 183 | { "lumask", I_LUMASK, NOARGS }, |
@@ -240,7 +241,7 @@ help(void) | |||
240 | "lcd path Change local directory to 'path'\n" | 241 | "lcd path Change local directory to 'path'\n" |
241 | "lls [ls-options [path]] Display local directory listing\n" | 242 | "lls [ls-options [path]] Display local directory listing\n" |
242 | "lmkdir path Create local directory\n" | 243 | "lmkdir path Create local directory\n" |
243 | "ln oldpath newpath Symlink remote file\n" | 244 | "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" |
244 | "lpwd Print local working directory\n" | 245 | "lpwd Print local working directory\n" |
245 | "ls [-1afhlnrSt] [path] Display remote directory listing\n" | 246 | "ls [-1afhlnrSt] [path] Display remote directory listing\n" |
246 | "lumask umask Set local umask to 'umask'\n" | 247 | "lumask umask Set local umask to 'umask'\n" |
@@ -377,6 +378,30 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, | |||
377 | } | 378 | } |
378 | 379 | ||
379 | static int | 380 | static int |
381 | parse_link_flags(const char *cmd, char **argv, int argc, int *sflag) | ||
382 | { | ||
383 | extern int opterr, optind, optopt, optreset; | ||
384 | int ch; | ||
385 | |||
386 | optind = optreset = 1; | ||
387 | opterr = 0; | ||
388 | |||
389 | *sflag = 0; | ||
390 | while ((ch = getopt(argc, argv, "s")) != -1) { | ||
391 | switch (ch) { | ||
392 | case 's': | ||
393 | *sflag = 1; | ||
394 | break; | ||
395 | default: | ||
396 | error("%s: Invalid flag -%c", cmd, optopt); | ||
397 | return -1; | ||
398 | } | ||
399 | } | ||
400 | |||
401 | return optind; | ||
402 | } | ||
403 | |||
404 | static int | ||
380 | parse_ls_flags(char **argv, int argc, int *lflag) | 405 | parse_ls_flags(char **argv, int argc, int *lflag) |
381 | { | 406 | { |
382 | extern int opterr, optind, optopt, optreset; | 407 | extern int opterr, optind, optopt, optreset; |
@@ -1088,7 +1113,7 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, | |||
1088 | 1113 | ||
1089 | static int | 1114 | static int |
1090 | parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, | 1115 | parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, |
1091 | int *hflag, unsigned long *n_arg, char **path1, char **path2) | 1116 | int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) |
1092 | { | 1117 | { |
1093 | const char *cmd, *cp = *cpp; | 1118 | const char *cmd, *cp = *cpp; |
1094 | char *cp2, **argv; | 1119 | char *cp2, **argv; |
@@ -1138,7 +1163,8 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, | |||
1138 | switch (cmdnum) { | 1163 | switch (cmdnum) { |
1139 | case I_GET: | 1164 | case I_GET: |
1140 | case I_PUT: | 1165 | case I_PUT: |
1141 | if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1) | 1166 | if ((optidx = parse_getput_flags(cmd, argv, argc, |
1167 | pflag, rflag)) == -1) | ||
1142 | return -1; | 1168 | return -1; |
1143 | /* Get first pathname (mandatory) */ | 1169 | /* Get first pathname (mandatory) */ |
1144 | if (argc - optidx < 1) { | 1170 | if (argc - optidx < 1) { |
@@ -1154,8 +1180,11 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, | |||
1154 | undo_glob_escape(*path2); | 1180 | undo_glob_escape(*path2); |
1155 | } | 1181 | } |
1156 | break; | 1182 | break; |
1157 | case I_RENAME: | 1183 | case I_LINK: |
1184 | if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) | ||
1185 | return -1; | ||
1158 | case I_SYMLINK: | 1186 | case I_SYMLINK: |
1187 | case I_RENAME: | ||
1159 | if (argc - optidx < 2) { | 1188 | if (argc - optidx < 2) { |
1160 | error("You must specify two paths after a %s " | 1189 | error("You must specify two paths after a %s " |
1161 | "command.", cmd); | 1190 | "command.", cmd); |
@@ -1258,7 +1287,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1258 | int err_abort) | 1287 | int err_abort) |
1259 | { | 1288 | { |
1260 | char *path1, *path2, *tmp; | 1289 | char *path1, *path2, *tmp; |
1261 | int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i; | 1290 | int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; |
1291 | int cmdnum, i; | ||
1262 | unsigned long n_arg = 0; | 1292 | unsigned long n_arg = 0; |
1263 | Attrib a, *aa; | 1293 | Attrib a, *aa; |
1264 | char path_buf[MAXPATHLEN]; | 1294 | char path_buf[MAXPATHLEN]; |
@@ -1266,8 +1296,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1266 | glob_t g; | 1296 | glob_t g; |
1267 | 1297 | ||
1268 | path1 = path2 = NULL; | 1298 | path1 = path2 = NULL; |
1269 | cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg, | 1299 | cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, |
1270 | &path1, &path2); | 1300 | &sflag, &n_arg, &path1, &path2); |
1271 | 1301 | ||
1272 | if (iflag != 0) | 1302 | if (iflag != 0) |
1273 | err_abort = 0; | 1303 | err_abort = 0; |
@@ -1295,8 +1325,11 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1295 | err = do_rename(conn, path1, path2); | 1325 | err = do_rename(conn, path1, path2); |
1296 | break; | 1326 | break; |
1297 | case I_SYMLINK: | 1327 | case I_SYMLINK: |
1328 | sflag = 1; | ||
1329 | case I_LINK: | ||
1330 | path1 = make_absolute(path1, *pwd); | ||
1298 | path2 = make_absolute(path2, *pwd); | 1331 | path2 = make_absolute(path2, *pwd); |
1299 | err = do_symlink(conn, path1, path2); | 1332 | err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); |
1300 | break; | 1333 | break; |
1301 | case I_RM: | 1334 | case I_RM: |
1302 | path1 = make_absolute(path1, *pwd); | 1335 | path1 = make_absolute(path1, *pwd); |