[patch] hard link protocol extension for sftp

Miklos Szeredi miklos at szeredi.hu
Fri Feb 13 08:11:41 EST 2009


Here's a patch that adds support for the creation of hard links over
SFTP.

Hard links are not used very often nowdays, but they do still have
their uses and this is currently the most often requested improvement
for SSHFS.

To detect hard links the st_nlink, st_dev and st_ino attributes are
usually used.  I'll also post patches adding extensions for these and
other attributes.

Please consider adding these to the OpenSSH sftp-server/client
implementations.  Comments are welcome.

Thanks,
Miklos


Index: ssh/sftp-client.c
===================================================================
--- ssh.orig/sftp-client.c	2009-02-10 14:54:58.000000000 +0100
+++ ssh/sftp-client.c	2009-02-10 15:15:08.000000000 +0100
@@ -63,6 +63,7 @@ struct sftp_conn {
 #define SFTP_EXT_POSIX_RENAME	0x00000001
 #define SFTP_EXT_STATVFS	0x00000002
 #define SFTP_EXT_FSTATVFS	0x00000004
+#define SFTP_EXT_LINK		0x00000008
 	u_int exts;
 };
 
@@ -328,10 +329,14 @@ do_init(int fd_in, int fd_out, u_int tra
 		    strcmp(value, "2") == 0) {
 			exts |= SFTP_EXT_STATVFS;
 			known = 1;
-		} if (strcmp(name, "fstatvfs at openssh.com") == 0 &&
+		} else if (strcmp(name, "fstatvfs at openssh.com") == 0 &&
 		    strcmp(value, "2") == 0) {
 			exts |= SFTP_EXT_FSTATVFS;
 			known = 1;
+		} else if (strcmp(name, "link at openssh.com") == 0 &&
+		    strcmp(value, "1") == 0) {
+			exts |= SFTP_EXT_LINK;
+			known = 1;
 		}
 		if (known) {
 			debug2("Server supports extension \"%s\" revision %s",
@@ -731,6 +736,39 @@ do_rename(struct sftp_conn *conn, char *
 		    newpath, fx2txt(status));
 
 	return(status);
+}
+
+int
+do_link(struct sftp_conn *conn, char *oldpath, char *newpath)
+{
+	Buffer msg;
+	u_int status, id;
+
+	buffer_init(&msg);
+
+	/* Send link request */
+	id = conn->msg_id++;
+	if ((conn->exts & SFTP_EXT_LINK) == 0) {
+		error("Server does not support link at openssh.com extension");
+		return -1;
+	}
+
+	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
+	buffer_put_int(&msg, id);
+	buffer_put_cstring(&msg, "link at openssh.com");
+	buffer_put_cstring(&msg, oldpath);
+	buffer_put_cstring(&msg, newpath);
+	send_msg(conn->fd_out, &msg);
+	debug3("Sent message link at openssh.com \"%s\" -> \"%s\"",
+	       oldpath, newpath);
+	buffer_free(&msg);
+
+	status = get_status(conn->fd_in, id);
+	if (status != SSH2_FX_OK)
+		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
+		    newpath, fx2txt(status));
+
+	return(status);
 }
 
 int
Index: ssh/sftp-client.h
===================================================================
--- ssh.orig/sftp-client.h	2009-02-10 14:54:58.000000000 +0100
+++ ssh/sftp-client.h	2009-02-10 15:15:08.000000000 +0100
@@ -94,6 +94,9 @@ int do_statvfs(struct sftp_conn *, const
 /* Rename 'oldpath' to 'newpath' */
 int do_rename(struct sftp_conn *, char *, char *);
 
+/* Link 'oldpath' to 'newpath' */
+int do_link(struct sftp_conn *, char *, char *);
+
 /* Rename 'oldpath' to 'newpath' */
 int do_symlink(struct sftp_conn *, char *, char *);
 
Index: ssh/sftp-server.c
===================================================================
--- ssh.orig/sftp-server.c	2009-02-10 14:54:58.000000000 +0100
+++ ssh/sftp-server.c	2009-02-10 15:15:08.000000000 +0100
@@ -523,6 +523,9 @@ process_init(void)
 	/* fstatvfs extension */
 	buffer_put_cstring(&msg, "fstatvfs at openssh.com");
 	buffer_put_cstring(&msg, "2"); /* version */
+	/* link extension */
+	buffer_put_cstring(&msg, "link at openssh.com");
+	buffer_put_cstring(&msg, "1"); /* version */
 	send_msg(&msg);
 	buffer_free(&msg);
 }
