From ed526a0e24e730ad19c39a2ccaa1b3f946ef4b11 Mon Sep 17 00:00:00 2001 From: Joey Degges Date: Sat, 22 May 2010 01:48:04 -0700 Subject: [PATCH] Add libssh2_sftp_statvfs() and libssh2_sftp_fstatvfs() These can be used to get file system statistics from servers that support the statvfs@openssh.com and fstatvfs@openssh.com extensions. --- docs/Makefile.am | 3 + docs/libssh2_sftp_fstatvfs.3 | 1 + docs/libssh2_sftp_statvfs.3 | 81 ++++++++++++ docs/libssh2_sftp_statvfs_ex.3 | 1 + include/libssh2_sftp.h | 29 ++++ src/libssh2_priv.h | 10 ++ src/sftp.c | 234 +++++++++++++++++++++++++++++++++ 7 files changed, 359 insertions(+) create mode 100644 docs/libssh2_sftp_fstatvfs.3 create mode 100644 docs/libssh2_sftp_statvfs.3 create mode 100644 docs/libssh2_sftp_statvfs_ex.3 diff --git a/docs/Makefile.am b/docs/Makefile.am index d10585f..022e6e4 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -113,6 +113,7 @@ dist_man_MANS = \ libssh2_sftp_fsetstat.3 \ libssh2_sftp_fstat.3 \ libssh2_sftp_fstat_ex.3 \ + libssh2_sftp_fstatvfs.3 \ libssh2_sftp_init.3 \ libssh2_sftp_last_error.3 \ libssh2_sftp_lstat.3 \ @@ -136,6 +137,8 @@ dist_man_MANS = \ libssh2_sftp_shutdown.3 \ libssh2_sftp_stat.3 \ libssh2_sftp_stat_ex.3 \ + libssh2_sftp_statvfs.3 \ + libssh2_sftp_statvfs_ex.3 \ libssh2_sftp_symlink.3 \ libssh2_sftp_symlink_ex.3 \ libssh2_sftp_tell.3 \ diff --git a/docs/libssh2_sftp_fstatvfs.3 b/docs/libssh2_sftp_fstatvfs.3 new file mode 100644 index 0000000..934d2a1 --- /dev/null +++ b/docs/libssh2_sftp_fstatvfs.3 @@ -0,0 +1 @@ +.so man3/libssh2_sftp_statvfs.3 diff --git a/docs/libssh2_sftp_statvfs.3 b/docs/libssh2_sftp_statvfs.3 new file mode 100644 index 0000000..e1b058f --- /dev/null +++ b/docs/libssh2_sftp_statvfs.3 @@ -0,0 +1,81 @@ +.TH libssh2_sftp_statvfs 3 "22 May 2010" "libssh2 1.2.6" "libssh2 manual" +.SH NAME +libssh2_sftp_statvfs, libssh2_sftp_fstatvfs - get file system statistics +.SH SYNOPSIS +.nf +#include +#include + +int +libssh2_sftp_statvfs_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, LIBSSH2_SFTP_STATVFS *st); +#define libssh2_sftp_statvfs(sftp, path, st) \\ + libssh2_sftp_statvfs_ex((sftp), (path), strlen(path), (st)) + +int +libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_STATVFS *st) +.fi +.SH DESCRIPTION +These functions provide statvfs(2)-like operations and require +statvfs@openssh.com and fstatvfs@openssh.com extension support on the server. + +\fIsftp\fP - SFTP instance as returned by +.BR libssh2_sftp_init(3) + +\fIhandle\fP - SFTP File Handle as returned by +.BR libssh2_sftp_open_ex(3) + +\fIpath\fP - full path of any file within the mounted file system. + +\fIpath_len\fP - length of the full path. + +\fIst\fP - Pointer to a LIBSSH2_SFTP_STATVFS structure to place file system +statistics into. + +.SH DATA TYPES +LIBSSH2_SFTP_STATVFS is a typedefed struct that is defined as below + +.nf +struct _LIBSSH2_SFTP_STATVFS { + libssh2_uint64_t f_bsize; /* file system block size */ + libssh2_uint64_t f_frsize; /* fragment size */ + libssh2_uint64_t f_blocks; /* size of fs in f_frsize units */ + libssh2_uint64_t f_bfree; /* # free blocks */ + libssh2_uint64_t f_bavail; /* # free blocks for non-root */ + libssh2_uint64_t f_files; /* # inodes */ + libssh2_uint64_t f_ffree; /* # free inodes */ + libssh2_uint64_t f_favail; /* # free inodes for non-root */ + libssh2_uint64_t f_fsid; /* file system ID */ + libssh2_uint64_t f_flag; /* mount flags */ + libssh2_uint64_t f_namemax; /* maximum filename length */ +}; +.fi + +It is unspecified whether all members of the returned struct have meaningful +values on all file systems. + +The field \fIf_flag\fP is a bit mask. Bits are defined as follows: +.IP LIBSSH2_SFTP_ST_RDONLY +Read-only file system. +.IP LIBSSH2_SFTP_ST_NOSUID +Set-user-ID/set-group-ID bits are ignored by \fBexec\fP(3). + +.SH RETURN VALUE +Returns 0 on success or negative on failure. If used in non-blocking mode, it +returns LIBSSH2_ERROR_EAGAIN when it would otherwise block. While +LIBSSH2_ERROR_EAGAIN is a negative number, it isn't really a failure per se. +.SH ERRORS +\fILIBSSH2_ERROR_ALLOC\fP - An internal memory allocation call failed. + +\fILIBSSH2_ERROR_SOCKET_SEND\fP - Unable to send data on socket. + +\fILIBSSH2_ERROR_SOCKET_TIMEOUT\fP - + +\fILIBSSH2_ERROR_SFTP_PROTOCOL\fP - An invalid SFTP protocol response was +received on the socket, or an SFTP operation caused an errorcode to be returned +by the server. +.SH AVAILABILITY +Added in libssh2 1.2.6 +.SH SEE ALSO +.BR libssh2_sftp_open_ex(3) diff --git a/docs/libssh2_sftp_statvfs_ex.3 b/docs/libssh2_sftp_statvfs_ex.3 new file mode 100644 index 0000000..934d2a1 --- /dev/null +++ b/docs/libssh2_sftp_statvfs_ex.3 @@ -0,0 +1 @@ +.so man3/libssh2_sftp_statvfs.3 diff --git a/include/libssh2_sftp.h b/include/libssh2_sftp.h index 031533d..fa5925d 100644 --- a/include/libssh2_sftp.h +++ b/include/libssh2_sftp.h @@ -59,6 +59,7 @@ extern "C" { typedef struct _LIBSSH2_SFTP LIBSSH2_SFTP; typedef struct _LIBSSH2_SFTP_HANDLE LIBSSH2_SFTP_HANDLE; typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; +typedef struct _LIBSSH2_SFTP_STATVFS LIBSSH2_SFTP_STATVFS; /* Flags for open_ex() */ #define LIBSSH2_SFTP_OPENFILE 0 @@ -86,6 +87,10 @@ typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; #define LIBSSH2_SFTP_ATTR_ACMODTIME 0x00000008 #define LIBSSH2_SFTP_ATTR_EXTENDED 0x80000000 +/* SFTP statvfs flag bits */ +#define LIBSSH2_SFTP_ST_RDONLY 0x00000001 +#define LIBSSH2_SFTP_ST_NOSUID 0x00000002 + struct _LIBSSH2_SFTP_ATTRIBUTES { /* If flags & ATTR_* bit is set, then the value in this struct will be * meaningful Otherwise it should be ignored @@ -98,6 +103,20 @@ struct _LIBSSH2_SFTP_ATTRIBUTES { unsigned long atime, mtime; }; +struct _LIBSSH2_SFTP_STATVFS { + libssh2_uint64_t f_bsize; /* file system block size */ + libssh2_uint64_t f_frsize; /* fragment size */ + libssh2_uint64_t f_blocks; /* size of fs in f_frsize units */ + libssh2_uint64_t f_bfree; /* # free blocks */ + libssh2_uint64_t f_bavail; /* # free blocks for non-root */ + libssh2_uint64_t f_files; /* # inodes */ + libssh2_uint64_t f_ffree; /* # free inodes */ + libssh2_uint64_t f_favail; /* # free inodes for non-root */ + libssh2_uint64_t f_fsid; /* file system ID */ + libssh2_uint64_t f_flag; /* mount flags */ + libssh2_uint64_t f_namemax; /* maximum filename length */ +}; + /* SFTP filetypes */ #define LIBSSH2_SFTP_TYPE_REGULAR 1 #define LIBSSH2_SFTP_TYPE_DIRECTORY 2 @@ -269,6 +288,16 @@ LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, #define libssh2_sftp_unlink(sftp, filename) \ libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) +LIBSSH2_API int libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, + LIBSSH2_SFTP_STATVFS *st); + +LIBSSH2_API int libssh2_sftp_statvfs_ex(LIBSSH2_SFTP *sftp, + const char *path, + unsigned int path_len, + LIBSSH2_SFTP_STATVFS *st); +#define libssh2_sftp_statvfs(sftp, path, st) \ + libssh2_sftp_statvfs_ex((sftp), (path), strlen(path), (st)) + LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, long mode); diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index c50fa8c..27ba47d 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -638,6 +638,16 @@ struct _LIBSSH2_SFTP unsigned char *rename_s; uint32_t rename_request_id; + /* State variables used in libssh2_sftp_fstatvfs() */ + libssh2_nonblocking_states fstatvfs_state; + unsigned char *fstatvfs_packet; + uint32_t fstatvfs_request_id; + + /* State variables used in libssh2_sftp_statvfs() */ + libssh2_nonblocking_states statvfs_state; + unsigned char *statvfs_packet; + uint32_t statvfs_request_id; + /* State variables used in libssh2_sftp_mkdir() */ libssh2_nonblocking_states mkdir_state; unsigned char *mkdir_packet; diff --git a/src/sftp.c b/src/sftp.c index 25610a2..be8be31 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -88,6 +88,9 @@ /* S_IFDIR */ #define LIBSSH2_SFTP_ATTR_PFILETYPE_DIR 0040000 +#define SSH_FXE_STATVFS_ST_RDONLY 0x00000001 +#define SSH_FXE_STATVFS_ST_NOSUID 0x00000002 + static int sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); /* sftp_attrsize @@ -758,6 +761,14 @@ sftp_shutdown(LIBSSH2_SFTP *sftp) LIBSSH2_FREE(session, sftp->rename_packet); sftp->rename_packet = NULL; } + if (sftp->fstatvfs_packet) { + LIBSSH2_FREE(session, sftp->fstatvfs_packet); + sftp->fstatvfs_packet = NULL; + } + if (sftp->statvfs_packet) { + LIBSSH2_FREE(session, sftp->statvfs_packet); + sftp->statvfs_packet = NULL; + } if (sftp->mkdir_packet) { LIBSSH2_FREE(session, sftp->mkdir_packet); sftp->mkdir_packet = NULL; @@ -1937,6 +1948,229 @@ libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, const char *source_filename, return rc; } +/* + * sftp_fstatvfs + * + * Get file system statistics + */ +static int sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) + + handle_len (4) */ + /* 20 = strlen ("fstatvfs@openssh.com") */ + ssize_t packet_len = handle->handle_len + 20 + 17; + unsigned char *packet, *s, *data; + int rc; + unsigned int flag; + + if (sftp->fstatvfs_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Getting file system statistics"); + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_EXTENDED " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_EXTENDED; + sftp->fstatvfs_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->fstatvfs_request_id); + _libssh2_store_str(&s, "fstatvfs@openssh.com", 20); + _libssh2_store_str(&s, handle->handle, handle->handle_len); + + sftp->fstatvfs_state = libssh2_NB_state_created; + } + else { + packet = sftp->fstatvfs_packet; + } + + if (sftp->fstatvfs_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, (char *) packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN || (0 <= rc && rc < packet_len)) { + sftp->fstatvfs_packet = packet; + return LIBSSH2_ERROR_EAGAIN; + } + + LIBSSH2_FREE(session, packet); + sftp->fstatvfs_packet = NULL; + + if (rc < 0) { + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + sftp->fstatvfs_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_EXTENDED_REPLY, + sftp->fstatvfs_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for status message"); + } else if (data_len < 93) { + LIBSSH2_FREE(session, data); + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error: short response"); + } + + sftp->fstatvfs_state = libssh2_NB_state_idle; + + st->f_bsize = _libssh2_ntohu64(data + 5); + st->f_frsize = _libssh2_ntohu64(data + 13); + st->f_blocks = _libssh2_ntohu64(data + 21); + st->f_bfree = _libssh2_ntohu64(data + 29); + st->f_bavail = _libssh2_ntohu64(data + 37); + st->f_files = _libssh2_ntohu64(data + 45); + st->f_ffree = _libssh2_ntohu64(data + 53); + st->f_favail = _libssh2_ntohu64(data + 61); + st->f_fsid = _libssh2_ntohu64(data + 69); + flag = _libssh2_ntohu64(data + 77); + st->f_namemax = _libssh2_ntohu64(data + 85); + + st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) + ? LIBSSH2_SFTP_ST_RDONLY : 0; + st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) + ? LIBSSH2_SFTP_ST_NOSUID : 0; + + LIBSSH2_FREE(session, data); + return 0; +} + +/* libssh2_sftp_fstatvfs + * Get filesystem space and inode utilization (requires fstatvfs@openssh.com + * support on the server) + */ +LIBSSH2_API int +libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) +{ + int rc; + BLOCK_ADJUST(rc, handle->sftp->channel->session, sftp_fstatvfs(handle, st)); + return rc; +} + +/* + * sftp_statvfs + * + * Get file system statistics + */ +static int sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, LIBSSH2_SFTP_STATVFS *st) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + size_t data_len; + /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) + + path_len (4) */ + /* 19 = strlen ("statvfs@openssh.com") */ + ssize_t packet_len = path_len + 19 + 17; + unsigned char *packet, *s, *data; + int rc; + unsigned int flag; + + if (sftp->statvfs_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_TRACE_SFTP, + "Getting file system statistics of %s", path); + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for FXP_EXTENDED " + "packet"); + } + + _libssh2_store_u32(&s, packet_len - 4); + *(s++) = SSH_FXP_EXTENDED; + sftp->statvfs_request_id = sftp->request_id++; + _libssh2_store_u32(&s, sftp->statvfs_request_id); + _libssh2_store_str(&s, "statvfs@openssh.com", 19); + _libssh2_store_str(&s, path, path_len); + + sftp->statvfs_state = libssh2_NB_state_created; + } + else { + packet = sftp->statvfs_packet; + } + + if (sftp->statvfs_state == libssh2_NB_state_created) { + rc = _libssh2_channel_write(channel, 0, (char *) packet, packet_len); + if (rc == LIBSSH2_ERROR_EAGAIN || (0 <= rc && rc < packet_len)) { + sftp->statvfs_packet = packet; + return LIBSSH2_ERROR_EAGAIN; + } + + LIBSSH2_FREE(session, packet); + sftp->statvfs_packet = NULL; + + if (rc < 0) { + sftp->statvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "_libssh2_channel_write() failed"); + } + sftp->statvfs_state = libssh2_NB_state_sent; + } + + rc = sftp_packet_require(sftp, SSH_FXP_EXTENDED_REPLY, + sftp->statvfs_request_id, &data, &data_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + sftp->statvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, + "Timeout waiting for status message"); + } else if (data_len < 93) { + LIBSSH2_FREE(session, data); + sftp->fstatvfs_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, + "SFTP Protocol Error: short response"); + } + + sftp->statvfs_state = libssh2_NB_state_idle; + + st->f_bsize = _libssh2_ntohu64(data + 5); + st->f_frsize = _libssh2_ntohu64(data + 13); + st->f_blocks = _libssh2_ntohu64(data + 21); + st->f_bfree = _libssh2_ntohu64(data + 29); + st->f_bavail = _libssh2_ntohu64(data + 37); + st->f_files = _libssh2_ntohu64(data + 45); + st->f_ffree = _libssh2_ntohu64(data + 53); + st->f_favail = _libssh2_ntohu64(data + 61); + st->f_fsid = _libssh2_ntohu64(data + 69); + flag = _libssh2_ntohu64(data + 77); + st->f_namemax = _libssh2_ntohu64(data + 85); + + st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) + ? LIBSSH2_SFTP_ST_RDONLY : 0; + st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) + ? LIBSSH2_SFTP_ST_NOSUID : 0; + + LIBSSH2_FREE(session, data); + return 0; +} + +/* libssh2_sftp_statvfs_ex + * Get filesystem space and inode utilization (requires statvfs@openssh.com + * support on the server) + */ +LIBSSH2_API int +libssh2_sftp_statvfs_ex(LIBSSH2_SFTP *sftp, const char *path, + unsigned int path_len, LIBSSH2_SFTP_STATVFS *st) +{ + int rc; + BLOCK_ADJUST(rc, sftp->channel->session, sftp_statvfs(sftp, path, path_len, + st)); + return rc; +} + + /* * sftp_mkdir *