Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
sshfs/sshfs.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
4063 lines (3574 sloc)
90 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| SSH file system | |
| Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu> | |
| This program can be distributed under the terms of the GNU GPL. | |
| See the file COPYING. | |
| */ | |
| #define _GNU_SOURCE /* avoid implicit declaration of *pt* functions */ | |
| #include "config.h" | |
| #include <fuse.h> | |
| #include <fuse_opt.h> | |
| #include <fuse_lowlevel.h> | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #include <errno.h> | |
| #ifdef __APPLE__ | |
| #include <darwin_semaphore.h> | |
| #else | |
| #include <semaphore.h> | |
| #endif | |
| #include <pthread.h> | |
| #include <netdb.h> | |
| #include <signal.h> | |
| #include <sys/uio.h> | |
| #include <sys/types.h> | |
| #include <sys/time.h> | |
| #include <sys/wait.h> | |
| #include <sys/socket.h> | |
| #include <sys/utsname.h> | |
| #include <sys/mman.h> | |
| #include <sys/poll.h> | |
| #include <netinet/in.h> | |
| #include <netinet/tcp.h> | |
| #include <glib.h> | |
| #include <pwd.h> | |
| #include <grp.h> | |
| #include <limits.h> | |
| #ifdef __APPLE__ | |
| #include <libgen.h> | |
| #include <strings.h> | |
| #endif | |
| #include "cache.h" | |
| #ifndef MAP_LOCKED | |
| #define MAP_LOCKED 0 | |
| #endif | |
| #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) | |
| #define MAP_ANONYMOUS MAP_ANON | |
| #endif | |
| #define SSH_FXP_INIT 1 | |
| #define SSH_FXP_VERSION 2 | |
| #define SSH_FXP_OPEN 3 | |
| #define SSH_FXP_CLOSE 4 | |
| #define SSH_FXP_READ 5 | |
| #define SSH_FXP_WRITE 6 | |
| #define SSH_FXP_LSTAT 7 | |
| #define SSH_FXP_FSTAT 8 | |
| #define SSH_FXP_SETSTAT 9 | |
| #define SSH_FXP_FSETSTAT 10 | |
| #define SSH_FXP_OPENDIR 11 | |
| #define SSH_FXP_READDIR 12 | |
| #define SSH_FXP_REMOVE 13 | |
| #define SSH_FXP_MKDIR 14 | |
| #define SSH_FXP_RMDIR 15 | |
| #define SSH_FXP_REALPATH 16 | |
| #define SSH_FXP_STAT 17 | |
| #define SSH_FXP_RENAME 18 | |
| #define SSH_FXP_READLINK 19 | |
| #define SSH_FXP_SYMLINK 20 | |
| #define SSH_FXP_STATUS 101 | |
| #define SSH_FXP_HANDLE 102 | |
| #define SSH_FXP_DATA 103 | |
| #define SSH_FXP_NAME 104 | |
| #define SSH_FXP_ATTRS 105 | |
| #define SSH_FXP_EXTENDED 200 | |
| #define SSH_FXP_EXTENDED_REPLY 201 | |
| #define SSH_FILEXFER_ATTR_SIZE 0x00000001 | |
| #define SSH_FILEXFER_ATTR_UIDGID 0x00000002 | |
| #define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 | |
| #define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 | |
| #define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 | |
| #define SSH_FX_OK 0 | |
| #define SSH_FX_EOF 1 | |
| #define SSH_FX_NO_SUCH_FILE 2 | |
| #define SSH_FX_PERMISSION_DENIED 3 | |
| #define SSH_FX_FAILURE 4 | |
| #define SSH_FX_BAD_MESSAGE 5 | |
| #define SSH_FX_NO_CONNECTION 6 | |
| #define SSH_FX_CONNECTION_LOST 7 | |
| #define SSH_FX_OP_UNSUPPORTED 8 | |
| #define SSH_FXF_READ 0x00000001 | |
| #define SSH_FXF_WRITE 0x00000002 | |
| #define SSH_FXF_APPEND 0x00000004 | |
| #define SSH_FXF_CREAT 0x00000008 | |
| #define SSH_FXF_TRUNC 0x00000010 | |
| #define SSH_FXF_EXCL 0x00000020 | |
| /* statvfs@openssh.com f_flag flags */ | |
| #define SSH2_FXE_STATVFS_ST_RDONLY 0x00000001 | |
| #define SSH2_FXE_STATVFS_ST_NOSUID 0x00000002 | |
| #define SFTP_EXT_POSIX_RENAME "posix-rename@openssh.com" | |
| #define SFTP_EXT_STATVFS "statvfs@openssh.com" | |
| #define SFTP_EXT_HARDLINK "hardlink@openssh.com" | |
| #define PROTO_VERSION 3 | |
| #define MY_EOF 1 | |
| #define MAX_REPLY_LEN (1 << 17) | |
| #define RENAME_TEMP_CHARS 8 | |
| #define SFTP_SERVER_PATH "/usr/lib/sftp-server" | |
| #define SSHNODELAY_SO "sshnodelay.so" | |
| #ifdef __APPLE__ | |
| #ifndef LIBDIR | |
| #define LIBDIR "/usr/local/lib" | |
| #endif | |
| static char sshfs_program_path[PATH_MAX] = { 0 }; | |
| #endif | |
| struct buffer { | |
| uint8_t *p; | |
| size_t len; | |
| size_t size; | |
| }; | |
| struct list_head { | |
| struct list_head *prev; | |
| struct list_head *next; | |
| }; | |
| struct request; | |
| typedef void (*request_func)(struct request *); | |
| struct request { | |
| unsigned int want_reply; | |
| sem_t ready; | |
| uint8_t reply_type; | |
| int replied; | |
| int error; | |
| struct buffer reply; | |
| struct timeval start; | |
| void *data; | |
| request_func end_func; | |
| size_t len; | |
| struct list_head list; | |
| }; | |
| struct sshfs_io { | |
| int num_reqs; | |
| pthread_cond_t finished; | |
| int error; | |
| }; | |
| struct read_req { | |
| struct sshfs_io *sio; | |
| struct list_head list; | |
| struct buffer data; | |
| size_t size; | |
| ssize_t res; | |
| }; | |
| struct read_chunk { | |
| off_t offset; | |
| size_t size; | |
| int refs; | |
| long modifver; | |
| struct list_head reqs; | |
| struct sshfs_io sio; | |
| }; | |
| struct sshfs_file { | |
| struct buffer handle; | |
| struct list_head write_reqs; | |
| pthread_cond_t write_finished; | |
| int write_error; | |
| struct read_chunk *readahead; | |
| off_t next_pos; | |
| int is_seq; | |
| int connver; | |
| int modifver; | |
| int refs; | |
| #ifdef __APPLE__ | |
| pthread_mutex_t file_lock; | |
| #endif | |
| }; | |
| struct sshfs { | |
| char *directport; | |
| char *ssh_command; | |
| char *sftp_server; | |
| struct fuse_args ssh_args; | |
| char *workarounds; | |
| int rename_workaround; | |
| int nodelay_workaround; | |
| int nodelaysrv_workaround; | |
| int truncate_workaround; | |
| int buflimit_workaround; | |
| int fstat_workaround; | |
| int transform_symlinks; | |
| int follow_symlinks; | |
| int no_check_root; | |
| int detect_uid; | |
| int idmap; | |
| int nomap; | |
| char *uid_file; | |
| char *gid_file; | |
| GHashTable *uid_map; | |
| GHashTable *gid_map; | |
| GHashTable *r_uid_map; | |
| GHashTable *r_gid_map; | |
| unsigned max_read; | |
| unsigned max_write; | |
| unsigned ssh_ver; | |
| int sync_write; | |
| int sync_read; | |
| int debug; | |
| int foreground; | |
| int reconnect; | |
| int delay_connect; | |
| int slave; | |
| char *host; | |
| char *base_path; | |
| GHashTable *reqtab; | |
| pthread_mutex_t lock; | |
| pthread_mutex_t lock_write; | |
| int processing_thread_started; | |
| unsigned int randseed; | |
| int rfd; | |
| int wfd; | |
| int ptyfd; | |
| int ptyslavefd; | |
| int connver; | |
| int server_version; | |
| unsigned remote_uid; | |
| unsigned local_uid; | |
| #ifdef __APPLE__ | |
| unsigned remote_gid; | |
| unsigned local_gid; | |
| #endif | |
| int remote_uid_detected; | |
| unsigned blksize; | |
| char *progname; | |
| long modifver; | |
| unsigned outstanding_len; | |
| unsigned max_outstanding_len; | |
| pthread_cond_t outstanding_cond; | |
| int password_stdin; | |
| char *password; | |
| int ext_posix_rename; | |
| int ext_statvfs; | |
| int ext_hardlink; | |
| mode_t mnt_mode; | |
| /* statistics */ | |
| uint64_t bytes_sent; | |
| uint64_t bytes_received; | |
| uint64_t num_sent; | |
| uint64_t num_received; | |
| unsigned int min_rtt; | |
| unsigned int max_rtt; | |
| uint64_t total_rtt; | |
| unsigned int num_connect; | |
| }; | |
| static struct sshfs sshfs; | |
| static const char *ssh_opts[] = { | |
| "AddressFamily", | |
| "BatchMode", | |
| "BindAddress", | |
| "ChallengeResponseAuthentication", | |
| "CheckHostIP", | |
| "Cipher", | |
| "Ciphers", | |
| "Compression", | |
| "CompressionLevel", | |
| "ConnectionAttempts", | |
| "ConnectTimeout", | |
| "ControlMaster", | |
| "ControlPath", | |
| "GlobalKnownHostsFile", | |
| "GSSAPIAuthentication", | |
| "GSSAPIDelegateCredentials", | |
| "HostbasedAuthentication", | |
| "HostKeyAlgorithms", | |
| "HostKeyAlias", | |
| "HostName", | |
| "IdentitiesOnly", | |
| "IdentityFile", | |
| "KbdInteractiveAuthentication", | |
| "KbdInteractiveDevices", | |
| "LocalCommand", | |
| "LogLevel", | |
| "MACs", | |
| "NoHostAuthenticationForLocalhost", | |
| "NumberOfPasswordPrompts", | |
| "PasswordAuthentication", | |
| "Port", | |
| "PreferredAuthentications", | |
| "ProxyCommand", | |
| "PubkeyAuthentication", | |
| "RekeyLimit", | |
| "RhostsRSAAuthentication", | |
| "RSAAuthentication", | |
| "ServerAliveCountMax", | |
| "ServerAliveInterval", | |
| "SmartcardDevice", | |
| "StrictHostKeyChecking", | |
| "TCPKeepAlive", | |
| "UsePrivilegedPort", | |
| "UserKnownHostsFile", | |
| "VerifyHostKeyDNS", | |
| NULL, | |
| }; | |
| enum { | |
| KEY_PORT, | |
| KEY_COMPRESS, | |
| KEY_HELP, | |
| KEY_VERSION, | |
| KEY_FOREGROUND, | |
| KEY_CONFIGFILE, | |
| }; | |
| enum { | |
| IDMAP_NONE, | |
| IDMAP_USER, | |
| IDMAP_FILE, | |
| }; | |
| enum { | |
| NOMAP_IGNORE, | |
| NOMAP_ERROR, | |
| }; | |
| #define SSHFS_OPT(t, p, v) { t, offsetof(struct sshfs, p), v } | |
| static struct fuse_opt sshfs_opts[] = { | |
| SSHFS_OPT("directport=%s", directport, 0), | |
| SSHFS_OPT("ssh_command=%s", ssh_command, 0), | |
| SSHFS_OPT("sftp_server=%s", sftp_server, 0), | |
| SSHFS_OPT("max_read=%u", max_read, 0), | |
| SSHFS_OPT("max_write=%u", max_write, 0), | |
| SSHFS_OPT("ssh_protocol=%u", ssh_ver, 0), | |
| SSHFS_OPT("-1", ssh_ver, 1), | |
| SSHFS_OPT("workaround=%s", workarounds, 0), | |
| SSHFS_OPT("idmap=none", idmap, IDMAP_NONE), | |
| SSHFS_OPT("idmap=user", idmap, IDMAP_USER), | |
| SSHFS_OPT("idmap=file", idmap, IDMAP_FILE), | |
| SSHFS_OPT("uidfile=%s", uid_file, 0), | |
| SSHFS_OPT("gidfile=%s", gid_file, 0), | |
| SSHFS_OPT("nomap=ignore", nomap, NOMAP_IGNORE), | |
| SSHFS_OPT("nomap=error", nomap, NOMAP_ERROR), | |
| SSHFS_OPT("sshfs_sync", sync_write, 1), | |
| SSHFS_OPT("no_readahead", sync_read, 1), | |
| SSHFS_OPT("sshfs_debug", debug, 1), | |
| SSHFS_OPT("reconnect", reconnect, 1), | |
| SSHFS_OPT("transform_symlinks", transform_symlinks, 1), | |
| SSHFS_OPT("follow_symlinks", follow_symlinks, 1), | |
| SSHFS_OPT("no_check_root", no_check_root, 1), | |
| SSHFS_OPT("password_stdin", password_stdin, 1), | |
| SSHFS_OPT("delay_connect", delay_connect, 1), | |
| SSHFS_OPT("slave", slave, 1), | |
| FUSE_OPT_KEY("-p ", KEY_PORT), | |
| FUSE_OPT_KEY("-C", KEY_COMPRESS), | |
| FUSE_OPT_KEY("-V", KEY_VERSION), | |
| FUSE_OPT_KEY("--version", KEY_VERSION), | |
| FUSE_OPT_KEY("-h", KEY_HELP), | |
| FUSE_OPT_KEY("--help", KEY_HELP), | |
| FUSE_OPT_KEY("debug", KEY_FOREGROUND), | |
| FUSE_OPT_KEY("-d", KEY_FOREGROUND), | |
| FUSE_OPT_KEY("-f", KEY_FOREGROUND), | |
| FUSE_OPT_KEY("-F ", KEY_CONFIGFILE), | |
| FUSE_OPT_END | |
| }; | |
| static struct fuse_opt workaround_opts[] = { | |
| SSHFS_OPT("none", rename_workaround, 0), | |
| SSHFS_OPT("none", nodelay_workaround, 0), | |
| SSHFS_OPT("none", nodelaysrv_workaround, 0), | |
| SSHFS_OPT("none", truncate_workaround, 0), | |
| SSHFS_OPT("none", buflimit_workaround, 0), | |
| SSHFS_OPT("none", fstat_workaround, 0), | |
| SSHFS_OPT("all", rename_workaround, 1), | |
| SSHFS_OPT("all", nodelay_workaround, 1), | |
| SSHFS_OPT("all", nodelaysrv_workaround, 1), | |
| SSHFS_OPT("all", truncate_workaround, 1), | |
| SSHFS_OPT("all", buflimit_workaround, 1), | |
| SSHFS_OPT("all", fstat_workaround, 1), | |
| SSHFS_OPT("rename", rename_workaround, 1), | |
| SSHFS_OPT("norename", rename_workaround, 0), | |
| SSHFS_OPT("nodelay", nodelay_workaround, 1), | |
| SSHFS_OPT("nonodelay", nodelay_workaround, 0), | |
| SSHFS_OPT("nodelaysrv", nodelaysrv_workaround, 1), | |
| SSHFS_OPT("nonodelaysrv", nodelaysrv_workaround, 0), | |
| SSHFS_OPT("truncate", truncate_workaround, 1), | |
| SSHFS_OPT("notruncate", truncate_workaround, 0), | |
| SSHFS_OPT("buflimit", buflimit_workaround, 1), | |
| SSHFS_OPT("nobuflimit", buflimit_workaround, 0), | |
| SSHFS_OPT("fstat", fstat_workaround, 1), | |
| SSHFS_OPT("nofstat", fstat_workaround, 0), | |
| FUSE_OPT_END | |
| }; | |
| #define DEBUG(format, args...) \ | |
| do { if (sshfs.debug) fprintf(stderr, format, args); } while(0) | |
| static const char *type_name(uint8_t type) | |
| { | |
| switch(type) { | |
| case SSH_FXP_INIT: return "INIT"; | |
| case SSH_FXP_VERSION: return "VERSION"; | |
| case SSH_FXP_OPEN: return "OPEN"; | |
| case SSH_FXP_CLOSE: return "CLOSE"; | |
| case SSH_FXP_READ: return "READ"; | |
| case SSH_FXP_WRITE: return "WRITE"; | |
| case SSH_FXP_LSTAT: return "LSTAT"; | |
| case SSH_FXP_FSTAT: return "FSTAT"; | |
| case SSH_FXP_SETSTAT: return "SETSTAT"; | |
| case SSH_FXP_FSETSTAT: return "FSETSTAT"; | |
| case SSH_FXP_OPENDIR: return "OPENDIR"; | |
| case SSH_FXP_READDIR: return "READDIR"; | |
| case SSH_FXP_REMOVE: return "REMOVE"; | |
| case SSH_FXP_MKDIR: return "MKDIR"; | |
| case SSH_FXP_RMDIR: return "RMDIR"; | |
| case SSH_FXP_REALPATH: return "REALPATH"; | |
| case SSH_FXP_STAT: return "STAT"; | |
| case SSH_FXP_RENAME: return "RENAME"; | |
| case SSH_FXP_READLINK: return "READLINK"; | |
| case SSH_FXP_SYMLINK: return "SYMLINK"; | |
| case SSH_FXP_STATUS: return "STATUS"; | |
| case SSH_FXP_HANDLE: return "HANDLE"; | |
| case SSH_FXP_DATA: return "DATA"; | |
| case SSH_FXP_NAME: return "NAME"; | |
| case SSH_FXP_ATTRS: return "ATTRS"; | |
| case SSH_FXP_EXTENDED: return "EXTENDED"; | |
| case SSH_FXP_EXTENDED_REPLY: return "EXTENDED_REPLY"; | |
| default: return "???"; | |
| } | |
| } | |
| #define container_of(ptr, type, member) ({ \ | |
| const typeof( ((type *)0)->member ) *__mptr = (ptr); \ | |
| (type *)( (char *)__mptr - offsetof(type,member) );}) | |
| #define list_entry(ptr, type, member) \ | |
| container_of(ptr, type, member) | |
| static void list_init(struct list_head *head) | |
| { | |
| head->next = head; | |
| head->prev = head; | |
| } | |
| static void list_add(struct list_head *new, struct list_head *head) | |
| { | |
| struct list_head *prev = head; | |
| struct list_head *next = head->next; | |
| next->prev = new; | |
| new->next = next; | |
| new->prev = prev; | |
| prev->next = new; | |
| } | |
| static void list_del(struct list_head *entry) | |
| { | |
| struct list_head *prev = entry->prev; | |
| struct list_head *next = entry->next; | |
| next->prev = prev; | |
| prev->next = next; | |
| } | |
| static int list_empty(const struct list_head *head) | |
| { | |
| return head->next == head; | |
| } | |
| /* given a pointer to the uid/gid, and the mapping table, remap the | |
| * uid/gid, if necessary */ | |
| static inline int translate_id(uint32_t *id, GHashTable *map) | |
| { | |
| gpointer id_p; | |
| if (g_hash_table_lookup_extended(map, GUINT_TO_POINTER(*id), NULL, &id_p)) { | |
| *id = GPOINTER_TO_UINT(id_p); | |
| return 0; | |
| } | |
| switch (sshfs.nomap) { | |
| case NOMAP_ERROR: return -1; | |
| case NOMAP_IGNORE: return 0; | |
| default: | |
| fprintf(stderr, "internal error\n"); | |
| abort(); | |
| } | |
| } | |
| static inline void buf_init(struct buffer *buf, size_t size) | |
| { | |
| if (size) { | |
| buf->p = (uint8_t *) malloc(size); | |
| if (!buf->p) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| } else | |
| buf->p = NULL; | |
| buf->len = 0; | |
| buf->size = size; | |
| } | |
| static inline void buf_free(struct buffer *buf) | |
| { | |
| free(buf->p); | |
| } | |
| static inline void buf_finish(struct buffer *buf) | |
| { | |
| buf->len = buf->size; | |
| } | |
| static inline void buf_clear(struct buffer *buf) | |
| { | |
| buf_free(buf); | |
| buf_init(buf, 0); | |
| } | |
| static void buf_resize(struct buffer *buf, size_t len) | |
| { | |
| buf->size = (buf->len + len + 63) & ~31; | |
| buf->p = (uint8_t *) realloc(buf->p, buf->size); | |
| if (!buf->p) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| } | |
| static inline void buf_check_add(struct buffer *buf, size_t len) | |
| { | |
| if (buf->len + len > buf->size) | |
| buf_resize(buf, len); | |
| } | |
| #define _buf_add_mem(b, d, l) \ | |
| buf_check_add(b, l); \ | |
| memcpy(b->p + b->len, d, l); \ | |
| b->len += l; | |
| static inline void buf_add_mem(struct buffer *buf, const void *data, | |
| size_t len) | |
| { | |
| _buf_add_mem(buf, data, len); | |
| } | |
| static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa) | |
| { | |
| _buf_add_mem(buf, bufa->p, bufa->len); | |
| } | |
| static inline void buf_add_uint8(struct buffer *buf, uint8_t val) | |
| { | |
| _buf_add_mem(buf, &val, 1); | |
| } | |
| static inline void buf_add_uint32(struct buffer *buf, uint32_t val) | |
| { | |
| uint32_t nval = htonl(val); | |
| _buf_add_mem(buf, &nval, 4); | |
| } | |
| static inline void buf_add_uint64(struct buffer *buf, uint64_t val) | |
| { | |
| buf_add_uint32(buf, val >> 32); | |
| buf_add_uint32(buf, val & 0xffffffff); | |
| } | |
| static inline void buf_add_data(struct buffer *buf, const struct buffer *data) | |
| { | |
| buf_add_uint32(buf, data->len); | |
| buf_add_mem(buf, data->p, data->len); | |
| } | |
| static inline void buf_add_string(struct buffer *buf, const char *str) | |
| { | |
| struct buffer data; | |
| data.p = (uint8_t *) str; | |
| data.len = strlen(str); | |
| buf_add_data(buf, &data); | |
| } | |
| static inline void buf_add_path(struct buffer *buf, const char *path) | |
| { | |
| char *realpath; | |
| if (sshfs.base_path[0]) { | |
| if (path[1]) { | |
| if (sshfs.base_path[strlen(sshfs.base_path)-1] != '/') { | |
| realpath = g_strdup_printf("%s/%s", | |
| sshfs.base_path, | |
| path + 1); | |
| } else { | |
| realpath = g_strdup_printf("%s%s", | |
| sshfs.base_path, | |
| path + 1); | |
| } | |
| } else { | |
| realpath = g_strdup(sshfs.base_path); | |
| } | |
| } else { | |
| if (path[1]) | |
| realpath = g_strdup(path + 1); | |
| else | |
| realpath = g_strdup("."); | |
| } | |
| buf_add_string(buf, realpath); | |
| g_free(realpath); | |
| } | |
| static int buf_check_get(struct buffer *buf, size_t len) | |
| { | |
| if (buf->len + len > buf->size) { | |
| fprintf(stderr, "buffer too short\n"); | |
| return -1; | |
| } else | |
| return 0; | |
| } | |
| static inline int buf_get_mem(struct buffer *buf, void *data, size_t len) | |
| { | |
| if (buf_check_get(buf, len) == -1) | |
| return -1; | |
| memcpy(data, buf->p + buf->len, len); | |
| buf->len += len; | |
| return 0; | |
| } | |
| static inline int buf_get_uint8(struct buffer *buf, uint8_t *val) | |
| { | |
| return buf_get_mem(buf, val, 1); | |
| } | |
| static inline int buf_get_uint32(struct buffer *buf, uint32_t *val) | |
| { | |
| uint32_t nval; | |
| if (buf_get_mem(buf, &nval, 4) == -1) | |
| return -1; | |
| *val = ntohl(nval); | |
| return 0; | |
| } | |
| static inline int buf_get_uint64(struct buffer *buf, uint64_t *val) | |
| { | |
| uint32_t val1; | |
| uint32_t val2; | |
| if (buf_get_uint32(buf, &val1) == -1 || | |
| buf_get_uint32(buf, &val2) == -1) { | |
| return -1; | |
| } | |
| *val = ((uint64_t) val1 << 32) + val2; | |
| return 0; | |
| } | |
| static inline int buf_get_data(struct buffer *buf, struct buffer *data) | |
| { | |
| uint32_t len; | |
| if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len) | |
| return -1; | |
| buf_init(data, len + 1); | |
| data->size = len; | |
| if (buf_get_mem(buf, data->p, data->size) == -1) { | |
| buf_free(data); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static inline int buf_get_string(struct buffer *buf, char **str) | |
| { | |
| struct buffer data; | |
| if (buf_get_data(buf, &data) == -1) | |
| return -1; | |
| data.p[data.size] = '\0'; | |
| *str = (char *) data.p; | |
| return 0; | |
| } | |
| static int buf_get_attrs(struct buffer *buf, struct stat *stbuf, int *flagsp) | |
| { | |
| uint32_t flags; | |
| uint64_t size = 0; | |
| uint32_t uid = 0; | |
| uint32_t gid = 0; | |
| uint32_t atime = 0; | |
| uint32_t mtime = 0; | |
| uint32_t mode = S_IFREG | 0777; | |
| if (buf_get_uint32(buf, &flags) == -1) | |
| return -EIO; | |
| if (flagsp) | |
| *flagsp = flags; | |
| if ((flags & SSH_FILEXFER_ATTR_SIZE) && | |
| buf_get_uint64(buf, &size) == -1) | |
| return -EIO; | |
| if ((flags & SSH_FILEXFER_ATTR_UIDGID) && | |
| (buf_get_uint32(buf, &uid) == -1 || | |
| buf_get_uint32(buf, &gid) == -1)) | |
| return -EIO; | |
| if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) && | |
| buf_get_uint32(buf, &mode) == -1) | |
| return -EIO; | |
| if ((flags & SSH_FILEXFER_ATTR_ACMODTIME)) { | |
| if (buf_get_uint32(buf, &atime) == -1 || | |
| buf_get_uint32(buf, &mtime) == -1) | |
| return -EIO; | |
| } | |
| if ((flags & SSH_FILEXFER_ATTR_EXTENDED)) { | |
| uint32_t extcount; | |
| unsigned i; | |
| if (buf_get_uint32(buf, &extcount) == -1) | |
| return -EIO; | |
| for (i = 0; i < extcount; i++) { | |
| struct buffer tmp; | |
| if (buf_get_data(buf, &tmp) == -1) | |
| return -EIO; | |
| buf_free(&tmp); | |
| if (buf_get_data(buf, &tmp) == -1) | |
| return -EIO; | |
| buf_free(&tmp); | |
| } | |
| } | |
| #ifdef __APPLE__ | |
| if (sshfs.remote_uid_detected) { | |
| if (uid == sshfs.remote_uid) | |
| uid = sshfs.local_uid; | |
| if (gid == sshfs.remote_gid) | |
| gid = sshfs.local_gid; | |
| } | |
| #else | |
| if (sshfs.remote_uid_detected && uid == sshfs.remote_uid) | |
| uid = sshfs.local_uid; | |
| #endif | |
| if (sshfs.idmap == IDMAP_FILE && sshfs.uid_map) | |
| if (translate_id(&uid, sshfs.uid_map) == -1) | |
| return -EPERM; | |
| if (sshfs.idmap == IDMAP_FILE && sshfs.gid_map) | |
| if (translate_id(&gid, sshfs.gid_map) == -1) | |
| return -EPERM; | |
| memset(stbuf, 0, sizeof(struct stat)); | |
| stbuf->st_mode = mode; | |
| stbuf->st_nlink = 1; | |
| stbuf->st_size = size; | |
| if (sshfs.blksize) { | |
| stbuf->st_blksize = sshfs.blksize; | |
| stbuf->st_blocks = ((size + sshfs.blksize - 1) & | |
| ~((unsigned long long) sshfs.blksize - 1)) >> 9; | |
| } | |
| stbuf->st_uid = uid; | |
| stbuf->st_gid = gid; | |
| stbuf->st_atime = atime; | |
| stbuf->st_ctime = stbuf->st_mtime = mtime; | |
| return 0; | |
| } | |
| static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf) | |
| { | |
| uint64_t bsize; | |
| uint64_t frsize; | |
| uint64_t blocks; | |
| uint64_t bfree; | |
| uint64_t bavail; | |
| uint64_t files; | |
| uint64_t ffree; | |
| uint64_t favail; | |
| uint64_t fsid; | |
| uint64_t flag; | |
| uint64_t namemax; | |
| if (buf_get_uint64(buf, &bsize) == -1 || | |
| buf_get_uint64(buf, &frsize) == -1 || | |
| buf_get_uint64(buf, &blocks) == -1 || | |
| buf_get_uint64(buf, &bfree) == -1 || | |
| buf_get_uint64(buf, &bavail) == -1 || | |
| buf_get_uint64(buf, &files) == -1 || | |
| buf_get_uint64(buf, &ffree) == -1 || | |
| buf_get_uint64(buf, &favail) == -1 || | |
| buf_get_uint64(buf, &fsid) == -1 || | |
| buf_get_uint64(buf, &flag) == -1 || | |
| buf_get_uint64(buf, &namemax) == -1) { | |
| return -1; | |
| } | |
| memset(stbuf, 0, sizeof(struct statvfs)); | |
| stbuf->f_bsize = bsize; | |
| stbuf->f_frsize = frsize; | |
| stbuf->f_blocks = blocks; | |
| stbuf->f_bfree = bfree; | |
| stbuf->f_bavail = bavail; | |
| stbuf->f_files = files; | |
| stbuf->f_ffree = ffree; | |
| stbuf->f_favail = favail; | |
| stbuf->f_namemax = namemax; | |
| return 0; | |
| } | |
| static int buf_get_entries(struct buffer *buf, fuse_cache_dirh_t h, | |
| fuse_cache_dirfil_t filler) | |
| { | |
| uint32_t count; | |
| unsigned i; | |
| if (buf_get_uint32(buf, &count) == -1) | |
| return -EIO; | |
| for (i = 0; i < count; i++) { | |
| int err = -1; | |
| char *name; | |
| char *longname; | |
| struct stat stbuf; | |
| if (buf_get_string(buf, &name) == -1) | |
| return -EIO; | |
| if (buf_get_string(buf, &longname) != -1) { | |
| free(longname); | |
| err = buf_get_attrs(buf, &stbuf, NULL); | |
| if (!err) { | |
| if (sshfs.follow_symlinks && | |
| S_ISLNK(stbuf.st_mode)) { | |
| stbuf.st_mode = 0; | |
| } | |
| filler(h, name, &stbuf); | |
| } | |
| } | |
| free(name); | |
| if (err) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static void ssh_add_arg(const char *arg) | |
| { | |
| if (fuse_opt_add_arg(&sshfs.ssh_args, arg) == -1) | |
| _exit(1); | |
| } | |
| #ifdef SSH_NODELAY_WORKAROUND | |
| static int do_ssh_nodelay_workaround(void) | |
| { | |
| #ifdef __APPLE__ | |
| char *oldpreload = getenv("DYLD_INSERT_LIBRARIES"); | |
| #else | |
| char *oldpreload = getenv("LD_PRELOAD"); | |
| #endif | |
| char *newpreload; | |
| char sopath[PATH_MAX]; | |
| int res; | |
| #ifdef __APPLE__ | |
| char *sshfs_program_path_base = NULL; | |
| if (!sshfs_program_path[0]) { | |
| goto nobundle; | |
| } | |
| sshfs_program_path_base = dirname(sshfs_program_path); | |
| if (!sshfs_program_path_base) { | |
| goto nobundle; | |
| } | |
| snprintf(sopath, sizeof(sopath), "%s/%s", sshfs_program_path_base, | |
| SSHNODELAY_SO); | |
| res = access(sopath, R_OK); | |
| if (res == -1) { | |
| goto nobundle; | |
| } | |
| goto pathok; | |
| nobundle: | |
| #endif | |
| snprintf(sopath, sizeof(sopath), "%s/%s", LIBDIR, SSHNODELAY_SO); | |
| res = access(sopath, R_OK); | |
| if (res == -1) { | |
| char *s; | |
| if (!realpath(sshfs.progname, sopath)) | |
| return -1; | |
| s = strrchr(sopath, '/'); | |
| if (!s) | |
| s = sopath; | |
| else | |
| s++; | |
| if (s + strlen(SSHNODELAY_SO) >= sopath + sizeof(sopath)) | |
| return -1; | |
| strcpy(s, SSHNODELAY_SO); | |
| res = access(sopath, R_OK); | |
| if (res == -1) { | |
| fprintf(stderr, "sshfs: cannot find %s\n", | |
| SSHNODELAY_SO); | |
| return -1; | |
| } | |
| } | |
| #ifdef __APPLE__ | |
| pathok: | |
| #endif | |
| newpreload = g_strdup_printf("%s%s%s", | |
| oldpreload ? oldpreload : "", | |
| oldpreload ? " " : "", | |
| sopath); | |
| #ifdef __APPLE__ | |
| if (!newpreload || setenv("DYLD_INSERT_LIBRARIES", newpreload, 1) == -1) | |
| fprintf(stderr, "warning: failed set DYLD_INSERT_LIBRARIES for ssh nodelay workaround\n"); | |
| #else | |
| if (!newpreload || setenv("LD_PRELOAD", newpreload, 1) == -1) { | |
| fprintf(stderr, "warning: failed set LD_PRELOAD " | |
| "for ssh nodelay workaround\n"); | |
| } | |
| #endif | |
| g_free(newpreload); | |
| return 0; | |
| } | |
| #endif | |
| static int pty_expect_loop(void) | |
| { | |
| int res; | |
| char buf[256]; | |
| const char *passwd_str = "assword:"; | |
| int timeout = 60 * 1000; /* 1min timeout for the prompt to appear */ | |
| int passwd_len = strlen(passwd_str); | |
| int len = 0; | |
| char c; | |
| while (1) { | |
| struct pollfd fds[2]; | |
| fds[0].fd = sshfs.rfd; | |
| fds[0].events = POLLIN; | |
| fds[1].fd = sshfs.ptyfd; | |
| fds[1].events = POLLIN; | |
| res = poll(fds, 2, timeout); | |
| if (res == -1) { | |
| perror("poll"); | |
| return -1; | |
| } | |
| if (res == 0) { | |
| fprintf(stderr, "Timeout waiting for prompt\n"); | |
| return -1; | |
| } | |
| if (fds[0].revents) { | |
| /* | |
| * Something happened on stdout of ssh, this | |
| * either means, that we are connected, or | |
| * that we are disconnected. In any case the | |
| * password doesn't matter any more. | |
| */ | |
| break; | |
| } | |
| res = read(sshfs.ptyfd, &c, 1); | |
| if (res == -1) { | |
| perror("read"); | |
| return -1; | |
| } | |
| if (res == 0) { | |
| fprintf(stderr, "EOF while waiting for prompt\n"); | |
| return -1; | |
| } | |
| buf[len] = c; | |
| len++; | |
| if (len == passwd_len) { | |
| if (memcmp(buf, passwd_str, passwd_len) == 0) { | |
| write(sshfs.ptyfd, sshfs.password, | |
| strlen(sshfs.password)); | |
| } | |
| memmove(buf, buf + 1, passwd_len - 1); | |
| len--; | |
| } | |
| } | |
| if (!sshfs.reconnect) { | |
| size_t size = getpagesize(); | |
| memset(sshfs.password, 0, size); | |
| munmap(sshfs.password, size); | |
| sshfs.password = NULL; | |
| } | |
| return 0; | |
| } | |
| static int pty_master(char **name) | |
| { | |
| int mfd; | |
| mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); | |
| if (mfd == -1) { | |
| perror("failed to open pty"); | |
| return -1; | |
| } | |
| if (grantpt(mfd) != 0) { | |
| perror("grantpt"); | |
| return -1; | |
| } | |
| if (unlockpt(mfd) != 0) { | |
| perror("unlockpt"); | |
| return -1; | |
| } | |
| *name = ptsname(mfd); | |
| return mfd; | |
| } | |
| static void replace_arg(char **argp, const char *newarg) | |
| { | |
| free(*argp); | |
| *argp = strdup(newarg); | |
| if (*argp == NULL) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| } | |
| static int start_ssh(void) | |
| { | |
| char *ptyname = NULL; | |
| int sockpair[2]; | |
| int pid; | |
| if (sshfs.password_stdin) { | |
| sshfs.ptyfd = pty_master(&ptyname); | |
| if (sshfs.ptyfd == -1) | |
| return -1; | |
| sshfs.ptyslavefd = open(ptyname, O_RDWR | O_NOCTTY); | |
| if (sshfs.ptyslavefd == -1) | |
| return -1; | |
| } | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1) { | |
| perror("failed to create socket pair"); | |
| return -1; | |
| } | |
| sshfs.rfd = sockpair[0]; | |
| sshfs.wfd = sockpair[0]; | |
| pid = fork(); | |
| if (pid == -1) { | |
| perror("failed to fork"); | |
| close(sockpair[1]); | |
| return -1; | |
| } else if (pid == 0) { | |
| int devnull; | |
| #ifdef SSH_NODELAY_WORKAROUND | |
| if (sshfs.nodelay_workaround && | |
| do_ssh_nodelay_workaround() == -1) { | |
| fprintf(stderr, | |
| "warning: ssh nodelay workaround disabled\n"); | |
| } | |
| #endif | |
| if (sshfs.nodelaysrv_workaround) { | |
| int i; | |
| /* | |
| * Hack to work around missing TCP_NODELAY | |
| * setting in sshd | |
| */ | |
| for (i = 1; i < sshfs.ssh_args.argc; i++) { | |
| if (strcmp(sshfs.ssh_args.argv[i], "-x") == 0) { | |
| replace_arg(&sshfs.ssh_args.argv[i], | |
| "-X"); | |
| break; | |
| } | |
| } | |
| } | |
| devnull = open("/dev/null", O_WRONLY); | |
| if (dup2(sockpair[1], 0) == -1 || dup2(sockpair[1], 1) == -1) { | |
| perror("failed to redirect input/output"); | |
| _exit(1); | |
| } | |
| if (!sshfs.foreground && devnull != -1) | |
| dup2(devnull, 2); | |
| close(devnull); | |
| close(sockpair[0]); | |
| close(sockpair[1]); | |
| switch (fork()) { | |
| case -1: | |
| perror("failed to fork"); | |
| _exit(1); | |
| case 0: | |
| break; | |
| default: | |
| _exit(0); | |
| } | |
| chdir("/"); | |
| if (sshfs.password_stdin) { | |
| int sfd; | |
| setsid(); | |
| sfd = open(ptyname, O_RDWR); | |
| if (sfd == -1) { | |
| perror(ptyname); | |
| _exit(1); | |
| } | |
| close(sfd); | |
| close(sshfs.ptyslavefd); | |
| close(sshfs.ptyfd); | |
| } | |
| if (sshfs.debug) { | |
| int i; | |
| fprintf(stderr, "executing"); | |
| for (i = 0; i < sshfs.ssh_args.argc; i++) | |
| fprintf(stderr, " <%s>", | |
| sshfs.ssh_args.argv[i]); | |
| fprintf(stderr, "\n"); | |
| } | |
| execvp(sshfs.ssh_args.argv[0], sshfs.ssh_args.argv); | |
| fprintf(stderr, "failed to execute '%s': %s\n", | |
| sshfs.ssh_args.argv[0], strerror(errno)); | |
| _exit(1); | |
| } | |
| waitpid(pid, NULL, 0); | |
| close(sockpair[1]); | |
| return 0; | |
| } | |
| static int connect_slave() | |
| { | |
| sshfs.rfd = STDIN_FILENO; | |
| sshfs.wfd = STDOUT_FILENO; | |
| return 0; | |
| } | |
| static int connect_to(char *host, char *port) | |
| { | |
| int err; | |
| int sock; | |
| int opt; | |
| struct addrinfo *ai; | |
| struct addrinfo hint; | |
| memset(&hint, 0, sizeof(hint)); | |
| hint.ai_family = PF_INET; | |
| hint.ai_socktype = SOCK_STREAM; | |
| err = getaddrinfo(host, port, &hint, &ai); | |
| if (err) { | |
| fprintf(stderr, "failed to resolve %s:%s: %s\n", host, port, | |
| gai_strerror(err)); | |
| return -1; | |
| } | |
| sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); | |
| if (sock == -1) { | |
| perror("failed to create socket"); | |
| return -1; | |
| } | |
| err = connect(sock, ai->ai_addr, ai->ai_addrlen); | |
| if (err == -1) { | |
| perror("failed to connect"); | |
| return -1; | |
| } | |
| opt = 1; | |
| err = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); | |
| if (err == -1) | |
| perror("warning: failed to set TCP_NODELAY"); | |
| freeaddrinfo(ai); | |
| sshfs.rfd = sock; | |
| sshfs.wfd = sock; | |
| return 0; | |
| } | |
| static int do_write(struct iovec *iov, size_t count) | |
| { | |
| int res; | |
| while (count) { | |
| res = writev(sshfs.wfd, iov, count); | |
| if (res == -1) { | |
| perror("write"); | |
| return -1; | |
| } else if (res == 0) { | |
| fprintf(stderr, "zero write\n"); | |
| return -1; | |
| } | |
| do { | |
| if ((unsigned) res < iov->iov_len) { | |
| iov->iov_len -= res; | |
| iov->iov_base += res; | |
| break; | |
| } else { | |
| res -= iov->iov_len; | |
| count --; | |
| iov ++; | |
| } | |
| } while(count); | |
| } | |
| return 0; | |
| } | |
| static uint32_t sftp_get_id(void) | |
| { | |
| static uint32_t idctr; | |
| return idctr++; | |
| } | |
| static void buf_to_iov(const struct buffer *buf, struct iovec *iov) | |
| { | |
| iov->iov_base = buf->p; | |
| iov->iov_len = buf->len; | |
| } | |
| static size_t iov_length(const struct iovec *iov, unsigned long nr_segs) | |
| { | |
| unsigned long seg; | |
| size_t ret = 0; | |
| for (seg = 0; seg < nr_segs; seg++) | |
| ret += iov[seg].iov_len; | |
| return ret; | |
| } | |
| #define SFTP_MAX_IOV 3 | |
| static int sftp_send_iov(uint8_t type, uint32_t id, struct iovec iov[], | |
| size_t count) | |
| { | |
| int res; | |
| struct buffer buf; | |
| struct iovec iovout[SFTP_MAX_IOV]; | |
| unsigned i; | |
| unsigned nout = 0; | |
| assert(count <= SFTP_MAX_IOV - 1); | |
| buf_init(&buf, 9); | |
| buf_add_uint32(&buf, iov_length(iov, count) + 5); | |
| buf_add_uint8(&buf, type); | |
| buf_add_uint32(&buf, id); | |
| buf_to_iov(&buf, &iovout[nout++]); | |
| for (i = 0; i < count; i++) | |
| iovout[nout++] = iov[i]; | |
| pthread_mutex_lock(&sshfs.lock_write); | |
| res = do_write(iovout, nout); | |
| pthread_mutex_unlock(&sshfs.lock_write); | |
| buf_free(&buf); | |
| return res; | |
| } | |
| static int do_read(struct buffer *buf) | |
| { | |
| int res; | |
| uint8_t *p = buf->p; | |
| size_t size = buf->size; | |
| while (size) { | |
| res = read(sshfs.rfd, p, size); | |
| if (res == -1) { | |
| perror("read"); | |
| return -1; | |
| } else if (res == 0) { | |
| fprintf(stderr, "remote host has disconnected\n"); | |
| return -1; | |
| } | |
| size -= res; | |
| p += res; | |
| } | |
| return 0; | |
| } | |
| static int sftp_read(uint8_t *type, struct buffer *buf) | |
| { | |
| int res; | |
| struct buffer buf2; | |
| uint32_t len; | |
| buf_init(&buf2, 5); | |
| res = do_read(&buf2); | |
| if (res != -1) { | |
| if (buf_get_uint32(&buf2, &len) == -1) | |
| return -1; | |
| if (len > MAX_REPLY_LEN) { | |
| fprintf(stderr, "reply len too large: %u\n", len); | |
| return -1; | |
| } | |
| if (buf_get_uint8(&buf2, type) == -1) | |
| return -1; | |
| buf_init(buf, len - 1); | |
| res = do_read(buf); | |
| } | |
| buf_free(&buf2); | |
| return res; | |
| } | |
| static void request_free(struct request *req) | |
| { | |
| buf_free(&req->reply); | |
| sem_destroy(&req->ready); | |
| g_free(req); | |
| } | |
| static void chunk_free(struct read_chunk *chunk) | |
| { | |
| while (!list_empty(&chunk->reqs)) { | |
| struct read_req *rreq; | |
| rreq = list_entry(chunk->reqs.prev, struct read_req, list); | |
| list_del(&rreq->list); | |
| buf_free(&rreq->data); | |
| g_free(rreq); | |
| } | |
| g_free(chunk); | |
| } | |
| static void chunk_put(struct read_chunk *chunk) | |
| { | |
| if (chunk) { | |
| chunk->refs--; | |
| if (!chunk->refs) | |
| chunk_free(chunk); | |
| } | |
| } | |
| static void chunk_put_locked(struct read_chunk *chunk) | |
| { | |
| pthread_mutex_lock(&sshfs.lock); | |
| chunk_put(chunk); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| } | |
| static int clean_req(void *key_, struct request *req) | |
| { | |
| (void) key_; | |
| req->error = -EIO; | |
| if (req->want_reply) | |
| sem_post(&req->ready); | |
| else { | |
| if (req->end_func) | |
| req->end_func(req); | |
| request_free(req); | |
| } | |
| return TRUE; | |
| } | |
| static int process_one_request(void) | |
| { | |
| int res; | |
| struct buffer buf; | |
| uint8_t type; | |
| struct request *req; | |
| uint32_t id; | |
| buf_init(&buf, 0); | |
| res = sftp_read(&type, &buf); | |
| if (res == -1) | |
| return -1; | |
| if (buf_get_uint32(&buf, &id) == -1) | |
| return -1; | |
| pthread_mutex_lock(&sshfs.lock); | |
| req = (struct request *) | |
| g_hash_table_lookup(sshfs.reqtab, GUINT_TO_POINTER(id)); | |
| if (req == NULL) | |
| fprintf(stderr, "request %i not found\n", id); | |
| else { | |
| int was_over; | |
| was_over = sshfs.outstanding_len > sshfs.max_outstanding_len; | |
| sshfs.outstanding_len -= req->len; | |
| if (was_over && | |
| sshfs.outstanding_len <= sshfs.max_outstanding_len) { | |
| pthread_cond_broadcast(&sshfs.outstanding_cond); | |
| } | |
| g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); | |
| } | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (req != NULL) { | |
| if (sshfs.debug) { | |
| struct timeval now; | |
| unsigned int difftime; | |
| unsigned msgsize = buf.size + 5; | |
| gettimeofday(&now, NULL); | |
| difftime = (now.tv_sec - req->start.tv_sec) * 1000; | |
| difftime += (now.tv_usec - req->start.tv_usec) / 1000; | |
| DEBUG(" [%05i] %14s %8ubytes (%ims)\n", id, | |
| type_name(type), msgsize, difftime); | |
| if (difftime < sshfs.min_rtt || !sshfs.num_received) | |
| sshfs.min_rtt = difftime; | |
| if (difftime > sshfs.max_rtt) | |
| sshfs.max_rtt = difftime; | |
| sshfs.total_rtt += difftime; | |
| sshfs.num_received++; | |
| sshfs.bytes_received += msgsize; | |
| } | |
| req->reply = buf; | |
| req->reply_type = type; | |
| req->replied = 1; | |
| if (req->want_reply) | |
| sem_post(&req->ready); | |
| else { | |
| if (req->end_func) { | |
| pthread_mutex_lock(&sshfs.lock); | |
| req->end_func(req); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| } | |
| request_free(req); | |
| } | |
| } else | |
| buf_free(&buf); | |
| return 0; | |
| } | |
| static void close_conn(void) | |
| { | |
| close(sshfs.rfd); | |
| if (sshfs.rfd != sshfs.wfd) | |
| close(sshfs.wfd); | |
| sshfs.rfd = -1; | |
| sshfs.wfd = -1; | |
| if (sshfs.ptyfd != -1) { | |
| close(sshfs.ptyfd); | |
| sshfs.ptyfd = -1; | |
| } | |
| if (sshfs.ptyslavefd != -1) { | |
| close(sshfs.ptyslavefd); | |
| sshfs.ptyslavefd = -1; | |
| } | |
| } | |
| static void *process_requests(void *data_) | |
| { | |
| (void) data_; | |
| while (1) { | |
| if (process_one_request() == -1) | |
| break; | |
| } | |
| pthread_mutex_lock(&sshfs.lock); | |
| sshfs.processing_thread_started = 0; | |
| close_conn(); | |
| g_hash_table_foreach_remove(sshfs.reqtab, (GHRFunc) clean_req, NULL); | |
| sshfs.connver ++; | |
| sshfs.outstanding_len = 0; | |
| pthread_cond_broadcast(&sshfs.outstanding_cond); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (!sshfs.reconnect) { | |
| /* harakiri */ | |
| kill(getpid(), SIGTERM); | |
| } | |
| return NULL; | |
| } | |
| static int sftp_init_reply_ok(struct buffer *buf, uint32_t *version) | |
| { | |
| uint32_t len; | |
| uint8_t type; | |
| if (buf_get_uint32(buf, &len) == -1) | |
| return -1; | |
| if (len < 5 || len > MAX_REPLY_LEN) | |
| return 1; | |
| if (buf_get_uint8(buf, &type) == -1) | |
| return -1; | |
| if (type != SSH_FXP_VERSION) | |
| return 1; | |
| if (buf_get_uint32(buf, version) == -1) | |
| return -1; | |
| DEBUG("Server version: %u\n", *version); | |
| if (len > 5) { | |
| struct buffer buf2; | |
| buf_init(&buf2, len - 5); | |
| if (do_read(&buf2) == -1) { | |
| buf_free(&buf2); | |
| return -1; | |
| } | |
| do { | |
| char *ext; | |
| char *extdata; | |
| if (buf_get_string(&buf2, &ext) == -1 || | |
| buf_get_string(&buf2, &extdata) == -1) { | |
| buf_free(&buf2); | |
| return -1; | |
| } | |
| DEBUG("Extension: %s <%s>\n", ext, extdata); | |
| if (strcmp(ext, SFTP_EXT_POSIX_RENAME) == 0 && | |
| strcmp(extdata, "1") == 0) { | |
| sshfs.ext_posix_rename = 1; | |
| sshfs.rename_workaround = 0; | |
| } | |
| if (strcmp(ext, SFTP_EXT_STATVFS) == 0 && | |
| strcmp(extdata, "2") == 0) | |
| sshfs.ext_statvfs = 1; | |
| if (strcmp(ext, SFTP_EXT_HARDLINK) == 0 && | |
| strcmp(extdata, "1") == 0) | |
| sshfs.ext_hardlink = 1; | |
| } while (buf2.len < buf2.size); | |
| buf_free(&buf2); | |
| } | |
| return 0; | |
| } | |
| static int sftp_find_init_reply(uint32_t *version) | |
| { | |
| int res; | |
| struct buffer buf; | |
| buf_init(&buf, 9); | |
| res = do_read(&buf); | |
| while (res != -1) { | |
| struct buffer buf2; | |
| res = sftp_init_reply_ok(&buf, version); | |
| if (res <= 0) | |
| break; | |
| /* Iterate over any rubbish until the version reply is found */ | |
| DEBUG("%c", *buf.p); | |
| memmove(buf.p, buf.p + 1, buf.size - 1); | |
| buf.len = 0; | |
| buf2.p = buf.p + buf.size - 1; | |
| buf2.size = 1; | |
| res = do_read(&buf2); | |
| } | |
| buf_free(&buf); | |
| return res; | |
| } | |
| static int sftp_init() | |
| { | |
| int res = -1; | |
| uint32_t version = 0; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| if (sftp_send_iov(SSH_FXP_INIT, PROTO_VERSION, NULL, 0) == -1) | |
| goto out; | |
| if (sshfs.password_stdin && pty_expect_loop() == -1) | |
| goto out; | |
| if (sftp_find_init_reply(&version) == -1) | |
| goto out; | |
| sshfs.server_version = version; | |
| if (version > PROTO_VERSION) { | |
| fprintf(stderr, | |
| "Warning: server uses version: %i, we support: %i\n", | |
| version, PROTO_VERSION); | |
| } | |
| res = 0; | |
| out: | |
| buf_free(&buf); | |
| return res; | |
| } | |
| static int sftp_error_to_errno(uint32_t error) | |
| { | |
| switch (error) { | |
| case SSH_FX_OK: return 0; | |
| case SSH_FX_NO_SUCH_FILE: return ENOENT; | |
| case SSH_FX_PERMISSION_DENIED: return EACCES; | |
| case SSH_FX_FAILURE: return EPERM; | |
| case SSH_FX_BAD_MESSAGE: return EBADMSG; | |
| case SSH_FX_NO_CONNECTION: return ENOTCONN; | |
| case SSH_FX_CONNECTION_LOST: return ECONNABORTED; | |
| case SSH_FX_OP_UNSUPPORTED: return EOPNOTSUPP; | |
| default: return EIO; | |
| } | |
| } | |
| static void sftp_detect_uid() | |
| { | |
| int flags; | |
| uint32_t id = sftp_get_id(); | |
| uint32_t replid; | |
| uint8_t type; | |
| struct buffer buf; | |
| struct stat stbuf; | |
| struct iovec iov[1]; | |
| buf_init(&buf, 5); | |
| buf_add_string(&buf, "."); | |
| buf_to_iov(&buf, &iov[0]); | |
| if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1) | |
| goto out; | |
| buf_clear(&buf); | |
| if (sftp_read(&type, &buf) == -1) | |
| goto out; | |
| if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { | |
| fprintf(stderr, "protocol error\n"); | |
| goto out; | |
| } | |
| if (buf_get_uint32(&buf, &replid) == -1) | |
| goto out; | |
| if (replid != id) { | |
| fprintf(stderr, "bad reply ID\n"); | |
| goto out; | |
| } | |
| if (type == SSH_FXP_STATUS) { | |
| uint32_t serr; | |
| if (buf_get_uint32(&buf, &serr) == -1) | |
| goto out; | |
| fprintf(stderr, "failed to stat home directory (%i)\n", serr); | |
| goto out; | |
| } | |
| if (buf_get_attrs(&buf, &stbuf, &flags) != 0) | |
| goto out; | |
| if (!(flags & SSH_FILEXFER_ATTR_UIDGID)) | |
| goto out; | |
| sshfs.remote_uid = stbuf.st_uid; | |
| sshfs.local_uid = getuid(); | |
| #ifdef __APPLE__ | |
| sshfs.remote_gid = stbuf.st_gid; | |
| sshfs.local_gid = getgid(); | |
| #endif | |
| sshfs.remote_uid_detected = 1; | |
| DEBUG("remote_uid = %i\n", sshfs.remote_uid); | |
| out: | |
| if (!sshfs.remote_uid_detected) | |
| fprintf(stderr, "failed to detect remote user ID\n"); | |
| buf_free(&buf); | |
| } | |
| static int sftp_check_root(const char *base_path) | |
| { | |
| int flags; | |
| uint32_t id = sftp_get_id(); | |
| uint32_t replid; | |
| uint8_t type; | |
| struct buffer buf; | |
| struct stat stbuf; | |
| struct iovec iov[1]; | |
| int err = -1; | |
| const char *remote_dir = base_path[0] ? base_path : "."; | |
| buf_init(&buf, 0); | |
| buf_add_string(&buf, remote_dir); | |
| buf_to_iov(&buf, &iov[0]); | |
| if (sftp_send_iov(SSH_FXP_STAT, id, iov, 1) == -1) | |
| goto out; | |
| buf_clear(&buf); | |
| if (sftp_read(&type, &buf) == -1) | |
| goto out; | |
| if (type != SSH_FXP_ATTRS && type != SSH_FXP_STATUS) { | |
| fprintf(stderr, "protocol error\n"); | |
| goto out; | |
| } | |
| if (buf_get_uint32(&buf, &replid) == -1) | |
| goto out; | |
| if (replid != id) { | |
| fprintf(stderr, "bad reply ID\n"); | |
| goto out; | |
| } | |
| if (type == SSH_FXP_STATUS) { | |
| uint32_t serr; | |
| if (buf_get_uint32(&buf, &serr) == -1) | |
| goto out; | |
| fprintf(stderr, "%s:%s: %s\n", sshfs.host, remote_dir, | |
| strerror(sftp_error_to_errno(serr))); | |
| goto out; | |
| } | |
| int err2 = buf_get_attrs(&buf, &stbuf, &flags); | |
| if (err2) { | |
| err = err2; | |
| goto out; | |
| } | |
| if (!(flags & SSH_FILEXFER_ATTR_PERMISSIONS)) | |
| goto out; | |
| if (S_ISDIR(sshfs.mnt_mode) && !S_ISDIR(stbuf.st_mode)) { | |
| fprintf(stderr, "%s:%s: Not a directory\n", sshfs.host, | |
| remote_dir); | |
| goto out; | |
| } | |
| if ((sshfs.mnt_mode ^ stbuf.st_mode) & S_IFMT) { | |
| fprintf(stderr, "%s:%s: type of file differs from mountpoint\n", | |
| sshfs.host, remote_dir); | |
| goto out; | |
| } | |
| err = 0; | |
| out: | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int connect_remote(void) | |
| { | |
| int err; | |
| if (sshfs.slave) | |
| err = connect_slave(); | |
| else if (sshfs.directport) | |
| err = connect_to(sshfs.host, sshfs.directport); | |
| else | |
| err = start_ssh(); | |
| if (!err) | |
| err = sftp_init(); | |
| if (err) | |
| close_conn(); | |
| else | |
| sshfs.num_connect++; | |
| return err; | |
| } | |
| static int start_processing_thread(void) | |
| { | |
| int err; | |
| pthread_t thread_id; | |
| sigset_t oldset; | |
| sigset_t newset; | |
| if (sshfs.processing_thread_started) | |
| return 0; | |
| if (sshfs.rfd == -1) { | |
| err = connect_remote(); | |
| if (err) | |
| return -EIO; | |
| } | |
| if (sshfs.detect_uid) { | |
| sftp_detect_uid(); | |
| sshfs.detect_uid = 0; | |
| } | |
| sigemptyset(&newset); | |
| sigaddset(&newset, SIGTERM); | |
| sigaddset(&newset, SIGINT); | |
| sigaddset(&newset, SIGHUP); | |
| sigaddset(&newset, SIGQUIT); | |
| pthread_sigmask(SIG_BLOCK, &newset, &oldset); | |
| err = pthread_create(&thread_id, NULL, process_requests, NULL); | |
| if (err) { | |
| fprintf(stderr, "failed to create thread: %s\n", strerror(err)); | |
| return -EIO; | |
| } | |
| pthread_detach(thread_id); | |
| pthread_sigmask(SIG_SETMASK, &oldset, NULL); | |
| sshfs.processing_thread_started = 1; | |
| return 0; | |
| } | |
| #if FUSE_VERSION >= 26 | |
| static void *sshfs_init(struct fuse_conn_info *conn) | |
| #else | |
| static void *sshfs_init(void) | |
| #endif | |
| { | |
| #if FUSE_VERSION >= 26 | |
| /* Readahead should be done by kernel or sshfs but not both */ | |
| if (conn->async_read) | |
| sshfs.sync_read = 1; | |
| #endif | |
| if (!sshfs.delay_connect) | |
| start_processing_thread(); | |
| return NULL; | |
| } | |
| static int sftp_request_wait(struct request *req, uint8_t type, | |
| uint8_t expect_type, struct buffer *outbuf) | |
| { | |
| int err; | |
| if (req->error) { | |
| err = req->error; | |
| goto out; | |
| } | |
| while (sem_wait(&req->ready)); | |
| if (req->error) { | |
| err = req->error; | |
| goto out; | |
| } | |
| err = -EIO; | |
| if (req->reply_type != expect_type && | |
| req->reply_type != SSH_FXP_STATUS) { | |
| fprintf(stderr, "protocol error\n"); | |
| goto out; | |
| } | |
| if (req->reply_type == SSH_FXP_STATUS) { | |
| uint32_t serr; | |
| if (buf_get_uint32(&req->reply, &serr) == -1) | |
| goto out; | |
| switch (serr) { | |
| case SSH_FX_OK: | |
| if (expect_type == SSH_FXP_STATUS) | |
| err = 0; | |
| else | |
| err = -EIO; | |
| break; | |
| case SSH_FX_EOF: | |
| if (type == SSH_FXP_READ || type == SSH_FXP_READDIR) | |
| err = MY_EOF; | |
| else | |
| err = -EIO; | |
| break; | |
| default: | |
| err = -sftp_error_to_errno(serr); | |
| } | |
| } else { | |
| buf_init(outbuf, req->reply.size - req->reply.len); | |
| buf_get_mem(&req->reply, outbuf->p, outbuf->size); | |
| err = 0; | |
| } | |
| out: | |
| if (req->end_func) { | |
| pthread_mutex_lock(&sshfs.lock); | |
| req->end_func(req); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| } | |
| request_free(req); | |
| return err; | |
| } | |
| static int sftp_request_send(uint8_t type, struct iovec *iov, size_t count, | |
| request_func begin_func, request_func end_func, | |
| int want_reply, void *data, | |
| struct request **reqp) | |
| { | |
| int err; | |
| uint32_t id; | |
| struct request *req = g_new0(struct request, 1); | |
| req->want_reply = want_reply; | |
| req->end_func = end_func; | |
| req->data = data; | |
| sem_init(&req->ready, 0, 0); | |
| buf_init(&req->reply, 0); | |
| pthread_mutex_lock(&sshfs.lock); | |
| if (begin_func) | |
| begin_func(req); | |
| id = sftp_get_id(); | |
| err = start_processing_thread(); | |
| if (err) { | |
| pthread_mutex_unlock(&sshfs.lock); | |
| goto out; | |
| } | |
| req->len = iov_length(iov, count) + 9; | |
| sshfs.outstanding_len += req->len; | |
| while (sshfs.outstanding_len > sshfs.max_outstanding_len) | |
| pthread_cond_wait(&sshfs.outstanding_cond, &sshfs.lock); | |
| g_hash_table_insert(sshfs.reqtab, GUINT_TO_POINTER(id), req); | |
| if (sshfs.debug) { | |
| gettimeofday(&req->start, NULL); | |
| sshfs.num_sent++; | |
| sshfs.bytes_sent += req->len; | |
| } | |
| DEBUG("[%05i] %s\n", id, type_name(type)); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| err = -EIO; | |
| if (sftp_send_iov(type, id, iov, count) == -1) { | |
| gboolean rmed; | |
| pthread_mutex_lock(&sshfs.lock); | |
| rmed = g_hash_table_remove(sshfs.reqtab, GUINT_TO_POINTER(id)); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (!rmed && !want_reply) { | |
| /* request already freed */ | |
| return err; | |
| } | |
| goto out; | |
| } | |
| if (want_reply) | |
| *reqp = req; | |
| return 0; | |
| out: | |
| req->error = err; | |
| if (!want_reply) | |
| sftp_request_wait(req, type, 0, NULL); | |
| else | |
| *reqp = req; | |
| return err; | |
| } | |
| static int sftp_request_iov(uint8_t type, struct iovec *iov, size_t count, | |
| uint8_t expect_type, struct buffer *outbuf) | |
| { | |
| int err; | |
| struct request *req; | |
| err = sftp_request_send(type, iov, count, NULL, NULL, expect_type, NULL, | |
| &req); | |
| if (expect_type == 0) | |
| return err; | |
| return sftp_request_wait(req, type, expect_type, outbuf); | |
| } | |
| static int sftp_request(uint8_t type, const struct buffer *buf, | |
| uint8_t expect_type, struct buffer *outbuf) | |
| { | |
| struct iovec iov; | |
| buf_to_iov(buf, &iov); | |
| return sftp_request_iov(type, &iov, 1, expect_type, outbuf); | |
| } | |
| static int sshfs_getattr(const char *path, struct stat *stbuf) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer outbuf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT, | |
| &buf, SSH_FXP_ATTRS, &outbuf); | |
| if (!err) { | |
| err = buf_get_attrs(&outbuf, stbuf, NULL); | |
| buf_free(&outbuf); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int count_components(const char *p) | |
| { | |
| int ctr; | |
| for (; *p == '/'; p++); | |
| for (ctr = 0; *p; ctr++) { | |
| for (; *p && *p != '/'; p++); | |
| for (; *p == '/'; p++); | |
| } | |
| return ctr; | |
| } | |
| static void strip_common(const char **sp, const char **tp) | |
| { | |
| const char *s = *sp; | |
| const char *t = *tp; | |
| do { | |
| for (; *s == '/'; s++); | |
| for (; *t == '/'; t++); | |
| *tp = t; | |
| *sp = s; | |
| for (; *s == *t && *s && *s != '/'; s++, t++); | |
| } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); | |
| } | |
| static void transform_symlink(const char *path, char **linkp) | |
| { | |
| const char *l = *linkp; | |
| const char *b = sshfs.base_path; | |
| char *newlink; | |
| char *s; | |
| int dotdots; | |
| int i; | |
| if (l[0] != '/' || b[0] != '/') | |
| return; | |
| strip_common(&l, &b); | |
| if (*b) | |
| return; | |
| strip_common(&l, &path); | |
| dotdots = count_components(path); | |
| if (!dotdots) | |
| return; | |
| dotdots--; | |
| newlink = malloc(dotdots * 3 + strlen(l) + 2); | |
| if (!newlink) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| for (s = newlink, i = 0; i < dotdots; i++, s += 3) | |
| strcpy(s, "../"); | |
| if (l[0]) | |
| strcpy(s, l); | |
| else if (!dotdots) | |
| strcpy(s, "."); | |
| else | |
| s[0] = '\0'; | |
| free(*linkp); | |
| *linkp = newlink; | |
| } | |
| static int sshfs_readlink(const char *path, char *linkbuf, size_t size) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer name; | |
| assert(size > 0); | |
| if (sshfs.server_version < 3) | |
| return -EPERM; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(SSH_FXP_READLINK, &buf, SSH_FXP_NAME, &name); | |
| if (!err) { | |
| uint32_t count; | |
| char *link; | |
| err = -EIO; | |
| if(buf_get_uint32(&name, &count) != -1 && count == 1 && | |
| buf_get_string(&name, &link) != -1) { | |
| if (sshfs.transform_symlinks) | |
| transform_symlink(path, &link); | |
| strncpy(linkbuf, link, size - 1); | |
| linkbuf[size - 1] = '\0'; | |
| free(link); | |
| err = 0; | |
| } | |
| buf_free(&name); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_getdir(const char *path, fuse_cache_dirh_t h, | |
| fuse_cache_dirfil_t filler) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer handle; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(SSH_FXP_OPENDIR, &buf, SSH_FXP_HANDLE, &handle); | |
| if (!err) { | |
| int err2; | |
| buf_finish(&handle); | |
| do { | |
| struct buffer name; | |
| err = sftp_request(SSH_FXP_READDIR, &handle, SSH_FXP_NAME, &name); | |
| if (!err) { | |
| err = buf_get_entries(&name, h, filler); | |
| buf_free(&name); | |
| } | |
| } while (!err); | |
| if (err == MY_EOF) | |
| err = 0; | |
| err2 = sftp_request(SSH_FXP_CLOSE, &handle, 0, NULL); | |
| if (!err) | |
| err = err2; | |
| buf_free(&handle); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_mkdir(const char *path, mode_t mode) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); | |
| buf_add_uint32(&buf, mode); | |
| err = sftp_request(SSH_FXP_MKDIR, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_mknod(const char *path, mode_t mode, dev_t rdev) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer handle; | |
| (void) rdev; | |
| if ((mode & S_IFMT) != S_IFREG) | |
| return -EPERM; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_EXCL); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); | |
| buf_add_uint32(&buf, mode); | |
| err = sftp_request(SSH_FXP_OPEN, &buf, SSH_FXP_HANDLE, &handle); | |
| if (!err) { | |
| int err2; | |
| buf_finish(&handle); | |
| err2 = sftp_request(SSH_FXP_CLOSE, &handle, SSH_FXP_STATUS, | |
| NULL); | |
| if (!err) | |
| err = err2; | |
| buf_free(&handle); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_symlink(const char *from, const char *to) | |
| { | |
| int err; | |
| struct buffer buf; | |
| if (sshfs.server_version < 3) | |
| return -EPERM; | |
| /* openssh sftp server doesn't follow standard: link target and | |
| link name are mixed up, so we must also be non-standard :( */ | |
| buf_init(&buf, 0); | |
| buf_add_string(&buf, from); | |
| buf_add_path(&buf, to); | |
| err = sftp_request(SSH_FXP_SYMLINK, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_unlink(const char *path) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(SSH_FXP_REMOVE, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_rmdir(const char *path) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(SSH_FXP_RMDIR, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_do_rename(const char *from, const char *to) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, from); | |
| buf_add_path(&buf, to); | |
| err = sftp_request(SSH_FXP_RENAME, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_ext_posix_rename(const char *from, const char *to) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_string(&buf, SFTP_EXT_POSIX_RENAME); | |
| buf_add_path(&buf, from); | |
| buf_add_path(&buf, to); | |
| err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static void random_string(char *str, int length) | |
| { | |
| int i; | |
| for (i = 0; i < length; i++) | |
| *str++ = (char)('0' + rand_r(&sshfs.randseed) % 10); | |
| *str = '\0'; | |
| } | |
| static int sshfs_rename(const char *from, const char *to) | |
| { | |
| int err; | |
| if (sshfs.ext_posix_rename) | |
| err = sshfs_ext_posix_rename(from, to); | |
| else | |
| err = sshfs_do_rename(from, to); | |
| if (err == -EPERM && sshfs.rename_workaround) { | |
| size_t tolen = strlen(to); | |
| if (tolen + RENAME_TEMP_CHARS < PATH_MAX) { | |
| int tmperr; | |
| char totmp[PATH_MAX]; | |
| strcpy(totmp, to); | |
| random_string(totmp + tolen, RENAME_TEMP_CHARS); | |
| tmperr = sshfs_do_rename(to, totmp); | |
| if (!tmperr) { | |
| err = sshfs_do_rename(from, to); | |
| if (!err) | |
| err = sshfs_unlink(totmp); | |
| else | |
| sshfs_do_rename(totmp, to); | |
| } | |
| } | |
| } | |
| return err; | |
| } | |
| static int sshfs_link(const char *from, const char *to) | |
| { | |
| int err = -ENOSYS; | |
| if (sshfs.ext_hardlink) { | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_string(&buf, SFTP_EXT_HARDLINK); | |
| buf_add_path(&buf, from); | |
| buf_add_path(&buf, to); | |
| err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_STATUS, | |
| NULL); | |
| buf_free(&buf); | |
| } | |
| return err; | |
| } | |
| static int sshfs_chmod(const char *path, mode_t mode) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); | |
| buf_add_uint32(&buf, mode); | |
| /* FIXME: really needs LSETSTAT extension (debian Bug#640038) */ | |
| err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_chown(const char *path, uid_t uid, gid_t gid) | |
| { | |
| int err; | |
| struct buffer buf; | |
| if (sshfs.remote_uid_detected && uid == sshfs.local_uid) | |
| uid = sshfs.remote_uid; | |
| if (sshfs.idmap == IDMAP_FILE && sshfs.r_uid_map) | |
| if(translate_id(&uid, sshfs.r_uid_map) == -1) | |
| return -EPERM; | |
| if (sshfs.idmap == IDMAP_FILE && sshfs.r_gid_map) | |
| if (translate_id(&gid, sshfs.r_gid_map) == -1) | |
| return -EPERM; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_UIDGID); | |
| #ifdef __APPLE__ | |
| if (sshfs.remote_uid_detected) { | |
| if (uid == sshfs.local_uid) | |
| uid = sshfs.remote_uid; | |
| if (gid == sshfs.local_gid) | |
| gid = sshfs.remote_gid; | |
| } | |
| #endif | |
| buf_add_uint32(&buf, uid); | |
| buf_add_uint32(&buf, gid); | |
| err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_truncate_workaround(const char *path, off_t size, | |
| struct fuse_file_info *fi); | |
| static void sshfs_inc_modifver(void) | |
| { | |
| pthread_mutex_lock(&sshfs.lock); | |
| sshfs.modifver++; | |
| pthread_mutex_unlock(&sshfs.lock); | |
| } | |
| static int sshfs_truncate(const char *path, off_t size) | |
| { | |
| int err; | |
| struct buffer buf; | |
| sshfs_inc_modifver(); | |
| if (size == 0 || sshfs.truncate_workaround) | |
| return sshfs_truncate_workaround(path, size, NULL); | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); | |
| buf_add_uint64(&buf, size); | |
| err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_utime(const char *path, struct utimbuf *ubuf) | |
| { | |
| int err; | |
| struct buffer buf; | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_ACMODTIME); | |
| buf_add_uint32(&buf, ubuf->actime); | |
| buf_add_uint32(&buf, ubuf->modtime); | |
| err = sftp_request(SSH_FXP_SETSTAT, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static inline int sshfs_file_is_conn(struct sshfs_file *sf) | |
| { | |
| int ret; | |
| pthread_mutex_lock(&sshfs.lock); | |
| ret = (sf->connver == sshfs.connver); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| return ret; | |
| } | |
| static int sshfs_open_common(const char *path, mode_t mode, | |
| struct fuse_file_info *fi) | |
| { | |
| int err; | |
| int err2; | |
| struct buffer buf; | |
| struct buffer outbuf; | |
| struct stat stbuf; | |
| struct sshfs_file *sf; | |
| struct request *open_req; | |
| uint32_t pflags = 0; | |
| struct iovec iov; | |
| uint8_t type; | |
| uint64_t wrctr = cache_get_write_ctr(); | |
| if ((fi->flags & O_ACCMODE) == O_RDONLY) | |
| pflags = SSH_FXF_READ; | |
| else if((fi->flags & O_ACCMODE) == O_WRONLY) | |
| pflags = SSH_FXF_WRITE; | |
| else if ((fi->flags & O_ACCMODE) == O_RDWR) | |
| pflags = SSH_FXF_READ | SSH_FXF_WRITE; | |
| else | |
| return -EINVAL; | |
| if (fi->flags & O_CREAT) | |
| pflags |= SSH_FXF_CREAT; | |
| if (fi->flags & O_EXCL) | |
| pflags |= SSH_FXF_EXCL; | |
| if (fi->flags & O_TRUNC) | |
| pflags |= SSH_FXF_TRUNC; | |
| sf = g_new0(struct sshfs_file, 1); | |
| list_init(&sf->write_reqs); | |
| pthread_cond_init(&sf->write_finished, NULL); | |
| #ifdef __APPLE__ | |
| pthread_mutex_init(&sf->file_lock, NULL); | |
| #endif | |
| /* Assume random read after open */ | |
| sf->is_seq = 0; | |
| sf->refs = 1; | |
| sf->next_pos = 0; | |
| pthread_mutex_lock(&sshfs.lock); | |
| sf->modifver= sshfs.modifver; | |
| sf->connver = sshfs.connver; | |
| pthread_mutex_unlock(&sshfs.lock); | |
| buf_init(&buf, 0); | |
| buf_add_path(&buf, path); | |
| buf_add_uint32(&buf, pflags); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_PERMISSIONS); | |
| buf_add_uint32(&buf, mode); | |
| buf_to_iov(&buf, &iov); | |
| sftp_request_send(SSH_FXP_OPEN, &iov, 1, NULL, NULL, 1, NULL, | |
| &open_req); | |
| buf_clear(&buf); | |
| buf_add_path(&buf, path); | |
| type = sshfs.follow_symlinks ? SSH_FXP_STAT : SSH_FXP_LSTAT; | |
| err2 = sftp_request(type, &buf, SSH_FXP_ATTRS, &outbuf); | |
| if (!err2) { | |
| err2 = buf_get_attrs(&outbuf, &stbuf, NULL); | |
| buf_free(&outbuf); | |
| } | |
| err = sftp_request_wait(open_req, SSH_FXP_OPEN, SSH_FXP_HANDLE, | |
| &sf->handle); | |
| if (!err && err2) { | |
| buf_finish(&sf->handle); | |
| sftp_request(SSH_FXP_CLOSE, &sf->handle, 0, NULL); | |
| buf_free(&sf->handle); | |
| err = err2; | |
| } | |
| if (!err) { | |
| #ifdef __APPLE__ | |
| if (cache_enabled) | |
| cache_add_attr(path, &stbuf, wrctr); | |
| #else | |
| cache_add_attr(path, &stbuf, wrctr); | |
| #endif | |
| buf_finish(&sf->handle); | |
| fi->fh = (unsigned long) sf; | |
| } else { | |
| #ifdef __APPLE__ | |
| if (cache_enabled) | |
| cache_invalidate(path); | |
| #else | |
| cache_invalidate(path); | |
| #endif | |
| g_free(sf); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_open(const char *path, struct fuse_file_info *fi) | |
| { | |
| return sshfs_open_common(path, 0, fi); | |
| } | |
| static inline struct sshfs_file *get_sshfs_file(struct fuse_file_info *fi) | |
| { | |
| return (struct sshfs_file *) (uintptr_t) fi->fh; | |
| } | |
| static int sshfs_flush(const char *path, struct fuse_file_info *fi) | |
| { | |
| int err; | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| struct list_head write_reqs; | |
| struct list_head *curr_list; | |
| if (!sshfs_file_is_conn(sf)) | |
| return -EIO; | |
| if (sshfs.sync_write) | |
| return 0; | |
| (void) path; | |
| pthread_mutex_lock(&sshfs.lock); | |
| if (!list_empty(&sf->write_reqs)) { | |
| curr_list = sf->write_reqs.prev; | |
| list_del(&sf->write_reqs); | |
| list_init(&sf->write_reqs); | |
| list_add(&write_reqs, curr_list); | |
| while (!list_empty(&write_reqs)) | |
| pthread_cond_wait(&sf->write_finished, &sshfs.lock); | |
| } | |
| err = sf->write_error; | |
| sf->write_error = 0; | |
| pthread_mutex_unlock(&sshfs.lock); | |
| return err; | |
| } | |
| static int sshfs_fsync(const char *path, int isdatasync, | |
| struct fuse_file_info *fi) | |
| { | |
| (void) isdatasync; | |
| return sshfs_flush(path, fi); | |
| } | |
| static void sshfs_file_put(struct sshfs_file *sf) | |
| { | |
| #ifdef __APPLE__ | |
| pthread_mutex_lock(&sf->file_lock); | |
| #endif | |
| sf->refs--; | |
| #ifdef __APPLE__ | |
| if (!sf->refs) { | |
| pthread_mutex_unlock(&sf->file_lock); | |
| g_free(sf); | |
| } else { | |
| pthread_mutex_unlock(&sf->file_lock); | |
| } | |
| #else | |
| if (!sf->refs) | |
| g_free(sf); | |
| #endif | |
| } | |
| static void sshfs_file_get(struct sshfs_file *sf) | |
| { | |
| #ifdef __APPLE__ | |
| pthread_mutex_lock(&sf->file_lock); | |
| #endif | |
| sf->refs++; | |
| #ifdef __APPLE__ | |
| pthread_mutex_unlock(&sf->file_lock); | |
| #endif | |
| } | |
| static int sshfs_release(const char *path, struct fuse_file_info *fi) | |
| { | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| struct buffer *handle = &sf->handle; | |
| if (sshfs_file_is_conn(sf)) { | |
| sshfs_flush(path, fi); | |
| sftp_request(SSH_FXP_CLOSE, handle, 0, NULL); | |
| } | |
| buf_free(handle); | |
| chunk_put_locked(sf->readahead); | |
| sshfs_file_put(sf); | |
| return 0; | |
| } | |
| static void sshfs_read_end(struct request *req) | |
| { | |
| struct read_req *rreq = (struct read_req *) req->data; | |
| if (req->error) | |
| rreq->res = req->error; | |
| else if (req->replied) { | |
| rreq->res = -EIO; | |
| if (req->reply_type == SSH_FXP_STATUS) { | |
| uint32_t serr; | |
| if (buf_get_uint32(&req->reply, &serr) != -1) { | |
| if (serr == SSH_FX_EOF) | |
| rreq->res = 0; | |
| else | |
| rreq->res = -sftp_error_to_errno(serr); | |
| } | |
| } else if (req->reply_type == SSH_FXP_DATA) { | |
| uint32_t retsize; | |
| if (buf_get_uint32(&req->reply, &retsize) != -1) { | |
| if (retsize > rreq->size) { | |
| fprintf(stderr, "long read\n"); | |
| } else if (buf_check_get(&req->reply, retsize) != -1) { | |
| rreq->res = retsize; | |
| rreq->data = req->reply; | |
| buf_init(&req->reply, 0); | |
| } | |
| } | |
| } else { | |
| fprintf(stderr, "protocol error\n"); | |
| } | |
| } else { | |
| rreq->res = -EIO; | |
| } | |
| rreq->sio->num_reqs--; | |
| if (!rreq->sio->num_reqs) | |
| pthread_cond_broadcast(&rreq->sio->finished); | |
| } | |
| static void sshfs_read_begin(struct request *req) | |
| { | |
| struct read_req *rreq = (struct read_req *) req->data; | |
| rreq->sio->num_reqs++; | |
| } | |
| static struct read_chunk *sshfs_send_read(struct sshfs_file *sf, size_t size, | |
| off_t offset) | |
| { | |
| struct read_chunk *chunk = g_new0(struct read_chunk, 1); | |
| struct buffer *handle = &sf->handle; | |
| pthread_cond_init(&chunk->sio.finished, NULL); | |
| list_init(&chunk->reqs); | |
| chunk->size = size; | |
| chunk->offset = offset; | |
| chunk->refs = 1; | |
| while (size) { | |
| int err; | |
| struct buffer buf; | |
| struct iovec iov[1]; | |
| struct read_req *rreq; | |
| size_t bsize = size < sshfs.max_read ? size : sshfs.max_read; | |
| rreq = g_new0(struct read_req, 1); | |
| rreq->sio = &chunk->sio; | |
| rreq->size = bsize; | |
| buf_init(&rreq->data, 0); | |
| list_add(&rreq->list, &chunk->reqs); | |
| buf_init(&buf, 0); | |
| buf_add_buf(&buf, handle); | |
| buf_add_uint64(&buf, offset); | |
| buf_add_uint32(&buf, bsize); | |
| buf_to_iov(&buf, &iov[0]); | |
| err = sftp_request_send(SSH_FXP_READ, iov, 1, | |
| sshfs_read_begin, | |
| sshfs_read_end, | |
| 0, rreq, NULL); | |
| buf_free(&buf); | |
| if (err) | |
| break; | |
| size -= bsize; | |
| offset += bsize; | |
| } | |
| return chunk; | |
| } | |
| static int wait_chunk(struct read_chunk *chunk, char *buf, size_t size) | |
| { | |
| int res = 0; | |
| struct read_req *rreq; | |
| pthread_mutex_lock(&sshfs.lock); | |
| while (chunk->sio.num_reqs) | |
| pthread_cond_wait(&chunk->sio.finished, &sshfs.lock); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (chunk->sio.error) { | |
| if (chunk->sio.error != MY_EOF) | |
| res = chunk->sio.error; | |
| goto out; | |
| } | |
| while (!list_empty(&chunk->reqs) && size) { | |
| rreq = list_entry(chunk->reqs.prev, struct read_req, list); | |
| if (rreq->res < 0) { | |
| chunk->sio.error = rreq->res; | |
| break; | |
| } if (rreq->res == 0) { | |
| chunk->sio.error = MY_EOF; | |
| break; | |
| } else if (size < (size_t) rreq->res) { | |
| buf_get_mem(&rreq->data, buf, size); | |
| rreq->res -= size; | |
| rreq->size -= size; | |
| res += size; | |
| break; | |
| } else { | |
| buf_get_mem(&rreq->data, buf, rreq->res); | |
| res += rreq->res; | |
| if ((size_t) rreq->res < rreq->size) { | |
| chunk->sio.error = MY_EOF; | |
| break; | |
| } | |
| buf += rreq->res; | |
| size -= rreq->res; | |
| list_del(&rreq->list); | |
| buf_free(&rreq->data); | |
| g_free(rreq); | |
| } | |
| } | |
| if (res > 0) { | |
| chunk->offset += res; | |
| chunk->size -= res; | |
| } | |
| out: | |
| chunk_put_locked(chunk); | |
| return res; | |
| } | |
| static int sshfs_sync_read(struct sshfs_file *sf, char *buf, size_t size, | |
| off_t offset) | |
| { | |
| struct read_chunk *chunk; | |
| chunk = sshfs_send_read(sf, size, offset); | |
| return wait_chunk(chunk, buf, size); | |
| } | |
| static void submit_read(struct sshfs_file *sf, size_t size, off_t offset, | |
| struct read_chunk **chunkp) | |
| { | |
| struct read_chunk *chunk; | |
| chunk = sshfs_send_read(sf, size, offset); | |
| pthread_mutex_lock(&sshfs.lock); | |
| chunk->modifver = sshfs.modifver; | |
| chunk_put(*chunkp); | |
| *chunkp = chunk; | |
| chunk->refs++; | |
| pthread_mutex_unlock(&sshfs.lock); | |
| } | |
| static struct read_chunk *search_read_chunk(struct sshfs_file *sf, off_t offset) | |
| { | |
| struct read_chunk *ch = sf->readahead; | |
| if (ch && ch->offset == offset && ch->modifver == sshfs.modifver) { | |
| ch->refs++; | |
| return ch; | |
| } else | |
| return NULL; | |
| } | |
| static int sshfs_async_read(struct sshfs_file *sf, char *rbuf, size_t size, | |
| off_t offset) | |
| { | |
| int res = 0; | |
| size_t total = 0; | |
| struct read_chunk *chunk; | |
| struct read_chunk *chunk_prev = NULL; | |
| size_t origsize = size; | |
| int curr_is_seq; | |
| pthread_mutex_lock(&sshfs.lock); | |
| curr_is_seq = sf->is_seq; | |
| sf->is_seq = (sf->next_pos == offset && sf->modifver == sshfs.modifver); | |
| sf->next_pos = offset + size; | |
| sf->modifver = sshfs.modifver; | |
| chunk = search_read_chunk(sf, offset); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (chunk && chunk->size < size) { | |
| chunk_prev = chunk; | |
| size -= chunk->size; | |
| offset += chunk->size; | |
| chunk = NULL; | |
| } | |
| if (!chunk) | |
| submit_read(sf, size, offset, &chunk); | |
| if (curr_is_seq && chunk && chunk->size <= size) | |
| submit_read(sf, origsize, offset + size, &sf->readahead); | |
| if (chunk_prev) { | |
| size_t prev_size = chunk_prev->size; | |
| res = wait_chunk(chunk_prev, rbuf, prev_size); | |
| if (res < (int) prev_size) { | |
| chunk_put_locked(chunk); | |
| return res; | |
| } | |
| rbuf += res; | |
| total += res; | |
| } | |
| res = wait_chunk(chunk, rbuf, size); | |
| if (res > 0) | |
| total += res; | |
| if (res < 0) | |
| return res; | |
| return total; | |
| } | |
| static int sshfs_read(const char *path, char *rbuf, size_t size, off_t offset, | |
| struct fuse_file_info *fi) | |
| { | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| (void) path; | |
| if (!sshfs_file_is_conn(sf)) | |
| return -EIO; | |
| if (sshfs.sync_read) | |
| return sshfs_sync_read(sf, rbuf, size, offset); | |
| else | |
| return sshfs_async_read(sf, rbuf, size, offset); | |
| } | |
| static void sshfs_write_begin(struct request *req) | |
| { | |
| struct sshfs_file *sf = (struct sshfs_file *) req->data; | |
| sshfs_file_get(sf); | |
| list_add(&req->list, &sf->write_reqs); | |
| } | |
| static void sshfs_write_end(struct request *req) | |
| { | |
| uint32_t serr; | |
| struct sshfs_file *sf = (struct sshfs_file *) req->data; | |
| if (req->error) | |
| sf->write_error = req->error; | |
| else if (req->replied) { | |
| if (req->reply_type != SSH_FXP_STATUS) { | |
| fprintf(stderr, "protocol error\n"); | |
| } else if (buf_get_uint32(&req->reply, &serr) != -1 && | |
| serr != SSH_FX_OK) { | |
| sf->write_error = -EIO; | |
| } | |
| } | |
| list_del(&req->list); | |
| pthread_cond_broadcast(&sf->write_finished); | |
| sshfs_file_put(sf); | |
| } | |
| static int sshfs_async_write(struct sshfs_file *sf, const char *wbuf, | |
| size_t size, off_t offset) | |
| { | |
| int err = 0; | |
| struct buffer *handle = &sf->handle; | |
| while (!err && size) { | |
| struct buffer buf; | |
| struct iovec iov[2]; | |
| size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; | |
| buf_init(&buf, 0); | |
| buf_add_buf(&buf, handle); | |
| buf_add_uint64(&buf, offset); | |
| buf_add_uint32(&buf, bsize); | |
| buf_to_iov(&buf, &iov[0]); | |
| iov[1].iov_base = (void *) wbuf; | |
| iov[1].iov_len = bsize; | |
| err = sftp_request_send(SSH_FXP_WRITE, iov, 2, | |
| sshfs_write_begin, sshfs_write_end, | |
| 0, sf, NULL); | |
| buf_free(&buf); | |
| size -= bsize; | |
| wbuf += bsize; | |
| offset += bsize; | |
| } | |
| return err; | |
| } | |
| static void sshfs_sync_write_begin(struct request *req) | |
| { | |
| struct sshfs_io *sio = (struct sshfs_io *) req->data; | |
| sio->num_reqs++; | |
| } | |
| static void sshfs_sync_write_end(struct request *req) | |
| { | |
| uint32_t serr; | |
| struct sshfs_io *sio = (struct sshfs_io *) req->data; | |
| if (req->error) { | |
| sio->error = req->error; | |
| } else if (req->replied) { | |
| if (req->reply_type != SSH_FXP_STATUS) { | |
| fprintf(stderr, "protocol error\n"); | |
| } else if (buf_get_uint32(&req->reply, &serr) != -1 && | |
| serr != SSH_FX_OK) { | |
| sio->error = -EIO; | |
| } | |
| } | |
| sio->num_reqs--; | |
| if (!sio->num_reqs) | |
| pthread_cond_broadcast(&sio->finished); | |
| } | |
| static int sshfs_sync_write(struct sshfs_file *sf, const char *wbuf, | |
| size_t size, off_t offset) | |
| { | |
| int err = 0; | |
| struct buffer *handle = &sf->handle; | |
| struct sshfs_io sio = { .error = 0, .num_reqs = 0 }; | |
| pthread_cond_init(&sio.finished, NULL); | |
| while (!err && size) { | |
| struct buffer buf; | |
| struct iovec iov[2]; | |
| size_t bsize = size < sshfs.max_write ? size : sshfs.max_write; | |
| buf_init(&buf, 0); | |
| buf_add_buf(&buf, handle); | |
| buf_add_uint64(&buf, offset); | |
| buf_add_uint32(&buf, bsize); | |
| buf_to_iov(&buf, &iov[0]); | |
| iov[1].iov_base = (void *) wbuf; | |
| iov[1].iov_len = bsize; | |
| err = sftp_request_send(SSH_FXP_WRITE, iov, 2, | |
| sshfs_sync_write_begin, | |
| sshfs_sync_write_end, | |
| 0, &sio, NULL); | |
| buf_free(&buf); | |
| size -= bsize; | |
| wbuf += bsize; | |
| offset += bsize; | |
| } | |
| pthread_mutex_lock(&sshfs.lock); | |
| while (sio.num_reqs) | |
| pthread_cond_wait(&sio.finished, &sshfs.lock); | |
| pthread_mutex_unlock(&sshfs.lock); | |
| if (!err) | |
| err = sio.error; | |
| return err; | |
| } | |
| static int sshfs_write(const char *path, const char *wbuf, size_t size, | |
| off_t offset, struct fuse_file_info *fi) | |
| { | |
| int err; | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| (void) path; | |
| if (!sshfs_file_is_conn(sf)) | |
| return -EIO; | |
| sshfs_inc_modifver(); | |
| if (!sshfs.sync_write && !sf->write_error) | |
| err = sshfs_async_write(sf, wbuf, size, offset); | |
| else | |
| err = sshfs_sync_write(sf, wbuf, size, offset); | |
| return err ? err : (int) size; | |
| } | |
| static int sshfs_ext_statvfs(const char *path, struct statvfs *stbuf) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer outbuf; | |
| buf_init(&buf, 0); | |
| buf_add_string(&buf, SFTP_EXT_STATVFS); | |
| buf_add_path(&buf, path); | |
| err = sftp_request(SSH_FXP_EXTENDED, &buf, SSH_FXP_EXTENDED_REPLY, | |
| &outbuf); | |
| if (!err) { | |
| if (buf_get_statvfs(&outbuf, stbuf) == -1) | |
| err = -EIO; | |
| buf_free(&outbuf); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| #if FUSE_VERSION >= 25 | |
| static int sshfs_statfs(const char *path, struct statvfs *buf) | |
| { | |
| if (sshfs.ext_statvfs) | |
| return sshfs_ext_statvfs(path, buf); | |
| buf->f_namemax = 255; | |
| buf->f_bsize = sshfs.blksize; | |
| /* | |
| * df seems to use f_bsize instead of f_frsize, so make them | |
| * the same | |
| */ | |
| buf->f_frsize = buf->f_bsize; | |
| buf->f_blocks = buf->f_bfree = buf->f_bavail = | |
| 1000ULL * 1024 * 1024 * 1024 / buf->f_frsize; | |
| buf->f_files = buf->f_ffree = 1000000000; | |
| return 0; | |
| } | |
| #else | |
| static int sshfs_statfs(const char *path, struct statfs *buf) | |
| { | |
| if (sshfs.ext_statvfs) { | |
| int err; | |
| struct statvfs vbuf; | |
| err = sshfs_ext_statvfs(path, &vbuf); | |
| if (!err) { | |
| buf->f_bsize = vbuf.f_bsize; | |
| buf->f_blocks = vbuf.f_blocks; | |
| buf->f_bfree = vbuf.f_bfree; | |
| buf->f_bavail = vbuf.f_bavail; | |
| buf->f_files = vbuf.f_files; | |
| buf->f_ffree = vbuf.f_ffree; | |
| buf->f_namelen = vbuf.f_namemax; | |
| } | |
| return err; | |
| } | |
| buf->f_namelen = 255; | |
| buf->f_bsize = sshfs.blksize; | |
| buf->f_blocks = buf->f_bfree = buf->f_bavail = | |
| 1000ULL * 1024 * 1024 * 1024 / buf->f_bsize; | |
| buf->f_files = buf->f_ffree = 1000000000; | |
| return 0; | |
| } | |
| #endif | |
| #if FUSE_VERSION >= 25 | |
| static int sshfs_create(const char *path, mode_t mode, | |
| struct fuse_file_info *fi) | |
| { | |
| return sshfs_open_common(path, mode, fi); | |
| } | |
| static int sshfs_ftruncate(const char *path, off_t size, | |
| struct fuse_file_info *fi) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| (void) path; | |
| if (!sshfs_file_is_conn(sf)) | |
| return -EIO; | |
| sshfs_inc_modifver(); | |
| if (sshfs.truncate_workaround) | |
| return sshfs_truncate_workaround(path, size, fi); | |
| buf_init(&buf, 0); | |
| buf_add_buf(&buf, &sf->handle); | |
| buf_add_uint32(&buf, SSH_FILEXFER_ATTR_SIZE); | |
| buf_add_uint64(&buf, size); | |
| err = sftp_request(SSH_FXP_FSETSTAT, &buf, SSH_FXP_STATUS, NULL); | |
| buf_free(&buf); | |
| return err; | |
| } | |
| #endif | |
| static int sshfs_fgetattr(const char *path, struct stat *stbuf, | |
| struct fuse_file_info *fi) | |
| { | |
| int err; | |
| struct buffer buf; | |
| struct buffer outbuf; | |
| struct sshfs_file *sf = get_sshfs_file(fi); | |
| (void) path; | |
| if (!sshfs_file_is_conn(sf)) | |
| return -EIO; | |
| if (sshfs.fstat_workaround) | |
| return sshfs_getattr(path, stbuf); | |
| buf_init(&buf, 0); | |
| buf_add_buf(&buf, &sf->handle); | |
| err = sftp_request(SSH_FXP_FSTAT, &buf, SSH_FXP_ATTRS, &outbuf); | |
| if (!err) { | |
| err = buf_get_attrs(&outbuf, stbuf, NULL); | |
| buf_free(&outbuf); | |
| } | |
| buf_free(&buf); | |
| return err; | |
| } | |
| static int sshfs_truncate_zero(const char *path) | |
| { | |
| int err; | |
| struct fuse_file_info fi; | |
| fi.flags = O_WRONLY | O_TRUNC; | |
| err = sshfs_open(path, &fi); | |
| if (!err) | |
| sshfs_release(path, &fi); | |
| return err; | |
| } | |
| static size_t calc_buf_size(off_t size, off_t offset) | |
| { | |
| return offset + sshfs.max_read < size ? sshfs.max_read : size - offset; | |
| } | |
| static int sshfs_truncate_shrink(const char *path, off_t size) | |
| { | |
| int res; | |
| char *data; | |
| off_t offset; | |
| struct fuse_file_info fi; | |
| data = calloc(size, 1); | |
| if (!data) | |
| return -ENOMEM; | |
| fi.flags = O_RDONLY; | |
| res = sshfs_open(path, &fi); | |
| if (res) | |
| goto out; | |
| for (offset = 0; offset < size; offset += res) { | |
| size_t bufsize = calc_buf_size(size, offset); | |
| res = sshfs_read(path, data + offset, bufsize, offset, &fi); | |
| if (res <= 0) | |
| break; | |
| } | |
| sshfs_release(path, &fi); | |
| if (res < 0) | |
| goto out; | |
| fi.flags = O_WRONLY | O_TRUNC; | |
| res = sshfs_open(path, &fi); | |
| if (res) | |
| goto out; | |
| for (offset = 0; offset < size; offset += res) { | |
| size_t bufsize = calc_buf_size(size, offset); | |
| res = sshfs_write(path, data + offset, bufsize, offset, &fi); | |
| if (res < 0) | |
| break; | |
| } | |
| if (res >= 0) | |
| res = sshfs_flush(path, &fi); | |
| sshfs_release(path, &fi); | |
| out: | |
| free(data); | |
| return res; | |
| } | |
| static int sshfs_truncate_extend(const char *path, off_t size, | |
| struct fuse_file_info *fi) | |
| { | |
| int res; | |
| char c = 0; | |
| struct fuse_file_info tmpfi; | |
| struct fuse_file_info *openfi = fi; | |
| if (!fi) { | |
| openfi = &tmpfi; | |
| openfi->flags = O_WRONLY; | |
| res = sshfs_open(path, openfi); | |
| if (res) | |
| return res; | |
| } | |
| res = sshfs_write(path, &c, 1, size - 1, openfi); | |
| if (res == 1) | |
| res = sshfs_flush(path, openfi); | |
| if (!fi) | |
| sshfs_release(path, openfi); | |
| return res; | |
| } | |
| /* | |
| * Work around broken sftp servers which don't handle | |
| * SSH_FILEXFER_ATTR_SIZE in SETSTAT request. | |
| * | |
| * If new size is zero, just open the file with O_TRUNC. | |
| * | |
| * If new size is smaller than current size, then copy file locally, | |
| * then open/trunc and send it back. | |
| * | |
| * If new size is greater than current size, then write a zero byte to | |
| * the new end of the file. | |
| */ | |
| static int sshfs_truncate_workaround(const char *path, off_t size, | |
| struct fuse_file_info *fi) | |
| { | |
| if (size == 0) | |
| return sshfs_truncate_zero(path); | |
| else { | |
| struct stat stbuf; | |
| int err; | |
| if (fi) | |
| err = sshfs_fgetattr(path, &stbuf, fi); | |
| else | |
| err = sshfs_getattr(path, &stbuf); | |
| if (err) | |
| return err; | |
| if (stbuf.st_size == size) | |
| return 0; | |
| else if (stbuf.st_size > size) | |
| return sshfs_truncate_shrink(path, size); | |
| else | |
| return sshfs_truncate_extend(path, size, fi); | |
| } | |
| } | |
| static int processing_init(void) | |
| { | |
| signal(SIGPIPE, SIG_IGN); | |
| pthread_mutex_init(&sshfs.lock, NULL); | |
| pthread_mutex_init(&sshfs.lock_write, NULL); | |
| pthread_cond_init(&sshfs.outstanding_cond, NULL); | |
| sshfs.reqtab = g_hash_table_new(NULL, NULL); | |
| if (!sshfs.reqtab) { | |
| fprintf(stderr, "failed to create hash table\n"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static struct fuse_cache_operations sshfs_oper = { | |
| .oper = { | |
| .init = sshfs_init, | |
| .getattr = sshfs_getattr, | |
| .readlink = sshfs_readlink, | |
| .mknod = sshfs_mknod, | |
| .mkdir = sshfs_mkdir, | |
| .symlink = sshfs_symlink, | |
| .unlink = sshfs_unlink, | |
| .rmdir = sshfs_rmdir, | |
| .rename = sshfs_rename, | |
| .link = sshfs_link, | |
| .chmod = sshfs_chmod, | |
| .chown = sshfs_chown, | |
| .truncate = sshfs_truncate, | |
| .utime = sshfs_utime, | |
| .open = sshfs_open, | |
| .flush = sshfs_flush, | |
| .fsync = sshfs_fsync, | |
| .release = sshfs_release, | |
| .read = sshfs_read, | |
| .write = sshfs_write, | |
| .statfs = sshfs_statfs, | |
| #if FUSE_VERSION >= 25 | |
| .create = sshfs_create, | |
| .ftruncate = sshfs_ftruncate, | |
| .fgetattr = sshfs_fgetattr, | |
| #endif | |
| }, | |
| .cache_getdir = sshfs_getdir, | |
| }; | |
| static void usage(const char *progname) | |
| { | |
| printf( | |
| "usage: %s [user@]host:[dir] mountpoint [options]\n" | |
| "\n" | |
| "general options:\n" | |
| " -o opt,[opt...] mount options\n" | |
| " -h --help print help\n" | |
| " -V --version print version\n" | |
| "\n" | |
| "SSHFS options:\n" | |
| " -p PORT equivalent to '-o port=PORT'\n" | |
| " -C equivalent to '-o compression=yes'\n" | |
| " -F ssh_configfile specifies alternative ssh configuration file\n" | |
| " -1 equivalent to '-o ssh_protocol=1'\n" | |
| " -o reconnect reconnect to server\n" | |
| " -o delay_connect delay connection to server\n" | |
| " -o sshfs_sync synchronous writes\n" | |
| " -o no_readahead synchronous reads (no speculative readahead)\n" | |
| " -o sshfs_debug print some debugging information\n" | |
| " -o cache=BOOL enable caching {yes,no} (default: yes)\n" | |
| " -o cache_timeout=N sets timeout for caches in seconds (default: 20)\n" | |
| " -o cache_X_timeout=N sets timeout for {stat,dir,link} cache\n" | |
| " -o workaround=LIST colon separated list of workarounds\n" | |
| " none no workarounds enabled\n" | |
| " all all workarounds enabled\n" | |
| " [no]rename fix renaming to existing file (default: off)\n" | |
| #ifdef SSH_NODELAY_WORKAROUND | |
| " [no]nodelay set nodelay tcp flag in ssh (default: on)\n" | |
| #endif | |
| " [no]nodelaysrv set nodelay tcp flag in sshd (default: off)\n" | |
| " [no]truncate fix truncate for old servers (default: off)\n" | |
| " [no]buflimit fix buffer fillup bug in server (default: on)\n" | |
| " -o idmap=TYPE user/group ID mapping, possible types are:\n" | |
| " none no translation of the ID space (default)\n" | |
| " user only translate UID of connecting user\n" | |
| " file translate UIDs/GIDs contained in uidfile/gidfile\n" | |
| " -o uidfile=FILE file containing username:remote_uid mappings\n" | |
| " -o gidfile=FILE file containing groupname:remote_gid mappings\n" | |
| " -o nomap=TYPE with idmap=file, how to handle missing mappings\n" | |
| " ignore don't do any re-mapping\n" | |
| " error return an error (default)\n" | |
| " -o ssh_command=CMD execute CMD instead of 'ssh'\n" | |
| " -o ssh_protocol=N ssh protocol to use (default: 2)\n" | |
| " -o sftp_server=SERV path to sftp server or subsystem (default: sftp)\n" | |
| " -o directport=PORT directly connect to PORT bypassing ssh\n" | |
| " -o slave communicate over stdin and stdout bypassing network\n" | |
| " -o transform_symlinks transform absolute symlinks to relative\n" | |
| " -o follow_symlinks follow symlinks on the server\n" | |
| " -o no_check_root don't check for existence of 'dir' on server\n" | |
| " -o password_stdin read password from stdin (only for pam_mount!)\n" | |
| " -o SSHOPT=VAL ssh options (see man ssh_config)\n" | |
| "\n", progname); | |
| } | |
| static int is_ssh_opt(const char *arg) | |
| { | |
| if (arg[0] != '-') { | |
| unsigned arglen = strlen(arg); | |
| const char **o; | |
| for (o = ssh_opts; *o; o++) { | |
| unsigned olen = strlen(*o); | |
| if (arglen > olen && arg[olen] == '=' && | |
| strncasecmp(arg, *o, olen) == 0) | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int sshfs_fuse_main(struct fuse_args *args) | |
| { | |
| #if FUSE_VERSION >= 26 | |
| return fuse_main(args->argc, args->argv, cache_init(&sshfs_oper), NULL); | |
| #else | |
| return fuse_main(args->argc, args->argv, cache_init(&sshfs_oper)); | |
| #endif | |
| } | |
| static int sshfs_opt_proc(void *data, const char *arg, int key, | |
| struct fuse_args *outargs) | |
| { | |
| char *tmp; | |
| (void) data; | |
| switch (key) { | |
| case FUSE_OPT_KEY_OPT: | |
| if (is_ssh_opt(arg)) { | |
| tmp = g_strdup_printf("-o%s", arg); | |
| ssh_add_arg(tmp); | |
| g_free(tmp); | |
| return 0; | |
| } | |
| return 1; | |
| case FUSE_OPT_KEY_NONOPT: | |
| if (!sshfs.host && strchr(arg, ':')) { | |
| sshfs.host = strdup(arg); | |
| return 0; | |
| } | |
| return 1; | |
| case KEY_PORT: | |
| tmp = g_strdup_printf("-oPort=%s", arg + 2); | |
| ssh_add_arg(tmp); | |
| g_free(tmp); | |
| return 0; | |
| case KEY_COMPRESS: | |
| ssh_add_arg("-oCompression=yes"); | |
| return 0; | |
| case KEY_CONFIGFILE: | |
| tmp = g_strdup_printf("-F%s", arg + 2); | |
| ssh_add_arg(tmp); | |
| g_free(tmp); | |
| return 0; | |
| case KEY_HELP: | |
| usage(outargs->argv[0]); | |
| fuse_opt_add_arg(outargs, "-ho"); | |
| sshfs_fuse_main(outargs); | |
| exit(1); | |
| case KEY_VERSION: | |
| printf("SSHFS version %s\n", PACKAGE_VERSION); | |
| #if FUSE_VERSION >= 25 | |
| fuse_opt_add_arg(outargs, "--version"); | |
| sshfs_fuse_main(outargs); | |
| #endif | |
| exit(0); | |
| case KEY_FOREGROUND: | |
| sshfs.foreground = 1; | |
| return 1; | |
| default: | |
| fprintf(stderr, "internal error\n"); | |
| abort(); | |
| } | |
| } | |
| static int workaround_opt_proc(void *data, const char *arg, int key, | |
| struct fuse_args *outargs) | |
| { | |
| (void) data; (void) key; (void) outargs; | |
| fprintf(stderr, "unknown workaround: '%s'\n", arg); | |
| return -1; | |
| } | |
| int parse_workarounds(void) | |
| { | |
| int res; | |
| char *argv[] = { "", "-o", sshfs.workarounds, NULL }; | |
| struct fuse_args args = FUSE_ARGS_INIT(3, argv); | |
| char *s = sshfs.workarounds; | |
| if (!s) | |
| return 0; | |
| while ((s = strchr(s, ':'))) | |
| *s = ','; | |
| res = fuse_opt_parse(&args, &sshfs, workaround_opts, | |
| workaround_opt_proc); | |
| fuse_opt_free_args(&args); | |
| return res; | |
| } | |
| #if FUSE_VERSION == 25 | |
| static int fuse_opt_insert_arg(struct fuse_args *args, int pos, | |
| const char *arg) | |
| { | |
| assert(pos <= args->argc); | |
| if (fuse_opt_add_arg(args, arg) == -1) | |
| return -1; | |
| if (pos != args->argc - 1) { | |
| char *newarg = args->argv[args->argc - 1]; | |
| memmove(&args->argv[pos + 1], &args->argv[pos], | |
| sizeof(char *) * (args->argc - pos - 1)); | |
| args->argv[pos] = newarg; | |
| } | |
| return 0; | |
| } | |
| #endif | |
| static void check_large_read(struct fuse_args *args) | |
| { | |
| struct utsname buf; | |
| int err = uname(&buf); | |
| if (!err && strcmp(buf.sysname, "Linux") == 0 && | |
| strncmp(buf.release, "2.4.", 4) == 0) | |
| fuse_opt_insert_arg(args, 1, "-olarge_read"); | |
| } | |
| static int read_password(void) | |
| { | |
| int size = getpagesize(); | |
| int max_password = 64; | |
| int n; | |
| sshfs.password = mmap(NULL, size, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, | |
| -1, 0); | |
| if (sshfs.password == MAP_FAILED) { | |
| perror("Failed to allocate locked page for password"); | |
| return -1; | |
| } | |
| #ifdef __APPLE__ | |
| if (mlock(sshfs.password, size) != 0) { | |
| memset(sshfs.password, 0, size); | |
| munmap(sshfs.password, size); | |
| sshfs.password = NULL; | |
| perror("Failed to allocate locked page for password"); | |
| return -1; | |
| } | |
| #endif /* __APPLE__ */ | |
| /* Don't use fgets() because password might stay in memory */ | |
| for (n = 0; n < max_password; n++) { | |
| int res; | |
| res = read(0, &sshfs.password[n], 1); | |
| if (res == -1) { | |
| perror("Reading password"); | |
| return -1; | |
| } | |
| if (res == 0) { | |
| sshfs.password[n] = '\n'; | |
| break; | |
| } | |
| if (sshfs.password[n] == '\n') | |
| break; | |
| } | |
| if (n == max_password) { | |
| fprintf(stderr, "Password too long\n"); | |
| return -1; | |
| } | |
| sshfs.password[n+1] = '\0'; | |
| ssh_add_arg("-oNumberOfPasswordPrompts=1"); | |
| return 0; | |
| } | |
| static void set_ssh_command(void) | |
| { | |
| char *s; | |
| char *d; | |
| int i = 0; | |
| int end = 0; | |
| d = sshfs.ssh_command; | |
| s = sshfs.ssh_command; | |
| while (!end) { | |
| switch (*s) { | |
| case '\0': | |
| end = 1; | |
| case ' ': | |
| *d = '\0'; | |
| if (i == 0) { | |
| replace_arg(&sshfs.ssh_args.argv[0], | |
| sshfs.ssh_command); | |
| } else { | |
| if (fuse_opt_insert_arg(&sshfs.ssh_args, i, | |
| sshfs.ssh_command) == -1) | |
| _exit(1); | |
| } | |
| i++; | |
| d = sshfs.ssh_command; | |
| break; | |
| case '\\': | |
| if (s[1]) | |
| s++; | |
| default: | |
| *d++ = *s; | |
| } | |
| s++; | |
| } | |
| } | |
| static char *find_base_path(void) | |
| { | |
| char *s = sshfs.host; | |
| char *d = s; | |
| for (; *s && *s != ':'; s++) { | |
| if (*s == '[') { | |
| /* | |
| * Handle IPv6 numerical address enclosed in square | |
| * brackets | |
| */ | |
| s++; | |
| for (; *s != ']'; s++) { | |
| if (!*s) { | |
| fprintf(stderr, "missing ']' in hostname\n"); | |
| exit(1); | |
| } | |
| *d++ = *s; | |
| } | |
| } else { | |
| *d++ = *s; | |
| } | |
| } | |
| *d++ = '\0'; | |
| s++; | |
| return s; | |
| } | |
| /* | |
| * Remove commas from fsname, as it confuses the fuse option parser. | |
| */ | |
| static void fsname_remove_commas(char *fsname) | |
| { | |
| if (strchr(fsname, ',') != NULL) { | |
| char *s = fsname; | |
| char *d = s; | |
| for (; *s; s++) { | |
| if (*s != ',') | |
| *d++ = *s; | |
| } | |
| *d = *s; | |
| } | |
| } | |
| #if FUSE_VERSION >= 27 | |
| static char *fsname_escape_commas(char *fsnameold) | |
| { | |
| char *fsname = g_malloc(strlen(fsnameold) * 2 + 1); | |
| char *d = fsname; | |
| char *s; | |
| for (s = fsnameold; *s; s++) { | |
| if (*s == '\\' || *s == ',') | |
| *d++ = '\\'; | |
| *d++ = *s; | |
| } | |
| *d = '\0'; | |
| g_free(fsnameold); | |
| return fsname; | |
| } | |
| #endif | |
| static int ssh_connect(void) | |
| { | |
| int res; | |
| res = processing_init(); | |
| if (res == -1) | |
| return -1; | |
| if (!sshfs.delay_connect) { | |
| if (connect_remote() == -1) | |
| return -1; | |
| if (!sshfs.no_check_root && | |
| sftp_check_root(sshfs.base_path) != 0) | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* number of ':' separated fields in a passwd/group file that we care | |
| * about */ | |
| #define IDMAP_FIELDS 3 | |
| /* given a line from a uidmap or gidmap, parse out the name and id */ | |
| static void parse_idmap_line(char *line, const char* filename, | |
| const unsigned int lineno, uint32_t *ret_id, char **ret_name, | |
| const int eof) | |
| { | |
| /* chomp off the trailing newline */ | |
| char *p = line; | |
| if ((p = strrchr(line, '\n'))) | |
| *p = '\0'; | |
| else if (!eof) { | |
| fprintf(stderr, "%s:%u: line too long\n", filename, lineno); | |
| exit(1); | |
| } | |
| char *tokens[IDMAP_FIELDS]; | |
| char *tok; | |
| int i; | |
| for (i = 0; (tok = strsep(&line, ":")) && (i < IDMAP_FIELDS) ; i++) { | |
| tokens[i] = tok; | |
| } | |
| char *name_tok, *id_tok; | |
| if (i == 2) { | |
| /* assume name:id format */ | |
| name_tok = tokens[0]; | |
| id_tok = tokens[1]; | |
| } else if (i >= IDMAP_FIELDS) { | |
| /* assume passwd/group file format */ | |
| name_tok = tokens[0]; | |
| id_tok = tokens[2]; | |
| } else { | |
| fprintf(stderr, "%s:%u: unknown format\n", filename, lineno); | |
| exit(1); | |
| } | |
| errno = 0; | |
| uint32_t remote_id = strtoul(id_tok, NULL, 10); | |
| if (errno) { | |
| fprintf(stderr, "Invalid id number on line %u of '%s': %s\n", | |
| lineno, filename, strerror(errno)); | |
| exit(1); | |
| } | |
| *ret_name = strdup(name_tok); | |
| *ret_id = remote_id; | |
| } | |
| /* read a uidmap or gidmap */ | |
| static void read_id_map(char *file, uint32_t *(*map_fn)(char *), | |
| const char *name_id, GHashTable **idmap, GHashTable **r_idmap) | |
| { | |
| *idmap = g_hash_table_new(NULL, NULL); | |
| *r_idmap = g_hash_table_new(NULL, NULL); | |
| FILE *fp; | |
| char line[LINE_MAX]; | |
| unsigned int lineno = 0; | |
| uid_t local_uid = getuid(); | |
| fp = fopen(file, "r"); | |
| if (fp == NULL) { | |
| fprintf(stderr, "failed to open '%s': %s\n", | |
| file, strerror(errno)); | |
| exit(1); | |
| } | |
| struct stat st; | |
| if (fstat(fileno(fp), &st) == -1) { | |
| fprintf(stderr, "failed to stat '%s': %s\n", file, | |
| strerror(errno)); | |
| exit(1); | |
| } | |
| if (st.st_uid != local_uid) { | |
| fprintf(stderr, "'%s' is not owned by uid %lu\n", file, | |
| (unsigned long)local_uid); | |
| exit(1); | |
| } | |
| if (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) { | |
| fprintf(stderr, "'%s' is writable by other users\n", file); | |
| exit(1); | |
| } | |
| while (fgets(line, LINE_MAX, fp) != NULL) { | |
| lineno++; | |
| uint32_t remote_id; | |
| char *name; | |
| /* skip blank lines */ | |
| if (line[0] == '\n' || line[0] == '\0') | |
| continue; | |
| parse_idmap_line(line, file, lineno, &remote_id, &name, feof(fp)); | |
| uint32_t *local_id = map_fn(name); | |
| if (local_id == NULL) { | |
| /* not found */ | |
| DEBUG("%s(%u): no local %s\n", name, remote_id, name_id); | |
| free(name); | |
| continue; | |
| } | |
| DEBUG("%s: remote %s %u => local %s %u\n", | |
| name, name_id, remote_id, name_id, *local_id); | |
| g_hash_table_insert(*idmap, GUINT_TO_POINTER(remote_id), GUINT_TO_POINTER(*local_id)); | |
| g_hash_table_insert(*r_idmap, GUINT_TO_POINTER(*local_id), GUINT_TO_POINTER(remote_id)); | |
| free(name); | |
| free(local_id); | |
| } | |
| if (fclose(fp) == EOF) { | |
| fprintf(stderr, "failed to close '%s': %s", | |
| file, strerror(errno)); | |
| exit(1); | |
| } | |
| } | |
| /* given a username, return a pointer to its uid, or NULL if it doesn't | |
| * exist on this system */ | |
| static uint32_t *username_to_uid(char *name) | |
| { | |
| errno = 0; | |
| struct passwd *pw = getpwnam(name); | |
| if (pw == NULL) { | |
| if (errno == 0) { | |
| /* "does not exist" */ | |
| return NULL; | |
| } | |
| fprintf(stderr, "Failed to look up user '%s': %s\n", | |
| name, strerror(errno)); | |
| exit(1); | |
| } | |
| uint32_t *r = malloc(sizeof(uint32_t)); | |
| if (r == NULL) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| *r = pw->pw_uid; | |
| return r; | |
| } | |
| /* given a groupname, return a pointer to its gid, or NULL if it doesn't | |
| * exist on this system */ | |
| static uint32_t *groupname_to_gid(char *name) | |
| { | |
| errno = 0; | |
| struct group *gr = getgrnam(name); | |
| if (gr == NULL) { | |
| if (errno == 0) { | |
| /* "does not exist" */ | |
| return NULL; | |
| } | |
| fprintf(stderr, "Failed to look up group '%s': %s\n", | |
| name, strerror(errno)); | |
| exit(1); | |
| } | |
| uint32_t *r = malloc(sizeof(uint32_t)); | |
| if (r == NULL) { | |
| fprintf(stderr, "sshfs: memory allocation failed\n"); | |
| abort(); | |
| } | |
| *r = gr->gr_gid; | |
| return r; | |
| } | |
| static inline void load_uid_map(void) | |
| { | |
| read_id_map(sshfs.uid_file, &username_to_uid, "uid", &sshfs.uid_map, &sshfs.r_uid_map); | |
| } | |
| static inline void load_gid_map(void) | |
| { | |
| read_id_map(sshfs.gid_file, &groupname_to_gid, "gid", &sshfs.gid_map, &sshfs.r_gid_map); | |
| } | |
| int main(int argc, char *argv[], __unused char *envp[], char **exec_path) | |
| { | |
| #ifdef __APPLE__ | |
| if (!realpath(*exec_path, sshfs_program_path)) { | |
| memset(sshfs_program_path, 0, PATH_MAX); | |
| } | |
| #endif | |
| int res; | |
| struct fuse_args args = FUSE_ARGS_INIT(argc, argv); | |
| char *tmp; | |
| char *fsname; | |
| const char *sftp_server; | |
| int libver; | |
| #ifdef __APPLE__ | |
| /* Until this gets fixed somewhere else. */ | |
| g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); | |
| #endif /* __APPLE__ */ | |
| g_thread_init(NULL); | |
| sshfs.blksize = 4096; | |
| /* SFTP spec says all servers should allow at least 32k I/O */ | |
| sshfs.max_read = 32768; | |
| sshfs.max_write = 32768; | |
| sshfs.nodelay_workaround = 1; | |
| sshfs.nodelaysrv_workaround = 0; | |
| #ifdef __APPLE__ | |
| sshfs.rename_workaround = 1; | |
| #else | |
| sshfs.rename_workaround = 0; | |
| #endif /* __APPLE__ */ | |
| sshfs.truncate_workaround = 0; | |
| sshfs.buflimit_workaround = 1; | |
| sshfs.ssh_ver = 2; | |
| sshfs.progname = argv[0]; | |
| sshfs.rfd = -1; | |
| sshfs.wfd = -1; | |
| sshfs.ptyfd = -1; | |
| sshfs.ptyslavefd = -1; | |
| sshfs.delay_connect = 0; | |
| sshfs.slave = 0; | |
| sshfs.detect_uid = 0; | |
| sshfs.idmap = IDMAP_NONE; | |
| sshfs.nomap = NOMAP_ERROR; | |
| ssh_add_arg("ssh"); | |
| ssh_add_arg("-x"); | |
| ssh_add_arg("-a"); | |
| ssh_add_arg("-oClearAllForwardings=yes"); | |
| #ifdef __APPLE__ | |
| sshfs.detect_uid = 1; | |
| #endif | |
| if (fuse_opt_parse(&args, &sshfs, sshfs_opts, sshfs_opt_proc) == -1 || | |
| parse_workarounds() == -1) | |
| exit(1); | |
| if (sshfs.idmap == IDMAP_USER) | |
| sshfs.detect_uid = 1; | |
| else if (sshfs.idmap == IDMAP_FILE) { | |
| sshfs.uid_map = NULL; | |
| sshfs.gid_map = NULL; | |
| sshfs.r_uid_map = NULL; | |
| sshfs.r_gid_map = NULL; | |
| if (!sshfs.uid_file && !sshfs.gid_file) { | |
| fprintf(stderr, "need a uidfile or gidfile with idmap=file\n"); | |
| exit(1); | |
| } | |
| if (sshfs.uid_file) | |
| load_uid_map(); | |
| if (sshfs.gid_file) | |
| load_gid_map(); | |
| } | |
| free(sshfs.uid_file); | |
| free(sshfs.gid_file); | |
| DEBUG("SSHFS version %s\n", PACKAGE_VERSION); | |
| if (sshfs.slave) { | |
| /* Force sshfs to the foreground when using stdin+stdout */ | |
| sshfs.foreground = 1; | |
| } | |
| if (sshfs.slave && sshfs.password_stdin) { | |
| fprintf(stderr, "the password_stdin and slave options cannot both be specified\n"); | |
| exit(1); | |
| } | |
| if (sshfs.password_stdin) { | |
| res = read_password(); | |
| if (res == -1) | |
| exit(1); | |
| } | |
| if (sshfs.buflimit_workaround) | |
| /* Work around buggy sftp-server in OpenSSH. Without this on | |
| a slow server a 10Mbyte buffer would fill up and the server | |
| would abort */ | |
| sshfs.max_outstanding_len = 8388608; | |
| else | |
| sshfs.max_outstanding_len = ~0; | |
| if (!sshfs.host) { | |
| fprintf(stderr, "missing host\n"); | |
| fprintf(stderr, "see `%s -h' for usage\n", argv[0]); | |
| exit(1); | |
| } | |
| fsname = g_strdup(sshfs.host); | |
| sshfs.base_path = g_strdup(find_base_path()); | |
| if (sshfs.ssh_command) | |
| set_ssh_command(); | |
| tmp = g_strdup_printf("-%i", sshfs.ssh_ver); | |
| ssh_add_arg(tmp); | |
| g_free(tmp); | |
| ssh_add_arg(sshfs.host); | |
| if (sshfs.sftp_server) | |
| sftp_server = sshfs.sftp_server; | |
| else if (sshfs.ssh_ver == 1) | |
| sftp_server = SFTP_SERVER_PATH; | |
| else | |
| sftp_server = "sftp"; | |
| if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL) | |
| ssh_add_arg("-s"); | |
| ssh_add_arg(sftp_server); | |
| free(sshfs.sftp_server); | |
| res = cache_parse_options(&args); | |
| if (res == -1) | |
| exit(1); | |
| sshfs.randseed = time(0); | |
| if (sshfs.max_read > 65536) | |
| sshfs.max_read = 65536; | |
| if (sshfs.max_write > 65536) | |
| sshfs.max_write = 65536; | |
| if (fuse_is_lib_option("ac_attr_timeout=")) | |
| fuse_opt_insert_arg(&args, 1, "-oauto_cache,ac_attr_timeout=0"); | |
| #if FUSE_VERSION >= 27 | |
| libver = fuse_version(); | |
| assert(libver >= 27); | |
| if (libver >= 28) | |
| fsname = fsname_escape_commas(fsname); | |
| else | |
| fsname_remove_commas(fsname); | |
| tmp = g_strdup_printf("-osubtype=sshfs,fsname=%s", fsname); | |
| #else | |
| fsname_remove_commas(fsname); | |
| tmp = g_strdup_printf("-ofsname=sshfs#%s", fsname); | |
| #endif | |
| fuse_opt_insert_arg(&args, 1, tmp); | |
| g_free(tmp); | |
| g_free(fsname); | |
| check_large_read(&args); | |
| #if FUSE_VERSION >= 26 | |
| { | |
| struct fuse *fuse; | |
| struct fuse_chan *ch; | |
| char *mountpoint; | |
| int multithreaded; | |
| int foreground; | |
| struct stat st; | |
| res = fuse_parse_cmdline(&args, &mountpoint, &multithreaded, | |
| &foreground); | |
| if (res == -1) | |
| exit(1); | |
| if (sshfs.slave) { | |
| /* Force sshfs to the foreground when using stdin+stdout */ | |
| foreground = 1; | |
| } | |
| res = stat(mountpoint, &st); | |
| if (res == -1) { | |
| perror(mountpoint); | |
| exit(1); | |
| } | |
| sshfs.mnt_mode = st.st_mode; | |
| ch = fuse_mount(mountpoint, &args); | |
| if (!ch) | |
| exit(1); | |
| res = fcntl(fuse_chan_fd(ch), F_SETFD, FD_CLOEXEC); | |
| if (res == -1) | |
| perror("WARNING: failed to set FD_CLOEXEC on fuse device"); | |
| fuse = fuse_new(ch, &args, cache_init(&sshfs_oper), | |
| sizeof(struct fuse_operations), NULL); | |
| if (fuse == NULL) { | |
| fuse_unmount(mountpoint, ch); | |
| exit(1); | |
| } | |
| res = ssh_connect(); | |
| if (res == -1) { | |
| fuse_unmount(mountpoint, ch); | |
| fuse_destroy(fuse); | |
| exit(1); | |
| } | |
| res = fuse_daemonize(foreground); | |
| if (res != -1) | |
| res = fuse_set_signal_handlers(fuse_get_session(fuse)); | |
| if (res == -1) { | |
| fuse_unmount(mountpoint, ch); | |
| fuse_destroy(fuse); | |
| exit(1); | |
| } | |
| if (multithreaded) | |
| res = fuse_loop_mt(fuse); | |
| else | |
| res = fuse_loop(fuse); | |
| if (res == -1) | |
| res = 1; | |
| else | |
| res = 0; | |
| fuse_remove_signal_handlers(fuse_get_session(fuse)); | |
| fuse_unmount(mountpoint, ch); | |
| fuse_destroy(fuse); | |
| free(mountpoint); | |
| } | |
| #else | |
| res = ssh_connect(); | |
| if (res == -1) | |
| exit(1); | |
| res = sshfs_fuse_main(&args); | |
| #endif | |
| if (sshfs.debug) { | |
| unsigned int avg_rtt = 0; | |
| if (sshfs.num_sent) | |
| avg_rtt = sshfs.total_rtt / sshfs.num_sent; | |
| DEBUG("\n" | |
| "sent: %llu messages, %llu bytes\n" | |
| "received: %llu messages, %llu bytes\n" | |
| "rtt min/max/avg: %ums/%ums/%ums\n" | |
| "num connect: %u\n", | |
| (unsigned long long) sshfs.num_sent, | |
| (unsigned long long) sshfs.bytes_sent, | |
| (unsigned long long) sshfs.num_received, | |
| (unsigned long long) sshfs.bytes_received, | |
| sshfs.min_rtt, sshfs.max_rtt, avg_rtt, | |
| sshfs.num_connect); | |
| } | |
| fuse_opt_free_args(&args); | |
| fuse_opt_free_args(&sshfs.ssh_args); | |
| free(sshfs.directport); | |
| return res; | |
| } |