@@ -1153,6 +1156,23 @@ process_extended_fstatvfs(u_int32_t id)
 }
 
 static void
+process_extended_link(u_int32_t id)
+{
+	char *oldpath, *newpath;
+
+	oldpath = get_string(NULL);
+	newpath = get_string(NULL);
+	debug3("request %u: link", id);
+	logit("link old \"%s\" new \"%s\"", oldpath, newpath);
+	if (link(oldpath, newpath) == -1)
+		send_status(id, errno_to_portable(errno));
+	else
+		send_status(id, SSH2_FX_OK);
+	xfree(oldpath);
+	xfree(newpath);
+}
+
+static void
 process_extended(void)
 {
 	u_int32_t id;
@@ -1166,6 +1186,8 @@ process_extended(void)
 		process_extended_statvfs(id);
 	else if (strcmp(request, "fstatvfs at openssh.com") == 0)
 		process_extended_fstatvfs(id);
+	else if (strcmp(request, "link at openssh.com") == 0)
+		process_extended_link(id);
 	else
 		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
 	xfree(request);
Index: ssh/sftp.c
===================================================================
--- ssh.orig/sftp.c	2009-02-10 14:54:58.000000000 +0100
+++ ssh/sftp.c	2009-02-10 15:15:19.000000000 +0100
@@ -98,6 +98,7 @@ int remote_glob(struct sftp_conn *, cons
 #define I_GET		5
 #define I_HELP		6
 #define I_LCHDIR	7
+#define I_LINK		25
 #define I_LLS		8
 #define I_LMKDIR	9
 #define I_LPWD		10
@@ -135,6 +136,7 @@ static const struct CMD cmds[] = {
 	{ "help",	I_HELP },
 	{ "lcd",	I_LCHDIR },
 	{ "lchdir",	I_LCHDIR },
+	{ "link",	I_LINK },
 	{ "lls",	I_LLS },
 	{ "lmkdir",	I_LMKDIR },
 	{ "ln",		I_SYMLINK },
@@ -198,6 +200,7 @@ help(void)
 	    "get [-P] remote-path [local-path]  Download file\n"
 	    "help                               Display this help text\n"
 	    "lcd path                           Change local directory to 'path'\n"
+	    "link oldpath newpath               Create hard link to remote file\n"
 	    "lls [ls-options [path]]            Display local directory listing\n"
 	    "lmkdir path                        Create local directory\n"
 	    "ln oldpath newpath                 Symlink remote file\n"
@@ -1111,6 +1114,7 @@ parse_args(const char **cpp, int *pflag,
 		}
 		break;
 	case I_RENAME:
+	case I_LINK:
 	case I_SYMLINK:
 		if (argc - optidx < 2) {
 			error("You must specify two paths after a %s "
@@ -1250,6 +1254,11 @@ parse_dispatch_command(struct sftp_conn
 		path2 = make_absolute(path2, *pwd);
 		err = do_rename(conn, path1, path2);
 		break;
+	case I_LINK:
+		path1 = make_absolute(path1, *pwd);
+		path2 = make_absolute(path2, *pwd);
+		err = do_link(conn, path1, path2);
+		break;
 	case I_SYMLINK:
 		path2 = make_absolute(path2, *pwd);
 		err = do_symlink(conn, path1, path2);
Index: ssh/PROTOCOL
===================================================================
--- ssh.orig/PROTOCOL	2009-02-10 14:54:58.000000000 +0100
+++ ssh/PROTOCOL	2009-02-10 15:22:05.000000000 +0100
@@ -240,4 +240,20 @@ The values of the f_flag bitmask are as
 Both the "statvfs at openssh.com" and "fstatvfs at openssh.com" extensions are
 advertised in the SSH_FXP_VERSION hello with version "2".
 
+10. sftp: Extension request "link at openssh.com"
+
+This request is for creating a hard link to a regular file. This
+request is implemented as a SSH_FXP_EXTENDED request with the
+following format:
+
+	uint32		id
+	string		"link at openssh.com"
+	string		oldpath
+	string		newpath
+
+On receiving this request the server will perform the operation
+link(oldpath, newpath) and will respond with a SSH_FXP_STATUS message.
+This extension is advertised in the SSH_FXP_VERSION hello with version
+"1".
+
 $OpenBSD: PROTOCOL,v 1.11 2008/07/05 05:16:01 djm Exp $


More information about the openssh-unix-dev mailing list