/** * xrdp: A Remote Desktop Protocol server. * * Copyright (C) Laxmikant Rashinkar 2013 LK.Rashinkar@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* FUSE mount point */ char g_fuse_root_path[256] = ""; #ifdef XRDP_FUSE static const char *g_fuse_root_path_basename; /* See xfuse_path_in_xfuse_fs() */ static int g_fuse_root_parent_dev; /* Ditto */ static int g_fuse_root_parent_ino; /* Ditto */ #endif char g_fuse_clipboard_path[256] = ""; /* for clipboard use */ #if defined(HAVE_CONFIG_H) #include #endif #ifndef XRDP_FUSE /****************************************************************************** ** ** ** when FUSE is NOT enabled in xrdp ** ** ** ******************************************************************************/ #include #include #include #include "arch.h" #include "chansrv_fuse.h" #include "chansrv_xfs.h" /* dummy calls when XRDP_FUSE is not defined */ int xfuse_init(void) { return 0; } int xfuse_deinit(void) { return 0; } int xfuse_check_wait_objs(void) { return 0; } int xfuse_get_wait_objs(tbus *objs, int *count, int *timeout) { return 0; } int xfuse_create_share(tui32 device_id, const char *dirname) { return 0; } void xfuse_delete_share(tui32 share_id) {} int xfuse_clear_clip_dir(void) { return 0; } int xfuse_file_contents_range(int stream_id, const char *data, int data_bytes) { return 0; } int xfuse_file_contents_size(int stream_id, int file_size) { return 0; } int xfuse_add_clip_dir_item(const char *filename, int flags, int size, int lindex) { return 0; } void xfuse_devredir_cb_enum_dir_add_entry( struct state_dirscan *fip, const char *name, const struct file_attr *fattr) {} void xfuse_devredir_cb_enum_dir_done(struct state_dirscan *fip, enum NTSTATUS IoStatus) {} void xfuse_devredir_cb_lookup_entry(struct state_lookup *fip, enum NTSTATUS IoStatus, const struct file_attr *file_info) {} void xfuse_devredir_cb_setattr(struct state_setattr *fip, enum NTSTATUS IoStatus) {} void xfuse_devredir_cb_create_file(struct state_create *fip, enum NTSTATUS IoStatus, tui32 DeviceId, tui32 FileId) {} void xfuse_devredir_cb_open_file(struct state_open *fip, enum NTSTATUS IoStatus, tui32 DeviceId, tui32 FileId) {} void xfuse_devredir_cb_read_file(struct state_read *fip, enum NTSTATUS IoStatus, const char *buf, size_t length) {} void xfuse_devredir_cb_write_file( struct state_write *fip, enum NTSTATUS IoStatus, off_t offset, size_t length) {} void xfuse_devredir_cb_rmdir_or_file(struct state_remove *fip, enum NTSTATUS IoStatus) {} void xfuse_devredir_cb_rename_file(struct state_rename *fip, enum NTSTATUS IoStatus) {} void xfuse_devredir_cb_file_close(struct state_close *fip) {} int xfuse_path_in_xfuse_fs(const char *path) { return 0; } #else /****************************************************************************** ** ** ** when FUSE is enabled in xrdp ** ** ** ******************************************************************************/ /* FUSE_USE_VERSION must be defined globally for other parts of * xrdp-chansrv which include for definitions. Check * it's actually defined here */ #ifndef FUSE_USE_VERSION #error Define FUSE_USE_VERSION in the make system and recompile #endif #include #include #include #include #include #include #include #include #include #include "arch.h" #include "os_calls.h" #include "string_calls.h" #include "clipboard_file.h" #include "chansrv_fuse.h" #include "chansrv_xfs.h" #include "chansrv.h" #include "chansrv_config.h" #include "devredir.h" #include "list.h" #include "file.h" #ifndef EREMOTEIO #define EREMOTEIO EIO #endif #define XFUSE_ATTR_TIMEOUT 5.0 #define XFUSE_ENTRY_TIMEOUT 5.0 /* Type of buffer used for fuse_add_direntry() calls */ struct dirbuf1 { char buf[4096]; size_t len; }; /* * Record type used to maintain state when running a directory scan */ struct state_dirscan { fuse_req_t req; /* Original FUSE request from opendir */ struct fuse_file_info fi; /* File info struct passed to opendir */ fuse_ino_t pinum; /* inum of parent directory */ }; /* * Record type used to maintain state when running an entry lookup */ struct state_lookup { fuse_req_t req; /* Original FUSE request from lookup */ fuse_ino_t pinum; /* inum of parent directory */ char name[XFS_MAXFILENAMELEN + 1]; /* Name to look up */ fuse_ino_t existing_inum; /* inum of an existing entry */ tui32 existing_generation; /* generation of the above */ }; /* * Record type used to maintain state when running an entry setattr */ struct state_setattr { fuse_req_t req; /* Original FUSE request from lookup */ fuse_ino_t inum; /* inum of entry */ struct file_attr fattr; /* File attributes to set */ tui32 change_mask; /* Attributes to set in fattr */ }; /* * Record type used to maintain state when running an open */ struct state_open { fuse_req_t req; /* Original FUSE request from lookup */ struct fuse_file_info fi; /* File info struct passed to open */ fuse_ino_t inum; /* inum of file to open */ }; /* * Record type used to maintain state when running a create */ struct state_create { fuse_req_t req; /* Original FUSE request from lookup */ struct fuse_file_info fi; /* File info struct passed to open */ fuse_ino_t pinum; /* inum of parent directory */ char name[XFS_MAXFILENAMELEN + 1]; /* Name of file in parent directory */ mode_t mode; /* Mode of file to create */ }; /* * Record type used to maintain state when running a read */ struct state_read { fuse_req_t req; /* Original FUSE request from lookup */ }; /* * Record type used to maintain state when running a write */ struct state_write { fuse_req_t req; /* Original FUSE request from lookup */ fuse_ino_t inum; /* inum of file we're writing */ }; /* * Record type used to maintain state when running a remove */ struct state_remove { fuse_req_t req; /* Original FUSE request from lookup */ fuse_ino_t inum; /* inum of file we're removing */ }; /* * Record type used to maintain state when running a rename */ struct state_rename { fuse_req_t req; /* Original FUSE request from lookup */ fuse_ino_t pinum; /* inum of parent of file */ fuse_ino_t new_pinum; /* inum of new parent of file */ char name[XFS_MAXFILENAMELEN + 1]; /* New name of file in new parent dir */ }; /* * Record type used to maintain state when running a close */ struct state_close { fuse_req_t req; /* Original FUSE request from lookup */ struct fuse_file_info fi; /* File info struct passed to open */ fuse_ino_t inum; /* inum of file to open */ }; struct xfuse_handle { tui32 DeviceId; tui32 FileId; int is_loc_resource; /* this is not a redirected resource */ /* a directory handle, if this xfuse_handle represents a directory. * NULL, if this xfuse_handle represents a file. * * Note: when this xfuse_handle represents a directory, then the other * fields of this structure contain invalid values. */ struct xfs_dir_handle *dir_handle; }; typedef struct xfuse_handle XFUSE_HANDLE; /* used for file data request sent to client */ struct req_list_item { fuse_req_t req; int stream_id; int lindex; int off; int size; }; extern struct config_chansrv *g_cfg; /* in chansrv.c */ static struct list *g_req_list = 0; static struct xfs_fs *g_xfs; /* an inst of xrdp file system */ static ino_t g_clipboard_inum; /* inode of clipboard dir */ static char *g_mount_point = 0; /* our FUSE mount point */ static struct fuse_lowlevel_ops g_xfuse_ops; /* setup FUSE callbacks */ static int g_xfuse_inited = 0; /* true when FUSE is inited */ static struct fuse_chan *g_ch = 0; static struct fuse_session *g_se = 0; static char *g_buffer = 0; static int g_fd = 0; static tintptr g_bufsize = 0; /* forward declarations for internal access */ static int xfuse_init_xrdp_fs(void); static int xfuse_deinit_xrdp_fs(void); static int xfuse_init_lib(struct fuse_args *args); /* forward declarations for FUSE callbacks */ static void xfuse_cb_lookup(fuse_req_t req, fuse_ino_t parent, const char *name); static void xfuse_cb_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); /* this is not a callback, but it's used by xfuse_cb_readdir() */ static int xfuse_dirbuf_add1(fuse_req_t req, struct dirbuf1 *b, XFS_INODE *xinode, off_t offset); static void xfuse_cb_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); static void xfuse_cb_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode); static void xfuse_cb_unlink(fuse_req_t req, fuse_ino_t parent, const char *name); static void xfuse_cb_rename(fuse_req_t req, fuse_ino_t old_parent, const char *old_name, fuse_ino_t new_parent, const char *new_name); /* Whether to create a dir of file depends on whether S_IFDIR is set in the mode field */ static void xfuse_create_dir_or_file(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi); static void xfuse_cb_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); static void xfuse_cb_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); static void xfuse_cb_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); static void xfuse_cb_write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); static void xfuse_cb_create(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi); #if 0 static void xfuse_cb_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi); #endif static void xfuse_cb_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi); static void xfuse_cb_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); static void xfuse_cb_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); /* miscellaneous functions */ static void xfs_inode_to_fuse_entry_param(const XFS_INODE *xinode, struct fuse_entry_param *e); static void make_fuse_entry_reply(fuse_req_t req, const XFS_INODE *xinode); static void make_fuse_attr_reply(fuse_req_t req, const XFS_INODE *xinode); static const char *filename_on_device(const char *full_path); static void update_inode_file_attributes(const struct file_attr *fattr, tui32 change_mask, XFS_INODE *xinode); static char *get_name_for_entry_in_parent(fuse_ino_t parent, const char *name); static unsigned int format_user_info(char *dest, unsigned int len, const char *format); /*****************************************************************************/ static int load_fuse_config(void) { return 0; } /*****************************************************************************/ static XFUSE_HANDLE * xfuse_handle_create() { return g_new0(XFUSE_HANDLE, 1); } /*****************************************************************************/ static void xfuse_handle_delete(XFUSE_HANDLE *self) { if (self == NULL) { return; } if (self->dir_handle != NULL) { free(self->dir_handle); } free(self); } /*****************************************************************************/ static uint64_t xfuse_handle_to_fuse_handle(XFUSE_HANDLE *self) { return (uint64_t) (tintptr) self; } /*****************************************************************************/ static XFUSE_HANDLE * xfuse_handle_from_fuse_handle(uint64_t handle) { return (XFUSE_HANDLE *) (tintptr) handle; } /***************************************************************************** ** ** ** public functions - can be called from any code path ** ** ** *****************************************************************************/ /** * Initialize FUSE subsystem * * @return 0 on success, -1 on failure, 1 for feature disabled *****************************************************************************/ int xfuse_init(void) { struct fuse_args args = FUSE_ARGS_INIT(0, NULL); char *p; /* if already inited, just return */ if (g_xfuse_inited) { LOG_DEVEL(LOG_LEVEL_DEBUG, "already inited"); return 0; } /* This feature may be disabled */ if (!g_cfg->enable_fuse_mount) { /* * Only log the 'disabled mounts' message one time */ static int disabled_mounts_msg_shown = 0; if (!disabled_mounts_msg_shown) { LOG(LOG_LEVEL_INFO, "FUSE mounts are disabled by config"); disabled_mounts_msg_shown = 1; } return 1; } if (g_ch != 0) { LOG_DEVEL(LOG_LEVEL_ERROR, "g_ch is not zero"); return -1; } load_fuse_config(); /* define FUSE mount point */ if (g_cfg->fuse_mount_name[0] == '/') { /* String is an absolute path to the mount point containing * %u or %U characters */ format_user_info(g_fuse_root_path, sizeof(g_fuse_root_path), g_cfg->fuse_mount_name); } else { /* mount_name is relative to $HOME, e.g. ~/xrdp_client, * or ~/thinclient_drives */ unsigned int len = g_snprintf(g_fuse_root_path, sizeof(g_fuse_root_path), "%s/", g_getenv("HOME")); if (len < sizeof(g_fuse_root_path)) { format_user_info(g_fuse_root_path + len, sizeof(g_fuse_root_path) - len, g_cfg->fuse_mount_name); } } /* Remove all trailing '/' from the root path */ p = g_fuse_root_path + g_strlen(g_fuse_root_path); while ( p > g_fuse_root_path && *(p - 1) == '/') { --p; *p = '\0'; } /* This shouldn't happen */ if (g_strlen(g_fuse_root_path) == 0) { LOG(LOG_LEVEL_ERROR, "Fuse root path is empty after removing trailing '/'"); return -1; } g_snprintf(g_fuse_clipboard_path, sizeof(g_fuse_clipboard_path), "%s/.clipboard", g_fuse_root_path); /* if FUSE mount point does not exist, create it */ if (!g_directory_exist(g_fuse_root_path)) { (void)g_create_path(g_fuse_root_path); if (!g_create_dir(g_fuse_root_path)) { LOG(LOG_LEVEL_ERROR, "mkdir %s failed (%s)", g_fuse_root_path, g_get_strerror()); return -1; } } /* Get the characteristics of the parent directory of the FUSE mount * point. Used by xfuse_path_in_xfuse_fs() */ g_fuse_root_parent_dev = -1; g_fuse_root_parent_ino = -1; p = (char *)g_strrchr(g_fuse_root_path, '/'); if (p != NULL) { /* Temporarily finish the root path at this point */ *p = '\0'; g_fuse_root_path_basename = p + 1; g_fuse_root_parent_dev = g_file_get_device_number(g_fuse_root_path); g_fuse_root_parent_ino = g_file_get_inode_num(g_fuse_root_path); *p = '/'; } if (g_fuse_root_parent_dev == -1 || g_fuse_root_parent_ino == -1) { LOG(LOG_LEVEL_ERROR, "Unable to obtain characteristics of directory containing %s", g_fuse_root_path); return -1; } /* setup xrdp file system */ if (xfuse_init_xrdp_fs()) { return -1; } /* setup FUSE callbacks */ g_memset(&g_xfuse_ops, 0, sizeof(g_xfuse_ops)); g_xfuse_ops.lookup = xfuse_cb_lookup; g_xfuse_ops.readdir = xfuse_cb_readdir; g_xfuse_ops.mkdir = xfuse_cb_mkdir; g_xfuse_ops.rmdir = xfuse_cb_unlink; g_xfuse_ops.unlink = xfuse_cb_unlink; g_xfuse_ops.rename = xfuse_cb_rename; g_xfuse_ops.open = xfuse_cb_open; g_xfuse_ops.release = xfuse_cb_release; g_xfuse_ops.read = xfuse_cb_read; g_xfuse_ops.write = xfuse_cb_write; g_xfuse_ops.create = xfuse_cb_create; //g_xfuse_ops.fsync = xfuse_cb_fsync; /* LK_TODO delete this */ g_xfuse_ops.getattr = xfuse_cb_getattr; g_xfuse_ops.setattr = xfuse_cb_setattr; g_xfuse_ops.opendir = xfuse_cb_opendir; g_xfuse_ops.releasedir = xfuse_cb_releasedir; fuse_opt_add_arg(&args, "xrdp-chansrv"); fuse_opt_add_arg(&args, g_fuse_root_path); //fuse_opt_add_arg(&args, "-s"); /* single threaded mode */ //fuse_opt_add_arg(&args, "-d"); /* debug mode */ if (xfuse_init_lib(&args)) { xfuse_deinit(); return -1; } g_xfuse_inited = 1; return 0; } /** * De-initialize FUSE subsystem * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_deinit(void) { if (g_ch != 0) { fuse_session_remove_chan(g_ch); fuse_unmount(g_mount_point, g_ch); g_ch = 0; } if (g_se != 0) { fuse_session_destroy(g_se); g_se = 0; } if (g_buffer != 0) { g_free(g_buffer); g_buffer = 0; } if (g_req_list != 0) { list_delete(g_req_list); g_req_list = 0; } xfuse_deinit_xrdp_fs(); g_xfuse_inited = 0; return 0; } /** * * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_check_wait_objs(void) { struct fuse_chan *tmpch; int rval; if (g_ch == 0) { return 0; } if (g_sck_can_recv(g_fd, 0)) { tmpch = g_ch; rval = fuse_chan_recv(&tmpch, g_buffer, g_bufsize); if (rval == -EINTR) { return -1; } if (rval == -ENODEV) { return -1; } if (rval <= 0) { return -1; } fuse_session_process(g_se, g_buffer, rval, tmpch); } return 0; } /** * * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_get_wait_objs(tbus *objs, int *count, int *timeout) { int lcount; if (g_ch == 0) { return 0; } lcount = *count; objs[lcount] = g_fd; lcount++; *count = lcount; return 0; } /** * @brief Create specified share directory. * * This code gets called from devredir * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_create_share(tui32 device_id, const char *dirname) { int result = -1; XFS_INODE *xinode; if (dirname != NULL && strlen(dirname) > 0 && xfuse_init_xrdp_fs() == 0) { xinode = xfs_add_entry(g_xfs, FUSE_ROOT_ID, dirname, (0777 | S_IFDIR)); if (xinode == NULL) { LOG_DEVEL(LOG_LEVEL_DEBUG, "xfs_add_entry() failed"); } else { xinode->is_redirected = 1; xinode->device_id = device_id; result = 0; } } return result; } /** * @brief Remove specified share directory. * * This code gets called from devredir *****************************************************************************/ void xfuse_delete_share(tui32 device_id) { xfs_delete_redirected_entries_with_device_id(g_xfs, device_id); } /** * Clear all clipboard entries in xrdp_fs * * This function is called by clipboard code * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_clear_clip_dir(void) { int result = 0; if (g_xfs != NULL && g_clipboard_inum > 0) { xfs_remove_directory_contents(g_xfs, g_clipboard_inum); } return result; } /** * Return clipboard data to fuse * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_file_contents_range(int stream_id, const char *data, int data_bytes) { LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: stream_id=%d data_bytes=%d", stream_id, data_bytes); struct req_list_item *rli; if ((rli = (struct req_list_item *) list_get_item(g_req_list, 0)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "range error!"); return -1; } LOG_DEVEL(LOG_LEVEL_DEBUG, "lindex=%d off=%d size=%d", rli->lindex, rli->off, rli->size); fuse_reply_buf(rli->req, data, data_bytes); list_remove_item(g_req_list, 0); if (g_req_list->count <= 0) { LOG_DEVEL(LOG_LEVEL_DEBUG, "completed all requests"); return 0; } /* send next request */ rli = (struct req_list_item *) list_get_item(g_req_list, 0); if (rli == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "range error!"); return -1; } LOG_DEVEL(LOG_LEVEL_DEBUG, "requesting clipboard file data"); clipboard_request_file_data(rli->stream_id, rli->lindex, rli->off, rli->size); return 0; } /** * Create a file in .clipboard dir * * This function is called by clipboard code * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_add_clip_dir_item(const char *filename, int flags, int size, int lindex) { LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: filename=%s flags=%d size=%d lindex=%d", filename, flags, size, lindex); int result = -1; if (g_xfs == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "xfuse_add_clip_dir_item() called with no filesystem"); } else { /* add entry to xrdp_fs */ XFS_INODE *xinode = xfs_add_entry( g_xfs, g_clipboard_inum, /* parent inode */ filename, (0666 | S_IFREG)); if (xinode == NULL) { LOG(LOG_LEVEL_INFO, "failed to create file %s in xrdp filesystem", filename); } else { xinode->size = size; xinode->lindex = lindex; result = 0; } } return result; } /** * * * @return 0 on success, -1 on failure *****************************************************************************/ int xfuse_file_contents_size(int stream_id, int file_size) { LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: stream_id=%d file_size=%d", stream_id, file_size); return 0; } /***************************************************************************** ** ** ** private functions - can only be called from within this file ** ** ** *****************************************************************************/ /** * Initialize FUSE library * * @return 0 on success, -1 on failure *****************************************************************************/ static int xfuse_init_lib(struct fuse_args *args) { if (fuse_parse_cmdline(args, &g_mount_point, 0, 0) < 0) { LOG(LOG_LEVEL_ERROR, "fuse_parse_cmdline() failed"); fuse_opt_free_args(args); return -1; } if ((g_ch = fuse_mount(g_mount_point, args)) == 0) { LOG(LOG_LEVEL_ERROR, "FUSE mount on %s failed." " If %s is already mounted, you must first unmount it", g_mount_point, g_mount_point); fuse_opt_free_args(args); return -1; } g_se = fuse_lowlevel_new(args, &g_xfuse_ops, sizeof(g_xfuse_ops), 0); if (g_se == 0) { LOG(LOG_LEVEL_ERROR, "fuse_lowlevel_new() failed"); fuse_unmount(g_mount_point, g_ch); g_ch = 0; fuse_opt_free_args(args); return -1; } fuse_opt_free_args(args); fuse_session_add_chan(g_se, g_ch); g_bufsize = fuse_chan_bufsize(g_ch); g_buffer = g_new0(char, g_bufsize); g_fd = fuse_chan_fd(g_ch); g_req_list = list_create(); g_req_list->auto_free = 1; return 0; } /** * Initialize xrdp file system * * @return 0 on success, -1 on failure * *****************************************************************************/ static int xfuse_init_xrdp_fs(void) { XFS_INODE *xino; int result = -1; /* Already called? */ if (g_xfs != NULL) { result = 0; } else if ((g_xfs = xfs_create_xfs_fs(0, g_getuid(), g_getgid())) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); } else { /* Need a top-level .clipboard directory */ xino = xfs_add_entry(g_xfs, FUSE_ROOT_ID, ".clipboard", (0777 | S_IFDIR)); if (xino == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); xfs_delete_xfs_fs(g_xfs); g_xfs = NULL; } else { g_clipboard_inum = xino->inum; result = 0; } } return result; } /** * zap the xrdp file system * * @return 0 on success, -1 on failure *****************************************************************************/ static int xfuse_deinit_xrdp_fs(void) { xfs_delete_xfs_fs(g_xfs); g_xfs = NULL; g_clipboard_inum = 0; return 0; } /****************************************************************************** ** ** ** callbacks for devredir ** ** ** ******************************************************************************/ /** * Add a file or directory to xrdp file system as part of a * directory request * * If the file or directory already exists, no changes are made to it. *****************************************************************************/ void xfuse_devredir_cb_enum_dir_add_entry( struct state_dirscan *fip, const char *name, const struct file_attr *fattr) { XFS_INODE *xinode = NULL; if (!xfs_get(g_xfs, fip->pinum)) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", fip->pinum); } else if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) { ; /* filename is . or .. - don't add it */ } else { LOG_DEVEL(LOG_LEVEL_DEBUG, "parent_inode=%ld name=%s", fip->pinum, name); /* Does the file already exist ? If it does it's important we * don't mess with it, as we're only enumerating the directory, and * we don't want to disrupt any existing operations on the file */ xinode = xfs_lookup_in_dir(g_xfs, fip->pinum, name); if (xinode == NULL) { /* Add a new node to the file system */ LOG_DEVEL(LOG_LEVEL_DEBUG, "Creating name=%s in parent=%ld in xrdp_fs", name, fip->pinum); xinode = xfs_add_entry(g_xfs, fip->pinum, name, fattr->mode); if (xinode == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "xfs_add_entry() failed"); } else { xinode->size = fattr->size; xinode->atime = fattr->atime; xinode->mtime = fattr->mtime; /* Initially, set the attribute change time to the file data change time */ xinode->ctime = fattr->mtime; /* device_id is inherited from parent */ } } } } /** * This routine is called by devredir when the opendir request has * completed *****************************************************************************/ void xfuse_devredir_cb_enum_dir_done(struct state_dirscan *fip, enum NTSTATUS IoStatus) { LOG_DEVEL(LOG_LEVEL_DEBUG, "fip=%p IoStatus=0x%x", fip, IoStatus); /* * STATUS_NO_SUCH_FILE is returned for empty directories */ if (IoStatus != STATUS_SUCCESS && IoStatus != STATUS_NO_SUCH_FILE) { int status; switch (IoStatus) { case STATUS_ACCESS_DENIED: status = EACCES; break; default: status = ENOENT; } fuse_reply_err(fip->req, status); } else if (!xfs_get(g_xfs, fip->pinum)) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", fip->pinum); fuse_reply_err(fip->req, ENOENT); } else { struct fuse_file_info *fi = &fip->fi; XFUSE_HANDLE *xhandle = xfuse_handle_create(); if (xhandle == NULL || (xhandle->dir_handle = xfs_opendir(g_xfs, fip->pinum)) == NULL) { xfuse_handle_delete(xhandle); fuse_reply_err(fip->req, ENOMEM); } else { fi->fh = xfuse_handle_to_fuse_handle(xhandle); fuse_reply_open(fip->req, &fip->fi); } } free(fip); } /** * This routine is caused as a result of a devredir remote lookup * instigated by xfuse_cb_lookup() *****************************************************************************/ void xfuse_devredir_cb_lookup_entry(struct state_lookup *fip, enum NTSTATUS IoStatus, const struct file_attr *file_info) { XFS_INODE *xinode = NULL; if (IoStatus != STATUS_SUCCESS) { switch (IoStatus) { case STATUS_SHARING_VIOLATION: /* This can happen when trying to read the attributes of * some system files (e.g. pagefile.sys) */ case STATUS_ACCESS_DENIED: fuse_reply_err(fip->req, EACCES); break; case STATUS_UNSUCCESSFUL: /* Happens if we try to lookup an illegal filename (e.g. * one with a '*' in it) */ fuse_reply_err(fip->req, ENOENT); break; case STATUS_NO_SUCH_FILE: /* Remove our copy, if any */ if (fip->existing_inum && (xinode = xfs_get(g_xfs, fip->existing_inum)) != NULL && xinode->generation == fip->existing_generation) { xfs_remove_entry(g_xfs, fip->existing_inum); } fuse_reply_err(fip->req, ENOENT); break; default: LOG_DEVEL(LOG_LEVEL_INFO, "Error code %08x - fallthrough", (int) IoStatus); fuse_reply_err(fip->req, EIO); break; } } else if (!xfs_get(g_xfs, fip->pinum)) { LOG_DEVEL(LOG_LEVEL_ERROR, "parent inode %ld is not valid", fip->pinum); fuse_reply_err(fip->req, ENOENT); } else { LOG_DEVEL(LOG_LEVEL_DEBUG, "parent_inode=%ld name=%s", fip->pinum, fip->name); /* Does the file already exist ? */ xinode = xfs_lookup_in_dir(g_xfs, fip->pinum, fip->name); if (xinode != NULL) { /* Is the existing file the same type ? */ if ((xinode->mode & (S_IFREG | S_IFDIR)) == (file_info->mode & (S_IFREG | S_IFDIR))) { LOG_DEVEL(LOG_LEVEL_DEBUG, "inode=%ld name=%s already exists in xrdp_fs as %ld", fip->pinum, fip->name, xinode->inum); if (xfs_get_file_open_count(g_xfs, xinode->inum) > 0) { /* * Don't mess with open files. The local attributes are * almost certainly more up-to-date. A worst case scenario * would be truncating a file we're currently writing * to, as the lookup value for the size is stale. */ LOG_DEVEL(LOG_LEVEL_DEBUG, "inode=%ld is open - " "preferring local attributes", xinode->inum); } else { LOG_DEVEL(LOG_LEVEL_DEBUG, "Updating attributes of inode=%ld", xinode->inum); update_inode_file_attributes(file_info, TO_SET_ALL, xinode); } } else { /* Type has changed from file to directory, or vice-versa */ LOG_DEVEL(LOG_LEVEL_DEBUG, "inode=%ld name=%s of different type in xrdp_fs" " - removing", fip->pinum, xinode->name); xfs_remove_entry(g_xfs, xinode->inum); xinode = NULL; } } if (xinode == NULL) { /* Add a new node to the file system */ LOG_DEVEL(LOG_LEVEL_DEBUG, "Creating name=%s in parent=%ld in xrdp_fs", fip->name, fip->pinum); xinode = xfs_add_entry(g_xfs, fip->pinum, fip->name, file_info->mode); if (xinode == NULL) { LOG_DEVEL(LOG_LEVEL_DEBUG, "xfs_add_entry() failed"); } else { xinode->size = file_info->size; xinode->atime = file_info->atime; xinode->mtime = file_info->mtime; /* Initially, set the attribute change time to the file data change time */ xinode->ctime = file_info->mtime; /* device_id is inherited from parent */ } } if (xinode != NULL) { make_fuse_entry_reply(fip->req, xinode); } else { fuse_reply_err(fip->req, EIO); } } free(fip); } /** * This routine is caused as a result of a devredir remote setattr * instigated by xfuse_cb_setattr() *****************************************************************************/ void xfuse_devredir_cb_setattr(struct state_setattr *fip, enum NTSTATUS IoStatus) { XFS_INODE *xinode; if (IoStatus != STATUS_SUCCESS) { switch (IoStatus) { case STATUS_SHARING_VIOLATION: /* This can happen when trying to read the attributes of * some system files (e.g. pagefile.sys) */ case STATUS_ACCESS_DENIED: fuse_reply_err(fip->req, EACCES); break; case STATUS_UNSUCCESSFUL: /* Happens if we try to lookup an illegal filename */ case STATUS_NO_SUCH_FILE: fuse_reply_err(fip->req, ENOENT); break; default: LOG_DEVEL(LOG_LEVEL_INFO, "Error code %08x - fallthrough", (int) IoStatus); fuse_reply_err(fip->req, EIO); break; } } else if ((xinode = xfs_get(g_xfs, fip->inum)) == NULL) { fuse_reply_err(fip->req, ENOENT); } else { update_inode_file_attributes(&fip->fattr, fip->change_mask, xinode); make_fuse_attr_reply(fip->req, xinode); } free(fip); } /** * This routine is caused as a result of a file or directory * create request */ void xfuse_devredir_cb_create_file(struct state_create *fip, enum NTSTATUS IoStatus, tui32 DeviceId, tui32 FileId) { XFUSE_HANDLE *fh = NULL; if (IoStatus != 0) { switch (IoStatus) { case STATUS_ACCESS_DENIED: fuse_reply_err(fip->req, EACCES); break; case STATUS_OBJECT_NAME_INVALID: case STATUS_OBJECT_NAME_NOT_FOUND: fuse_reply_err(fip->req, ENOENT); break; default: fuse_reply_err(fip->req, EIO); break; } } else { if ((fip->mode & S_IFREG) != 0) { /* We've created a regular file */ /* Allocate an XFUSE_HANDLE for future file operations */ if ((fh = xfuse_handle_create()) != NULL) { /* save file handle for later use */ fh->DeviceId = DeviceId; fh->FileId = FileId; fip->fi.fh = xfuse_handle_to_fuse_handle(fh); } } if ((fip->mode & S_IFREG) != 0 && fh == NULL) { /* We failed to allocate a file handle */ LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(fip->req, ENOMEM); } else { XFS_INODE *xinode; /* create entry in xrdp file system */ xinode = xfs_add_entry(g_xfs, fip->pinum, fip->name, fip->mode); if (xinode == NULL) { /* It's possible xfs_add_entry() has failed, as the * file has already been added (by a lookup request) * in the time between our request and the response * This can happen if an 'ls' is happening in the same * directory as this file is being created. * * We'll check for this before we fail the create */ if ((xinode = xfs_lookup_in_dir(g_xfs, fip->pinum, fip->name)) != NULL) { /* * The mode should be correct anyway, but we'll * set it to the mode requested at creation */ xinode->mode = fip->mode; } } if (xinode == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "Out of memory!"); fuse_reply_err(fip->req, ENOMEM); } else { if ((fip->mode & S_IFDIR) != 0) { make_fuse_entry_reply(fip->req, xinode); } else { struct fuse_entry_param e; xfs_inode_to_fuse_entry_param(xinode, &e); fuse_reply_create(fip->req, &e, &fip->fi); xfs_increment_file_open_count(g_xfs, xinode->inum); } } } } free(fip); } /** * This routine is caused as a result of a file open request */ void xfuse_devredir_cb_open_file(struct state_open *fip, enum NTSTATUS IoStatus, tui32 DeviceId, tui32 FileId) { XFUSE_HANDLE *fh; if (IoStatus != 0) { switch (IoStatus) { case STATUS_ACCESS_DENIED: fuse_reply_err(fip->req, EACCES); break; case STATUS_OBJECT_NAME_INVALID: case STATUS_OBJECT_NAME_NOT_FOUND: fuse_reply_err(fip->req, ENOENT); break; default: fuse_reply_err(fip->req, EIO); break; } } else { /* Allocate an XFUSE_HANDLE for future file operations */ if ((fh = xfuse_handle_create()) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(fip->req, ENOMEM); } else { /* save file handle for later use */ fh->DeviceId = DeviceId; fh->FileId = FileId; fip->fi.fh = xfuse_handle_to_fuse_handle(fh); LOG_DEVEL(LOG_LEVEL_DEBUG, "sending fuse_reply_open(); " "DeviceId=%d FileId=%d req=%p", fh->DeviceId, fh->FileId, fip->req); /* update open count */ xfs_increment_file_open_count(g_xfs, fip->inum); fuse_reply_open(fip->req, &fip->fi); } } free(fip); } void xfuse_devredir_cb_read_file(struct state_read *fip, enum NTSTATUS IoStatus, const char *buf, size_t length) { if (IoStatus != STATUS_SUCCESS) { LOG_DEVEL(LOG_LEVEL_ERROR, "Read NTSTATUS is %d", (int) IoStatus); fuse_reply_err(fip->req, EIO); } else { fuse_reply_buf(fip->req, buf, length); } free(fip); } void xfuse_devredir_cb_write_file( struct state_write *fip, enum NTSTATUS IoStatus, off_t offset, size_t length) { XFS_INODE *xinode; if (IoStatus != STATUS_SUCCESS) { LOG_DEVEL(LOG_LEVEL_ERROR, "Write NTSTATUS is %d", (int) IoStatus); fuse_reply_err(fip->req, EIO); } else { off_t new_size = offset + length; fuse_reply_write(fip->req, length); /* update file size */ if ((xinode = xfs_get(g_xfs, fip->inum)) != NULL) { if (new_size > xinode->size) { xinode->size = new_size; } } else { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is invalid", fip->inum); } } free(fip); } void xfuse_devredir_cb_rmdir_or_file(struct state_remove *fip, enum NTSTATUS IoStatus) { XFS_INODE *xinode = xfs_get(g_xfs, fip->inum); switch (IoStatus) { case STATUS_SUCCESS: case STATUS_NO_SUCH_FILE: xfs_remove_entry(g_xfs, xinode->inum); /* Remove local copy */ fuse_reply_err(fip->req, 0); break; case STATUS_SHARING_VIOLATION: case STATUS_ACCESS_DENIED: fuse_reply_err(fip->req, EACCES); break; default: LOG_DEVEL(LOG_LEVEL_INFO, "Error code %08x - fallthrough", (int) IoStatus); fuse_reply_err(fip->req, EBADF); break; } free(fip); } void xfuse_devredir_cb_rename_file(struct state_rename *fip, enum NTSTATUS IoStatus) { int status; if (IoStatus != STATUS_SUCCESS) { status = (IoStatus == STATUS_SHARING_VIOLATION) ? EBUSY : (IoStatus == STATUS_ACCESS_DENIED) ? EACCES : /* default */ EEXIST ; } else { status = xfs_move_entry(g_xfs, fip->pinum, fip->new_pinum, fip->name); } fuse_reply_err(fip->req, status); free(fip); } void xfuse_devredir_cb_file_close(struct state_close *fip) { fuse_reply_err(fip->req, 0); xfs_decrement_file_open_count(g_xfs, fip->inum); free(fip); } /* * Determine is a file is in the FUSE filesystem * * The whole purpose of this function is to avoid deadlocks. If we try * to determine the size (or any other attribute) of a file in the FUSE * filesystem, the kernel will block the calling thread and send a message * to libfuse. Since we're single-threaded, this will result in deadlock. * * Ideally we'd use a stat() of the file to compare with a stat() of * the mountpoint. This won't work however, for the reasons outline above. * * A simple implementation might be to compare the start of the filename * with the FUSE mount point. This will work, but can easily be defeated, * (perhaps unintentionally) by symlinks in the path. * * The approach taken here is to record the basename of the FUSE mount * point directory (e.g. 'thinclient_drives') and look for that in the * passed-in filename as a directory element. If we find it, we look * at the device name and inode number of the parent directory of the * FUSE mount point. If these match we consider the filename to be in * the FUSE filesystem. */ int xfuse_path_in_xfuse_fs(const char *path) { char *wpath = NULL; /* Writeable copy of path */ char *p; int blen = g_strlen(g_fuse_root_path_basename); int rv = 0; if ((wpath = g_strdup(path)) == NULL) { LOG(LOG_LEVEL_ERROR, "system out of memory"); } else { /* Look ahead in the string for the basename of the FUSE mount point */ for (p = wpath; (p = g_strstr(p, g_fuse_root_path_basename)) != NULL ; p = p + 1) { /* Is this string preceded by a '/' ? */ if (p == wpath || *(p - 1) != '/') { continue; } /* Is the string followed by a '/' or '\0' ? We know it's * valid to look ahead this far in the string, as g_strstr() * tells us it is */ if (*(p + blen) != '/' && *(p + blen) != '\0') { continue; } /* Temporarily terminate the string early, and check the * file system characteristics of the preceding directory */ *(p - 1) = '\0'; if (g_file_get_device_number(wpath) == g_fuse_root_parent_dev && g_file_get_inode_num(wpath) == g_fuse_root_parent_ino) { rv = 1; break; } *(p - 1) = '/'; } g_free(wpath); } return rv; } /****************************************************************************** ** ** ** callbacks for fuse ** ** ** ******************************************************************************/ /** * Look up a directory entry by name and get its attributes *****************************************************************************/ static void xfuse_cb_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { XFS_INODE *parent_xinode; XFS_INODE *xinode = NULL; LOG_DEVEL(LOG_LEVEL_DEBUG, "looking for parent=%ld name=%s", parent, name); if (strlen(name) > XFS_MAXFILENAMELEN) { fuse_reply_err(req, ENAMETOOLONG); } else if ((parent_xinode = xfs_get(g_xfs, parent)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", parent); fuse_reply_err(req, ENOENT); } else { if (!parent_xinode->is_redirected) { /* File cannot be remote - we either know about it or we don't */ if ((xinode = xfs_lookup_in_dir(g_xfs, parent, name)) != NULL) { LOG_DEVEL(LOG_LEVEL_DEBUG, "found entry for parent=%ld name=%s", parent, name); make_fuse_entry_reply(req, xinode); } else { fuse_reply_err(req, ENOENT); } } else { /* specified file resides on redirected share * * We always look these up, and relying on libfuse to do sane * caching */ struct state_lookup *fip = g_new0(struct state_lookup, 1); char *full_path = get_name_for_entry_in_parent(parent, name); if (fip == NULL || full_path == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); free(fip); free(full_path); } else { const char *cptr; fip->req = req; fip->pinum = parent; strcpy(fip->name, name); /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* If the file already exists on our side, save the inum * and generation. If it's not remote any more this means we * can remove it when we get the response */ if ((xinode = xfs_lookup_in_dir(g_xfs, parent, name)) != NULL) { fip->existing_inum = xinode->inum; fip->existing_generation = xinode->generation; } LOG_DEVEL(LOG_LEVEL_DEBUG, "Looking up %s in %s on %d", name, cptr, parent_xinode->device_id); /* * If this call succeeds, further request processing happens in * xfuse_devredir_cb_lookup_entry() */ if (devredir_lookup_entry(fip, parent_xinode->device_id, cptr)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_lookup_entry() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } free(full_path); } } } } /** * Get file attributes * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { XFS_INODE *xino; LOG_DEVEL(LOG_LEVEL_DEBUG, "req=%p ino=%ld", req, ino); /* if ino is not valid, just return */ if ((xino = xfs_get(g_xfs, ino)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else { make_fuse_attr_reply(req, xino); } } /** * *****************************************************************************/ /* * Adds an entry to the buffer, using fuse_add_direnty() * * Returns 1 for success, or zero if the entry couldn't be added */ static int xfuse_dirbuf_add1(fuse_req_t req, struct dirbuf1 *b, XFS_INODE *xinode, off_t offset) { struct stat stbuf; size_t len; int result = 0; memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_ino = xinode->inum; stbuf.st_mode = xinode->mode & ~g_cfg->file_umask; /* * Try to add the entry. From the docs for fuse_add_direntry():- * "Buffer needs to be large enough to hold the entry. If it's not, * then the entry is not filled in but the size of the entry is * still returned." */ len = fuse_add_direntry(req, &b->buf[b->len], /* index where new entry will be added to buf */ sizeof(b->buf) - b->len, /* Space left */ xinode->name, /* name of entry */ &stbuf, /* file attributes */ offset /* offset of next entry */ ); if (len + b->len <= sizeof(b->buf)) { /* Entry fitted in OK */ b->len += len; result = 1; } return result; } /** * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { XFS_INODE *xinode; XFUSE_HANDLE *xhandle; struct xfs_dir_handle *dh; struct dirbuf1 b; LOG_DEVEL(LOG_LEVEL_DEBUG, "req=%p inode=%ld size=%zd offset=%lld", req, ino, size, (long long) off); /* On the first call, check the inode is valid */ if (off == 0 && !xfs_get(g_xfs, ino)) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else if ((xhandle = xfuse_handle_from_fuse_handle(fi->fh)) == NULL || (dh = xhandle->dir_handle) == NULL) { /* something seriously wrong somewhere! */ fuse_reply_buf(req, 0, 0); } else { b.len = 0; off_t new_off = off; while ((xinode = xfs_readdir(g_xfs, dh, &new_off)) != NULL) { if (xfuse_dirbuf_add1(req, &b, xinode, new_off) == 0) { break; /* buffer is full */ } /* Make sure we get the next entry next time */ off = new_off; } fuse_reply_buf(req, b.buf, b.len); } } /** * Create a directory *****************************************************************************/ static void xfuse_cb_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) { XFS_INODE *xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: parent_inode=%ld name=%s", parent, name); if ((xinode = xfs_lookup_in_dir(g_xfs, parent, name)) != NULL) { /* dir already exists, just return it */ make_fuse_entry_reply(req, xinode); } else { /* dir does not exist, create it */ xfuse_create_dir_or_file(req, parent, name, mode | S_IFDIR, NULL); } } /** * Remove a dir or file * *****************************************************************************/ static void xfuse_cb_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) { XFS_INODE *xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: parent=%ld name=%s", parent, name); if (strlen(name) > XFS_MAXFILENAMELEN) { fuse_reply_err(req, ENAMETOOLONG); } else if ((xinode = xfs_lookup_in_dir(g_xfs, parent, name)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "did not find file with pinode=%ld name=%s", parent, name); fuse_reply_err(req, ENOENT); } else if ((xinode->mode & S_IFDIR) != 0 && !xfs_is_dir_empty(g_xfs, xinode->inum)) { LOG_DEVEL(LOG_LEVEL_DEBUG, "cannot rmdir; directory is not empty"); fuse_reply_err(req, ENOTEMPTY); } else if (!xinode->is_redirected) { /* specified file is a local resource */ //XFUSE_HANDLE *fh; LOG_DEVEL(LOG_LEVEL_DEBUG, "LK_TODO: this is still a TODO"); fuse_reply_err(req, EROFS); } else { /* specified file resides on redirected share */ struct state_remove *fip = g_new0(struct state_remove, 1); char *full_path = xfs_get_full_path(g_xfs, xinode->inum); if (!full_path || !fip) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); free(fip); } else { const char *cptr; fip->req = req; fip->inum = xinode->inum; /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* get devredir to open the remote file * * If this call succeeds, further request processing happens in * xfuse_devredir_cb_rmdir_or_file() */ if (devredir_rmdir_or_file(fip, xinode->device_id, cptr)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_rmdir_or_file() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } } free(full_path); } } static void xfuse_cb_rename(fuse_req_t req, fuse_ino_t old_parent, const char *old_name, fuse_ino_t new_parent, const char *new_name) { XFS_INODE *old_xinode; XFS_INODE *new_parent_xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: old_parent=%ld old_name=%s new_parent=%ld new_name=%s", old_parent, old_name, new_parent, new_name); if (strlen(old_name) > XFS_MAXFILENAMELEN || strlen(new_name) > XFS_MAXFILENAMELEN) { fuse_reply_err(req, ENAMETOOLONG); } else if (!(old_xinode = xfs_lookup_in_dir(g_xfs, old_parent, old_name))) { LOG_DEVEL(LOG_LEVEL_ERROR, "did not find file with pinode=%ld name=%s", old_parent, old_name); fuse_reply_err(req, ENOENT); } else if (!(new_parent_xinode = xfs_get(g_xfs, new_parent))) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", new_parent); fuse_reply_err(req, ENOENT); } else if (!xfs_check_move_entry(g_xfs, old_xinode->inum, new_parent, new_name)) { /* Catchall -see rename(2). Fix when logging is improved */ fuse_reply_err(req, EINVAL); } else if (new_parent_xinode->is_redirected != old_xinode->is_redirected || new_parent_xinode->device_id != old_xinode->device_id) { fuse_reply_err(req, EXDEV); } else if (!old_xinode->is_redirected) { /* specified file is a local resource */ LOG_DEVEL(LOG_LEVEL_DEBUG, "LK_TODO: this is still a TODO"); fuse_reply_err(req, EROFS); } else { /* resource is on a redirected share */ struct state_rename *fip = g_new0(struct state_rename, 1); char *old_full_path = xfs_get_full_path(g_xfs, old_xinode->inum); char *new_full_path = get_name_for_entry_in_parent(new_parent, new_name); if (!old_full_path || !new_full_path || !fip) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); free(fip); free(old_full_path); free(new_full_path); } else { const char *cptr; const char *cp; fip->req = req; fip->pinum = old_xinode->inum; fip->new_pinum = new_parent; strcpy(fip->name, new_name); /* we want path minus 'root node of the share' */ cptr = filename_on_device(old_full_path); cp = filename_on_device(new_full_path); /* * If this call succeeds, further request processing happens in * xfuse_devredir_cb_rename_file() */ if (devredir_file_rename(fip, old_xinode->device_id, cptr, cp)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_file_rename() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } free(old_full_path); free(new_full_path); } } } /** * Create a directory or file * * @param req opaque FUSE object * @param parent parent inode * @param name name of dir or file to create * @param mode creation mode * @param fi File info from fuse_lowlevel_ops.create, or NULL. * This may need to be copied if we're not replying immediately *****************************************************************************/ static void xfuse_create_dir_or_file(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi) { XFS_INODE *xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: parent_ino=%ld name=%s mode=%o type=%s", parent, name, mode, (mode & S_IFDIR) ? "dir" : "file"); /* name must be valid */ if (strlen(name) > XFS_MAXFILENAMELEN) { fuse_reply_err(req, ENAMETOOLONG); } else { /* Sanitise the mode * * Currently group/world write is not allowed. */ if (mode & S_IFDIR) { mode = (mode & 0777) | S_IFDIR; } else { mode = (mode & 0777) | S_IFREG; } /* is parent inode valid? */ if (parent == FUSE_ROOT_ID) { fuse_reply_err(req, EROFS); } else if ((xinode = xfs_get(g_xfs, parent)) == NULL) { fuse_reply_err(req, ENOENT); } else if ((xinode->mode & S_IFDIR) == 0) { fuse_reply_err(req, ENOTDIR); } else if (!xinode->is_redirected) { /* specified file is a local resource */ //XFUSE_HANDLE *fh; LOG_DEVEL(LOG_LEVEL_DEBUG, "LK_TODO: this is still a TODO"); fuse_reply_err(req, EROFS); } else { struct state_create *fip = g_new0(struct state_create, 1); char *full_path = get_name_for_entry_in_parent(parent, name); if (full_path == NULL || fip == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "Out of memory"); fuse_reply_err(req, ENOMEM); free(fip); free(full_path); } else { const char *cptr; fip->req = req; if (fi != NULL) { fip->fi = *fi; } fip->pinum = parent; strcpy(fip->name, name); fip->mode = mode; /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* * If this call succeeds, further request processing happens * in xfuse_devredir_cb_create_file() */ if (devredir_file_create(fip, xinode->device_id, cptr, mode)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_file_create() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } free(full_path); } } } } /** * Open specified file * * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { XFS_INODE *xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: ino=%ld", ino); if (!(xinode = xfs_get(g_xfs, ino))) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else if (xinode->mode & S_IFDIR) { /* Can't open directories like this */ LOG_DEVEL(LOG_LEVEL_DEBUG, "reading/writing a dir not allowed!"); fuse_reply_err(req, EISDIR); } else if ((fi->flags & O_ACCMODE) != O_RDONLY && (fi->flags & O_ACCMODE) != O_WRONLY && (fi->flags & O_ACCMODE) != O_RDWR) { LOG_DEVEL(LOG_LEVEL_DEBUG, "Invalid access mode specified"); fuse_reply_err(req, EINVAL); } else if (!xinode->is_redirected) { /* specified file is a local resource */ if ((fi->flags & O_ACCMODE) != O_RDONLY) { fuse_reply_err(req, EROFS); } else { XFUSE_HANDLE *fh = xfuse_handle_create(); fh->is_loc_resource = 1; fi->fh = xfuse_handle_to_fuse_handle(fh); fuse_reply_open(req, fi); } } else { /* specified file resides on redirected share */ struct state_open *fip = g_new0(struct state_open, 1); char *full_path = xfs_get_full_path(g_xfs, ino); if (!full_path || !fip) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); free(fip); free(full_path); } else { const char *cptr; fip->req = req; fip->fi = *fi; fip->inum = ino; /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* get devredir to open the remote file * * If the caller has set O_TRUNC when writing the file, * fuse should call us back via fuse_cb_setattr() to set * the size to zero - we don't need to do this ourselves. * * If this call succeeds, further request processing happens in * xfuse_devredir_cb_open_file() */ if (devredir_file_open(fip, xinode->device_id, cptr, fi->flags)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_file_open() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } free(full_path); } } } /* * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately */ static void xfuse_cb_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { XFS_INODE *xinode; XFUSE_HANDLE *handle = xfuse_handle_from_fuse_handle(fi->fh); LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: ino=%ld fi=%p fi->fh=0x%llx", ino, fi, (long long) fi->fh); if ((xinode = xfs_get(g_xfs, ino)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else if (!xinode->is_redirected) { /* specified file is a local resource */ fuse_reply_err(req, 0); } else { /* specified file resides on redirected share */ struct state_close *fip = g_new0(struct state_close, 1); if (fip == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); return; } fip->req = req; fip->inum = ino; fip->fi = *fi; fi->fh = xfuse_handle_to_fuse_handle(NULL); /* * If this call succeeds, further request processing happens in * xfuse_devredir_cb_file_close() */ if (devredir_file_close(fip, xinode->device_id, handle->FileId)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_close_file() cmd"); fuse_reply_err(req, EREMOTEIO); free(fip); } xfuse_handle_delete(handle); } } /** * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { XFUSE_HANDLE *fh; struct state_read *fusep; XFS_INODE *xinode; struct req_list_item *rli; LOG_DEVEL(LOG_LEVEL_DEBUG, "want_bytes %zd bytes at off %lld", size, (long long) off); if ((fh = xfuse_handle_from_fuse_handle(fi->fh)) == NULL) { fuse_reply_err(req, EINVAL); } else if (fh->is_loc_resource) { /* target file is in .clipboard dir */ LOG_DEVEL(LOG_LEVEL_DEBUG, "target file is in .clipboard dir"); if ((xinode = xfs_get(g_xfs, ino)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "ino does not exist in xrdp_fs"); fuse_reply_buf(req, 0, 0); return; } rli = g_new0(struct req_list_item, 1); rli->stream_id = 0; rli->req = req; rli->lindex = xinode->lindex; rli->off = off; rli->size = size; list_add_item(g_req_list, (tbus) rli); if (g_req_list->count == 1) { LOG_DEVEL(LOG_LEVEL_DEBUG, "requesting clipboard file data lindex = %d off = %lld size = %zd", rli->lindex, (long long) off, size); clipboard_request_file_data(rli->stream_id, rli->lindex, (int) off, (int) size); } } else { /* target file is on a remote device */ fusep = g_new0(struct state_read, 1); if (fusep == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); } else { fusep->req = req; /* * If this call succeeds, further request processing happens in * xfuse_devredir_cb_read_file() */ devredir_file_read(fusep, fh->DeviceId, fh->FileId, size, off); } } } /** * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) { XFUSE_HANDLE *fh; struct state_write *fusep; LOG_DEVEL(LOG_LEVEL_DEBUG, "write %zd bytes at off %lld to inode=%ld", size, (long long) off, ino); if ((fh = xfuse_handle_from_fuse_handle(fi->fh)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "file handle fi->fh is NULL"); fuse_reply_err(req, EINVAL); } else if (fh->is_loc_resource) { /* target file is in .clipboard dir */ LOG_DEVEL(LOG_LEVEL_DEBUG, "THIS IS STILL A TODO!"); fuse_reply_err(req, EROFS); } else { /* target file is on a remote device */ fusep = g_new0(struct state_write, 1); if (fusep == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); } else { fusep->req = req; fusep->inum = ino; /* * If this call succeeds, further request processing happens in * xfuse_devredir_cb_write_file() */ devredir_file_write(fusep, fh->DeviceId, fh->FileId, buf, size, off); } } } /** *****************************************************************************/ /* * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately */ static void xfuse_cb_create(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi) { LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: parent_inode=%ld, name=%s fi=%p", parent, name, fi); xfuse_create_dir_or_file(req, parent, name, mode & ~S_IFDIR, fi); } /** *****************************************************************************/ #if 0 static void xfuse_cb_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi) { LOG_DEVEL(LOG_LEVEL_DEBUG, "#################### entered: ino=%ld datasync=%d", ino, datasync); LOG_DEVEL(LOG_LEVEL_DEBUG, "function not required"); fuse_reply_err(req, EINVAL); } #endif /** * Sets attributes for a directory entry. * * If the file resides on a remote share, devredir * is asked to update it. This will result in the following * callbacks:- * - xfuse_devredir_cb_setattr() to update our copy and return status * * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) { XFS_INODE *xinode; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered to_set=0x%x", to_set); if ((xinode = xfs_get(g_xfs, ino)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else if (((to_set & FUSE_SET_ATTR_UID) && attr->st_uid != xinode->uid) || ((to_set & FUSE_SET_ATTR_GID) && attr->st_gid != xinode->gid)) { /* We don't allow any of these */ fuse_reply_err(req, EPERM); } else if ((to_set & FUSE_SET_ATTR_MODE) && (attr->st_mode & ~(0777 | S_IFDIR | S_IFREG)) != 0) { /* We only support standard mode bits and S_IFDIR / S_IFREG */ LOG_DEVEL(LOG_LEVEL_ERROR, "Asking for invalid mode bits 0%o to be set", attr->st_mode); fuse_reply_err(req, EINVAL); } else { struct file_attr attrs = {0}; tui32 change_mask = 0; if ((to_set & FUSE_SET_ATTR_MODE) && xinode->mode != attr->st_mode) { attrs.mode = attr->st_mode & (0777 | S_IFDIR | S_IFREG); change_mask |= TO_SET_MODE; } if ((to_set & FUSE_SET_ATTR_SIZE) && xinode->size != attr->st_size) { attrs.size = attr->st_size; change_mask |= TO_SET_SIZE; } if ((to_set & FUSE_SET_ATTR_ATIME) && xinode->atime != attr->st_atime) { attrs.atime = attr->st_atime; change_mask |= TO_SET_ATIME; } if ((to_set & FUSE_SET_ATTR_MTIME) && xinode->mtime != attr->st_mtime) { attrs.mtime = attr->st_mtime; change_mask |= TO_SET_MTIME; } if (change_mask == 0) { /* No changes have been made */ make_fuse_attr_reply(req, xinode); } else if (!xinode->is_redirected) { /* Update the local fs */ update_inode_file_attributes(&attrs, change_mask, xinode); make_fuse_attr_reply(req, xinode); } else { struct state_setattr *fip = g_new0(struct state_setattr, 1); char *full_path = xfs_get_full_path(g_xfs, ino); if (!full_path || !fip) { LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); fuse_reply_err(req, ENOMEM); free(fip); free(full_path); } else { const char *cptr; fip->req = req; fip->inum = ino; /* Save the important stuff so we can update our node if the * remote update is successful */ fip->fattr = attrs; fip->change_mask = change_mask; /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* * If this call succeeds, further request processing happens * in xfuse_devredir_cb_setattr() */ if (devredir_setattr_for_entry(fip, xinode->device_id, cptr, &attrs, change_mask) < 0) { fuse_reply_err(req, EIO); free(fip); } free(full_path); } } } } /** * Get dir listing * * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { XFS_INODE *xinode; XFUSE_HANDLE *xhandle; LOG_DEVEL(LOG_LEVEL_DEBUG, "inode=%ld", ino); if ((xinode = xfs_get(g_xfs, ino)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "inode %ld is not valid", ino); fuse_reply_err(req, ENOENT); } else if (!xinode->is_redirected) { if ((xhandle = xfuse_handle_create()) == NULL) { fuse_reply_err(req, ENOMEM); } else { if ((xhandle->dir_handle = xfs_opendir(g_xfs, ino)) == NULL) { xfuse_handle_delete(xhandle); fuse_reply_err(req, ENOMEM); } else { fi->fh = xfuse_handle_to_fuse_handle(xhandle); fuse_reply_open(req, fi); } } } else { LOG_DEVEL(LOG_LEVEL_DEBUG, "did not find entry; redirecting call to devredir"); struct state_dirscan *fip = g_new0(struct state_dirscan, 1); char *full_path = xfs_get_full_path(g_xfs, ino); if (full_path == NULL || fip == NULL) { fuse_reply_err(req, ENOMEM); free(fip); free(full_path); } else { const char *cptr; LOG_DEVEL(LOG_LEVEL_DEBUG, "dev_id=%d ino=%ld full_path=%s", xinode->device_id, ino, full_path); fip->req = req; fip->pinum = ino; fip->fi = *fi; /* we want path minus 'root node of the share' */ cptr = filename_on_device(full_path); /* * If this call succeeds, further request processing happens in:- * - xfuse_devredir_cb_enum_dir_add_entry() * Called for every file in the directory * - xfuse_devredir_cb_enum_dir_done() * Called at the end of the directory scan */ if (devredir_get_dir_listing(fip, xinode->device_id, cptr)) { LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_get_dir_listing() cmd"); fuse_reply_buf(req, NULL, 0); free(fip); } free(full_path); } } } /** * GOTCHA : For FUSE 2.9 at least, the 'fi' parameter is allocated on the * stack by the caller, so must be copied if we're not using it * to reply to FUSE immediately *****************************************************************************/ static void xfuse_cb_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { XFUSE_HANDLE *xhandle = xfuse_handle_from_fuse_handle(fi->fh); xfs_closedir(g_xfs, xhandle->dir_handle); xhandle->dir_handle = NULL; xfuse_handle_delete(xhandle); fuse_reply_err(req, 0); } /****************************************************************************** * miscellaneous functions *****************************************************************************/ static void xfs_inode_to_fuse_entry_param(const XFS_INODE *xinode, struct fuse_entry_param *e) { memset(e, 0, sizeof(*e)); e->ino = xinode->inum; e->attr_timeout = XFUSE_ATTR_TIMEOUT; e->entry_timeout = XFUSE_ENTRY_TIMEOUT; e->attr.st_ino = xinode->inum; e->attr.st_mode = xinode->mode & ~g_cfg->file_umask; e->attr.st_nlink = 1; e->attr.st_uid = xinode->uid; e->attr.st_gid = xinode->gid; e->attr.st_size = xinode->size; e->attr.st_atime = xinode->atime; e->attr.st_mtime = xinode->mtime; e->attr.st_ctime = xinode->ctime; e->generation = xinode->generation; } static void make_fuse_entry_reply(fuse_req_t req, const XFS_INODE *xinode) { struct fuse_entry_param e; xfs_inode_to_fuse_entry_param(xinode, &e); fuse_reply_entry(req, &e); } static void make_fuse_attr_reply(fuse_req_t req, const XFS_INODE *xinode) { struct stat st; memset(&st, 0, sizeof(st)); st.st_ino = xinode->inum; st.st_mode = xinode->mode & ~g_cfg->file_umask; st.st_nlink = 1; st.st_uid = xinode->uid; st.st_gid = xinode->gid; st.st_size = xinode->size; st.st_atime = xinode->atime; st.st_mtime = xinode->mtime; st.st_ctime = xinode->ctime; fuse_reply_attr(req, &st, XFUSE_ATTR_TIMEOUT); } /* * Get the name of a file on the device * * For redirected devices, the routine xfs_get_full_path() returns names * like "/C:/Windows/System32". * This routine simply returns a pointer to the part of the name following * the device specification. */ static const char *filename_on_device(const char *full_path) { const char *result = NULL; if (full_path[0] != '\0') { result = strchr(full_path + 1, '/'); } return result ? result : "/"; } /* * Updates attributes on the filesystem, and bumps the inode ctime * * This call is used to set attributes, either locally, or following a * setattr devredir call */ static void update_inode_file_attributes(const struct file_attr *fattr, tui32 change_mask, XFS_INODE *xinode) { int updated = 0; if ((change_mask & TO_SET_MODE) != 0 && xinode->mode != fattr->mode) { xinode->mode = fattr->mode; updated = 1; } if ((change_mask & TO_SET_SIZE) != 0 && xinode->size != fattr->size) { xinode->size = fattr->size; updated = 1; } if ((change_mask & TO_SET_ATIME) != 0 && xinode->atime != fattr->atime) { xinode->atime = fattr->atime; updated = 1; } if ((change_mask & TO_SET_MTIME) != 0 && xinode->mtime != fattr->mtime) { xinode->mtime = fattr->mtime; updated = 1; } if (updated) { xinode->ctime = time(0); } } /* * Gets the name for a file where we know the parent inode and the * name for the file under that inode * * Result must be freed after use */ static char *get_name_for_entry_in_parent(fuse_ino_t parent, const char *name) { char *result; if ((result = xfs_get_full_path(g_xfs, parent)) != NULL) { char *p = (char *) realloc(result, strlen(result) + 1 + strlen(name) + 1); if (p == NULL) { /* See cppcheck trac #9292 and #9437 */ /* cppcheck-suppress doubleFree symbolName=result */ free(result); result = NULL; } else { result = p; strcat(result, "/"); strcat(result, name); } } return result; } /* * Scans a user-provided string substituting %u/%U/ for UID/username * and %d/%D fordisplaynumber/DISPLAY */ static unsigned int format_user_info(char *dest, unsigned int len, const char *format) { char uidstr[64]; char username[64]; char display[64]; char displaynum[64]; const struct info_string_tag map[] = { {'u', uidstr}, {'U', username}, {'d', displaynum}, {'D', display}, INFO_STRING_END_OF_LIST }; int uid = g_getuid(); g_snprintf(uidstr, sizeof(uidstr), "%d", uid); if (g_getlogin(username, sizeof(username)) != 0) { /* Fall back to UID */ g_strncpy(username, uidstr, sizeof(username) - 1); } if (getenv("DISPLAY") == NULL) { /* if environment variable is not set, set it to empty string */ display[0] = '\0'; displaynum[0] = '\0'; } else { g_strncpy(display, getenv("DISPLAY"), sizeof(display) - 1); g_snprintf(displaynum, sizeof(displaynum), "%d", g_get_display_num_from_display(display)); } return g_format_info_string(dest, len, format, map); } #endif /* end else #ifndef XRDP_FUSE */