From d671e5a978efd5ba949d3fdcd03f728dcd68c636 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 19 May 2008 14:53:33 +1000 Subject: [PATCH] - djm@cvs.openbsd.org 2008/04/18 12:32:11 [sftp-client.c sftp-client.h sftp-server.c sftp.1 sftp.c sftp.h] introduce sftp extension methods statvfs@openssh.com and fstatvfs@openssh.com that implement statvfs(2)-like operations, based on a patch from miklos AT szeredi.hu (bz#1399) also add a "df" command to the sftp client that uses the statvfs@openssh.com to produce a df(1)-like display of filesystem space and inode utilisation ok markus@ --- ChangeLog | 11 ++++- sftp-client.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++-- sftp-client.h | 6 ++- sftp-server.c | 75 ++++++++++++++++++++++++++++++- sftp.1 | 24 ++++++++-- sftp.c | 112 +++++++++++++++++++++++++++++++++++++++++++--- sftp.h | 6 ++- 7 files changed, 339 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index b32f93f0cff..eba49fee6b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,15 @@ Use arc4random_uniform() when the desired random number upper bound is not a power of two ok deraadt@ millert@ + - djm@cvs.openbsd.org 2008/04/18 12:32:11 + [sftp-client.c sftp-client.h sftp-server.c sftp.1 sftp.c sftp.h] + introduce sftp extension methods statvfs@openssh.com and + fstatvfs@openssh.com that implement statvfs(2)-like operations, + based on a patch from miklos AT szeredi.hu (bz#1399) + also add a "df" command to the sftp client that uses the + statvfs@openssh.com to produce a df(1)-like display of filesystem + space and inode utilisation + ok markus@ 20080403 - (djm) [openbsd-compat/bsd-poll.c] Include stdlib.h to avoid compile- @@ -3881,4 +3890,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.4910 2008/05/19 04:50:00 djm Exp $ +$Id: ChangeLog,v 1.4911 2008/05/19 04:53:33 djm Exp $ diff --git a/sftp-client.c b/sftp-client.c index 69c63778590..1e54348b7e2 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.c,v 1.81 2008/03/23 12:54:01 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.82 2008/04/18 12:32:11 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -24,6 +24,7 @@ #include #include +#include #include "openbsd-compat/sys-queue.h" #ifdef HAVE_SYS_STAT_H # include @@ -65,7 +66,9 @@ struct sftp_conn { u_int num_requests; u_int version; u_int msg_id; -#define SFTP_EXT_POSIX_RENAME 1 +#define SFTP_EXT_POSIX_RENAME 0x00000001 +#define SFTP_EXT_STATVFS 0x00000002 +#define SFTP_EXT_FSTATVFS 0x00000004 u_int exts; }; @@ -238,6 +241,56 @@ get_decode_stat(int fd, u_int expected_id, int quiet) return(a); } +static int +get_decode_statvfs(int fd, struct statvfs *st, u_int expected_id, int quiet) +{ + Buffer msg; + u_int type, id, flag; + + buffer_init(&msg); + get_msg(fd, &msg); + + type = buffer_get_char(&msg); + id = buffer_get_int(&msg); + + debug3("Received statvfs reply T:%u I:%u", type, id); + if (id != expected_id) + fatal("ID mismatch (%u != %u)", id, expected_id); + if (type == SSH2_FXP_STATUS) { + int status = buffer_get_int(&msg); + + if (quiet) + debug("Couldn't statvfs: %s", fx2txt(status)); + else + error("Couldn't statvfs: %s", fx2txt(status)); + buffer_free(&msg); + return -1; + } else if (type != SSH2_FXP_EXTENDED_REPLY) { + fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", + SSH2_FXP_EXTENDED_REPLY, type); + } + + bzero(st, sizeof(*st)); + st->f_bsize = buffer_get_int(&msg); + st->f_frsize = buffer_get_int(&msg); + st->f_blocks = buffer_get_int64(&msg); + st->f_bfree = buffer_get_int64(&msg); + st->f_bavail = buffer_get_int64(&msg); + st->f_files = buffer_get_int64(&msg); + st->f_ffree = buffer_get_int64(&msg); + st->f_favail = buffer_get_int64(&msg); + st->f_fsid = buffer_get_int(&msg); + flag = buffer_get_int(&msg); + st->f_namemax = buffer_get_int(&msg); + + st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; + st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; + + buffer_free(&msg); + + return 0; +} + struct sftp_conn * do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests) { @@ -272,8 +325,15 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests) char *value = buffer_get_string(&msg, NULL); debug2("Init extension: \"%s\"", name); - if (strcmp(name, "posix-rename@openssh.com") == 0) + if (strcmp(name, "posix-rename@openssh.com") == 0 && + strcmp(value, "1") == 0) exts |= SFTP_EXT_POSIX_RENAME; + if (strcmp(name, "statvfs@openssh.com") == 0 && + strcmp(value, "1") == 0) + exts |= SFTP_EXT_STATVFS; + if (strcmp(name, "fstatvfs@openssh.com") == 0 && + strcmp(value, "1") == 0) + exts |= SFTP_EXT_FSTATVFS; xfree(name); xfree(value); } @@ -749,6 +809,60 @@ do_readlink(struct sftp_conn *conn, char *path) } #endif +int +do_statvfs(struct sftp_conn *conn, const char *path, struct statvfs *st, + int quiet) +{ + Buffer msg; + u_int id; + + if ((conn->exts & SFTP_EXT_STATVFS) == 0) { + error("Server does not support statvfs@openssh.com extension"); + return -1; + } + + id = conn->msg_id++; + + buffer_init(&msg); + buffer_clear(&msg); + buffer_put_char(&msg, SSH2_FXP_EXTENDED); + buffer_put_int(&msg, id); + buffer_put_cstring(&msg, "statvfs@openssh.com"); + buffer_put_cstring(&msg, path); + send_msg(conn->fd_out, &msg); + buffer_free(&msg); + + return get_decode_statvfs(conn->fd_in, st, id, quiet); +} + +#ifdef notyet +int +do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len, + struct statvfs *st, int quiet) +{ + Buffer msg; + u_int id; + + if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { + error("Server does not support fstatvfs@openssh.com extension"); + return -1; + } + + id = conn->msg_id++; + + buffer_init(&msg); + buffer_clear(&msg); + buffer_put_char(&msg, SSH2_FXP_EXTENDED); + buffer_put_int(&msg, id); + buffer_put_cstring(&msg, "fstatvfs@openssh.com"); + buffer_put_string(&msg, handle, handle_len); + send_msg(conn->fd_out, &msg); + buffer_free(&msg); + + return get_decode_statvfs(conn->fd_in, st, id, quiet); +} +#endif + static void send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len, char *handle, u_int handle_len) diff --git a/sftp-client.h b/sftp-client.h index fd0630e9a78..b102e118047 100644 --- a/sftp-client.h +++ b/sftp-client.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.h,v 1.15 2008/01/11 07:22:28 chl Exp $ */ +/* $OpenBSD: sftp-client.h,v 1.16 2008/04/18 12:32:11 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller @@ -70,6 +70,10 @@ int do_fsetstat(struct sftp_conn *, char *, u_int, Attrib *); /* Canonicalise 'path' - caller must free result */ char *do_realpath(struct sftp_conn *, char *); +/* Get statistics for filesystem hosting file at "path" */ +struct statvfs; +int do_statvfs(struct sftp_conn *, const char *, struct statvfs *, int); + /* Rename 'oldpath' to 'newpath' */ int do_rename(struct sftp_conn *, char *, char *); diff --git a/sftp-server.c b/sftp-server.c index d9549f5bc66..300fd5cfdb1 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-server.c,v 1.78 2008/02/27 20:21:15 djm Exp $ */ +/* $OpenBSD: sftp-server.c,v 1.79 2008/04/18 12:32:11 djm Exp $ */ /* * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. * @@ -23,6 +23,8 @@ #ifdef HAVE_SYS_TIME_H # include #endif +#include +#include #include #include @@ -475,6 +477,33 @@ send_attrib(u_int32_t id, const Attrib *a) buffer_free(&msg); } +static void +send_statvfs(u_int32_t id, struct statvfs *st) +{ + Buffer msg; + u_int64_t flag; + + flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; + + buffer_init(&msg); + buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY); + buffer_put_int(&msg, id); + buffer_put_int(&msg, st->f_bsize); + buffer_put_int(&msg, st->f_frsize); + buffer_put_int64(&msg, st->f_blocks); + buffer_put_int64(&msg, st->f_bfree); + buffer_put_int64(&msg, st->f_bavail); + buffer_put_int64(&msg, st->f_files); + buffer_put_int64(&msg, st->f_ffree); + buffer_put_int64(&msg, st->f_favail); + buffer_put_int(&msg, st->f_fsid); + buffer_put_int(&msg, flag); + buffer_put_int(&msg, st->f_namemax); + send_msg(&msg); + buffer_free(&msg); +} + /* parse incoming */ static void @@ -490,6 +519,10 @@ process_init(void) /* POSIX rename extension */ buffer_put_cstring(&msg, "posix-rename@openssh.com"); buffer_put_cstring(&msg, "1"); /* version */ + buffer_put_cstring(&msg, "statvfs@openssh.com"); + buffer_put_cstring(&msg, "1"); /* version */ + buffer_put_cstring(&msg, "fstatvfs@openssh.com"); + buffer_put_cstring(&msg, "1"); /* version */ send_msg(&msg); buffer_free(&msg); } @@ -1099,6 +1132,42 @@ process_extended_posix_rename(u_int32_t id) xfree(newpath); } +static void +process_extended_statvfs(u_int32_t id) +{ + char *path; + struct statvfs st; + + path = get_string(NULL); + debug3("request %u: statfs", id); + logit("statfs \"%s\"", path); + + if (statvfs(path, &st) != 0) + send_status(id, errno_to_portable(errno)); + else + send_statvfs(id, &st); + xfree(path); +} + +static void +process_extended_fstatvfs(u_int32_t id) +{ + int handle, fd; + struct statvfs st; + + handle = get_handle(); + debug("request %u: fstatvfs \"%s\" (handle %u)", + id, handle_to_name(handle), handle); + if ((fd = handle_to_fd(handle)) < 0) { + send_status(id, SSH2_FX_FAILURE); + return; + } + if (fstatvfs(fd, &st) != 0) + send_status(id, errno_to_portable(errno)); + else + send_statvfs(id, &st); +} + static void process_extended(void) { @@ -1109,6 +1178,10 @@ process_extended(void) request = get_string(NULL); if (strcmp(request, "posix-rename@openssh.com") == 0) process_extended_posix_rename(id); + else if (strcmp(request, "statvfs@openssh.com") == 0) + process_extended_statvfs(id); + else if (strcmp(request, "fstatvfs@openssh.com") == 0) + process_extended_fstatvfs(id); else send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ xfree(request); diff --git a/sftp.1 b/sftp.1 index 1f517a40abd..f34f58856ce 100644 --- a/sftp.1 +++ b/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.64 2007/05/31 19:20:16 jmc Exp $ +.\" $OpenBSD: sftp.1,v 1.65 2008/04/18 12:32:11 djm Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: May 31 2007 $ +.Dd $Mdocdate: April 18 2008 $ .Dt SFTP 1 .Os .Sh NAME @@ -112,7 +112,7 @@ will abort if any of the following commands fail: .Ic get , put , rename , ln , .Ic rm , mkdir , chdir , ls , -.Ic lchdir , chmod , chown , chgrp , lpwd +.Ic lchdir , chmod , chown , chgrp , lpwd, df, and .Ic lmkdir . Termination on error can be suppressed on a command by command basis by @@ -272,6 +272,24 @@ may contain characters and may match multiple files. .Ar own must be a numeric UID. +.It Xo Ic df +.Op Fl hi +.Op Ar path +.Xc +Display usage information for the filesystem holding the current directory +(or +.Ar path +if specified). +If the +.Fl h +flag is specified, the capacity information will be displayed using +"human-readable" suffixes. +The +.Fl i +flag requests display of inode information in addition to capacity information. +This command is only supported on servers that implement the +.Dq statvfs@openssh.com +extension. .It Ic exit Quit .Nm sftp . diff --git a/sftp.c b/sftp.c index 861c3db0503..0745baf0813 100644 --- a/sftp.c +++ b/sftp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.99 2008/01/20 00:38:30 djm Exp $ */ +/* $OpenBSD: sftp.c,v 1.100 2008/04/18 12:32:11 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ typedef void EditLine; #include #include #include +#include #include #include "xmalloc.h" @@ -104,6 +106,7 @@ extern char *__progname; #define I_CHGRP 2 #define I_CHMOD 3 #define I_CHOWN 4 +#define I_DF 24 #define I_GET 5 #define I_HELP 6 #define I_LCHDIR 7 @@ -136,6 +139,7 @@ static const struct CMD cmds[] = { { "chgrp", I_CHGRP }, { "chmod", I_CHMOD }, { "chown", I_CHOWN }, + { "df", I_DF }, { "dir", I_LS }, { "exit", I_QUIT }, { "get", I_GET }, @@ -200,6 +204,8 @@ help(void) printf("chgrp grp path Change group of file 'path' to 'grp'\n"); printf("chmod mode path Change permissions of file 'path' to 'mode'\n"); printf("chown own path Change owner of file 'path' to 'own'\n"); + printf("df [path] Display statistics for current directory or\n"); + printf(" filesystem containing 'path'\n"); printf("help Display this help text\n"); printf("get remote-path [local-path] Download file\n"); printf("lls [ls-options [path]] Display local directory listing\n"); @@ -421,6 +427,33 @@ parse_ls_flags(char **argv, int argc, int *lflag) return optind; } +static int +parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) +{ + extern int optind, optreset, opterr; + int ch; + + optind = optreset = 1; + opterr = 0; + + *hflag = *iflag = 0; + while ((ch = getopt(argc, argv, "hi")) != -1) { + switch (ch) { + case 'h': + *hflag = 1; + break; + case 'i': + *iflag = 1; + break; + default: + error("%s: Invalid flag -%c", cmd, ch); + return -1; + } + } + + return optind; +} + static int is_dir(char *path) { @@ -797,6 +830,56 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, return (0); } +static int +do_df(struct sftp_conn *conn, char *path, int hflag, int iflag) +{ + struct statvfs st; + char s_used[FMT_SCALED_STRSIZE]; + char s_avail[FMT_SCALED_STRSIZE]; + char s_root[FMT_SCALED_STRSIZE]; + char s_total[FMT_SCALED_STRSIZE]; + + if (do_statvfs(conn, path, &st, 1) == -1) + return -1; + if (iflag) { + printf(" Inodes Used Avail " + "(root) %%Capacity\n"); + printf("%11llu %11llu %11llu %11llu %3llu%%\n", + (unsigned long long)st.f_files, + (unsigned long long)(st.f_files - st.f_ffree), + (unsigned long long)st.f_favail, + (unsigned long long)st.f_ffree, + (unsigned long long)(100 * (st.f_files - st.f_ffree) / + st.f_files)); + } else if (hflag) { + strlcpy(s_used, "error", sizeof(s_used)); + strlcpy(s_avail, "error", sizeof(s_avail)); + strlcpy(s_root, "error", sizeof(s_root)); + strlcpy(s_total, "error", sizeof(s_total)); + fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); + fmt_scaled(st.f_bavail * st.f_frsize, s_avail); + fmt_scaled(st.f_bfree * st.f_frsize, s_root); + fmt_scaled(st.f_blocks * st.f_frsize, s_total); + printf(" Size Used Avail (root) %%Capacity\n"); + printf("%7sB %7sB %7sB %7sB %3llu%%\n", + s_total, s_used, s_avail, s_root, + (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / + st.f_blocks)); + } else { + printf(" Size Used Avail " + "(root) %%Capacity\n"); + printf("%12llu %12llu %12llu %12llu %3llu%%\n", + (unsigned long long)(st.f_frsize * st.f_blocks / 1024), + (unsigned long long)(st.f_frsize * + (st.f_blocks - st.f_bfree) / 1024), + (unsigned long long)(st.f_frsize * st.f_bavail / 1024), + (unsigned long long)(st.f_frsize * st.f_bfree / 1024), + (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / + st.f_blocks)); + } + return 0; +} + /* * Undo escaping of glob sequences in place. Used to undo extra escaping * applied in makeargv() when the string is destined for a function that @@ -972,7 +1055,7 @@ makeargv(const char *arg, int *argcp) } static int -parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, +parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag, unsigned long *n_arg, char **path1, char **path2) { const char *cmd, *cp = *cpp; @@ -1016,7 +1099,7 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, } /* Get arguments and parse flags */ - *lflag = *pflag = *n_arg = 0; + *lflag = *pflag = *hflag = *n_arg = 0; *path1 = *path2 = NULL; optidx = 1; switch (cmdnum) { @@ -1068,6 +1151,18 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, if (cmdnum != I_RM) undo_glob_escape(*path1); break; + case I_DF: + if ((optidx = parse_df_flags(cmd, argv, argc, hflag, + iflag)) == -1) + return -1; + /* Default to current directory if no path specified */ + if (argc - optidx < 1) + *path1 = NULL; + else { + *path1 = xstrdup(argv[optidx]); + undo_glob_escape(*path1); + } + break; case I_LS: if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) return(-1); @@ -1130,7 +1225,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, int err_abort) { char *path1, *path2, *tmp; - int pflag, lflag, iflag, cmdnum, i; + int pflag, lflag, iflag, hflag, cmdnum, i; unsigned long n_arg; Attrib a, *aa; char path_buf[MAXPATHLEN]; @@ -1138,7 +1233,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, glob_t g; path1 = path2 = NULL; - cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg, + cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg, &path1, &path2); if (iflag != 0) @@ -1232,6 +1327,13 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, path1 = make_absolute(path1, *pwd); err = do_globbed_ls(conn, path1, tmp, lflag); break; + case I_DF: + /* Default to current directory if no path specified */ + if (path1 == NULL) + path1 = xstrdup(*pwd); + path1 = make_absolute(path1, *pwd); + err = do_df(conn, path1, hflag, iflag); + break; case I_LCHDIR: if (chdir(path1) == -1) { error("Couldn't change local directory to " diff --git a/sftp.h b/sftp.h index 0835da6ed41..b101b95a03c 100644 --- a/sftp.h +++ b/sftp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.h,v 1.7 2008/02/08 23:24:07 djm Exp $ */ +/* $OpenBSD: sftp.h,v 1.8 2008/04/18 12:32:11 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -79,6 +79,10 @@ #define SSH2_FXF_TRUNC 0x00000010 #define SSH2_FXF_EXCL 0x00000020 +/* statvfs@openssh.com f_flag flags */ +#define SSH2_FXE_STATVFS_ST_RDONLY 0x00000001 +#define SSH2_FXE_STATVFS_ST_NOSUID 0x00000002 + /* status messages */ #define SSH2_FX_OK 0 #define SSH2_FX_EOF 1