From 2815c63f670c770616880e7e85d579f2245b1028 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:35:04 +0000 Subject: [PATCH 1/2] Add support for statvfs() to FUSE Some desktop environments are now checking for free space before copying files to a destination. To support this, the FUSE filesystem needs to convert the statvfs() system call to the relevent PDUs from [MS-RDPEFS] (cherry picked from commit 5dc4fdbc2cdd8557a91ec0bec67f6a9f4dff7302) --- common/ms-erref.h | 1 + common/ms-fscc.h | 22 +++- sesman/chansrv/chansrv_fuse.c | 90 ++++++++++++++++ sesman/chansrv/chansrv_fuse.h | 6 ++ sesman/chansrv/devredir.c | 190 +++++++++++++++++++++++++++++++++- sesman/chansrv/devredir.h | 4 + 6 files changed, 309 insertions(+), 4 deletions(-) diff --git a/common/ms-erref.h b/common/ms-erref.h index 394766a1ad..47a8399b33 100644 --- a/common/ms-erref.h +++ b/common/ms-erref.h @@ -32,6 +32,7 @@ enum NTSTATUS STATUS_NO_MORE_FILES = 0x80000006, STATUS_UNSUCCESSFUL = 0xc0000001, + STATUS_INFO_LENGTH_MISMATCH = 0xc0000004, STATUS_NO_SUCH_FILE = 0xc000000f, STATUS_ACCESS_DENIED = 0xc0000022, STATUS_OBJECT_NAME_INVALID = 0xc0000033, diff --git a/common/ms-fscc.h b/common/ms-fscc.h index 2f8cc20174..781d646370 100644 --- a/common/ms-fscc.h +++ b/common/ms-fscc.h @@ -30,7 +30,7 @@ /* * File information classes (section 2.4) */ -enum FS_INFORMATION_CLASS +enum FILE_INFORMATION_CLASS { FileAllocationInformation = 19, /* Set */ FileBasicInformation = 4, /* Query, Set */ @@ -52,6 +52,26 @@ enum FS_INFORMATION_CLASS #define FILE_STD_INFORMATION_SIZE 22 #define FILE_END_OF_FILE_INFORMATION_SIZE 8 +/* + * File System information classes (section 2.5) + */ +enum FILE_SYSTEM_INFORMATION_CLASS +{ + FileFsVolumeInformation = 1, + FileFsSizeInformation = 3, + FileFsDeviceInformation = 4, + FileFsAttributeInformation = 5, + FileFsFullSizeInformation = 7 +}; + +/* + * Size of structs above without trailing RESERVED fields (MS-RDPEFS + * 2.2.3.3.6) + */ +#define FILE_FS_SIZE_INFORMATION_SIZE 24 +#define FILE_FS_DEVICE_INFORMATION_SIZE 8 +#define FILE_FS_FULL_SIZE_INFORMATION_SIZE 32 + /* Windows file attributes (section 2.6) */ #define W_FILE_ATTRIBUTE_DIRECTORY 0x00000010 #define W_FILE_ATTRIBUTE_READONLY 0x00000001 diff --git a/sesman/chansrv/chansrv_fuse.c b/sesman/chansrv/chansrv_fuse.c index d8a2c17919..4d3175c53a 100644 --- a/sesman/chansrv/chansrv_fuse.c +++ b/sesman/chansrv/chansrv_fuse.c @@ -127,6 +127,11 @@ void xfuse_devredir_cb_rename_file(struct state_rename *fip, void xfuse_devredir_cb_file_close(struct state_close *fip) {} +void xfuse_devredir_cb_statfs(struct state_statfs *fip, + const struct statvfs *fss, + enum NTSTATUS IoStatus) +{} + int xfuse_path_in_xfuse_fs(const char *path) { return 0; @@ -294,6 +299,15 @@ struct state_close fuse_ino_t inum; /* inum of file to open */ }; +/* + * Record type used to maintain state when running a statfs + */ +struct state_statfs +{ + fuse_req_t req; /* Original FUSE request from statfs */ +}; + + struct xfuse_handle { tui32 DeviceId; @@ -400,6 +414,8 @@ static void xfuse_cb_opendir(fuse_req_t req, fuse_ino_t ino, static void xfuse_cb_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); +static void xfuse_cb_statfs(fuse_req_t req, fuse_ino_t ino); + /* miscellaneous functions */ static void xfs_inode_to_fuse_entry_param(const XFS_INODE *xinode, struct fuse_entry_param *e); @@ -602,6 +618,7 @@ xfuse_init(void) g_xfuse_ops.setattr = xfuse_cb_setattr; g_xfuse_ops.opendir = xfuse_cb_opendir; g_xfuse_ops.releasedir = xfuse_cb_releasedir; + g_xfuse_ops.statfs = xfuse_cb_statfs; fuse_opt_add_arg(&args, "xrdp-chansrv"); fuse_opt_add_arg(&args, g_fuse_root_path); @@ -1550,6 +1567,26 @@ void xfuse_devredir_cb_file_close(struct state_close *fip) free(fip); } +void xfuse_devredir_cb_statfs(struct state_statfs *fip, + const struct statvfs *fss, + enum NTSTATUS IoStatus) +{ + int status; + if (IoStatus != STATUS_SUCCESS) + { + status = + (IoStatus == STATUS_ACCESS_DENIED) ? EACCES : + /* default */ EIO ; + fuse_reply_err(fip->req, status); + } + else + { + fuse_reply_statfs(fip->req, fss); + } + + free(fip); +} + /* * Determine is a file is in the FUSE filesystem * @@ -2660,6 +2697,59 @@ static void xfuse_cb_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_reply_err(req, 0); } +/*****************************************************************************/ +static void xfuse_cb_statfs(fuse_req_t req, fuse_ino_t ino) +{ + 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->is_redirected) + { + /* specified file is a local resource */ + struct statvfs vfs_stats = {0}; + fuse_reply_statfs(req, &vfs_stats); + } + else + { + /* specified file resides on redirected share */ + + struct state_statfs *fip = g_new0(struct state_statfs, 1); + char *full_path = xfs_get_full_path(g_xfs, ino); + if (full_path == NULL || fip == NULL) + { + LOG_DEVEL(LOG_LEVEL_ERROR, "system out of memory"); + fuse_reply_err(req, ENOMEM); + free(full_path); + free(fip); + } + else + { + const char *cptr; + fip->req = req; + + /* get devredir to statfs the filesystem for the file + * + * If this call succeeds, further request processing happens in + * xfuse_devredir_cb_statfs() + */ + cptr = filename_on_device(full_path); + if (devredir_statfs(fip, xinode->device_id, cptr)) + { + LOG_DEVEL(LOG_LEVEL_ERROR, "failed to send devredir_statfs() cmd"); + fuse_reply_err(req, EREMOTEIO); + free(fip); + } + free(full_path); + } + } +} + /****************************************************************************** * miscellaneous functions *****************************************************************************/ diff --git a/sesman/chansrv/chansrv_fuse.h b/sesman/chansrv/chansrv_fuse.h index a456bc9033..ae38356633 100644 --- a/sesman/chansrv/chansrv_fuse.h +++ b/sesman/chansrv/chansrv_fuse.h @@ -72,6 +72,8 @@ struct state_write; struct state_remove; struct state_rename; struct state_close; +struct statvfs; // OS structure defined in +struct state_statfs; /* functions that are invoked from devredir */ @@ -114,6 +116,10 @@ void xfuse_devredir_cb_rename_file(struct state_rename *fip, void xfuse_devredir_cb_file_close(struct state_close *fip); +void xfuse_devredir_cb_statfs(struct state_statfs *fip, + const struct statvfs *fss, + enum NTSTATUS IoStatus); + /* * Returns true if a filesystem path lies in the FUSE filesystem * diff --git a/sesman/chansrv/devredir.c b/sesman/chansrv/devredir.c index e33e2f358c..749a2c3653 100644 --- a/sesman/chansrv/devredir.c +++ b/sesman/chansrv/devredir.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -93,7 +94,9 @@ enum COMPLETION_TYPE CID_RENAME_FILE, CID_RENAME_FILE_RESP, CID_LOOKUP, - CID_SETATTR + CID_SETATTR, + CID_STATFS, + CID_STATFS_RESP }; @@ -135,6 +138,12 @@ static void devredir_proc_cid_lookup( IRP *irp, static void devredir_proc_cid_setattr( IRP *irp, struct stream *s_in, enum NTSTATUS IoStatus); +static void devredir_proc_cid_statfs( IRP *irp, + struct stream *s_in, + enum NTSTATUS IoStatus); +static void devredir_proc_cid_statfs_resp(IRP *irp, + struct stream *s_in, + enum NTSTATUS IoStatus); /* Other local functions */ static void devredir_send_server_core_cap_req(void); static void devredir_send_server_clientID_confirm(void); @@ -212,6 +221,8 @@ const char *completion_type_to_str(enum COMPLETION_TYPE cid) (cid == CID_RENAME_FILE_RESP) ? "CID_RENAME_FILE_RESP" : (cid == CID_LOOKUP) ? "CID_LOOKUP" : (cid == CID_SETATTR) ? "CID_SETATTR" : + (cid == CID_STATFS) ? "CID_STATFS" : + (cid == CID_STATFS_RESP) ? "CID_STATFS_RESP" : /* default */ ""; }; @@ -1204,6 +1215,14 @@ devredir_proc_device_iocompletion(struct stream *s) devredir_proc_cid_setattr(irp, s, IoStatus); break; + case CID_STATFS: + devredir_proc_cid_statfs(irp, s, IoStatus); + break; + + case CID_STATFS_RESP: + devredir_proc_cid_statfs_resp(irp, s, IoStatus); + break; + default: LOG_DEVEL(LOG_LEVEL_ERROR, "got unknown CompletionID: DeviceId=0x%x " "CompletionId=0x%x IoStatus=0x%x", @@ -1542,6 +1561,57 @@ devredir_setattr_for_entry(struct state_setattr *fusep, tui32 device_id, return rval; } +/** + * FUSE calls this function whenever it wants us to run a statfs + * + * @param fusep opaque data struct that we just pass back to FUSE when done + * @param device_id device_id of the redirected share + * + * @return 0 on success, -1 on failure + *****************************************************************************/ + +int +devredir_statfs(struct state_statfs *fusep, tui32 device_id, const char *path) +{ + tui32 DesiredAccess; + tui32 CreateOptions; + tui32 CreateDisposition; + int rval = -1; + IRP *irp; + + LOG_DEVEL(LOG_LEVEL_DEBUG, "fusep=%p", fusep); + + if ((irp = devredir_irp_with_pathname_new(path)) != NULL) + { + /* convert / to windows compatible \ */ + devredir_cvt_slash(irp->pathname); + + /* + * Allocate an IRP to open the file, read the filesystem size + * attributes and then close the file + */ + irp->CompletionId = g_completion_id++; + irp->completion_type = CID_STATFS; + irp->DeviceId = device_id; + irp->fuse_info = fusep; + + DesiredAccess = DA_SYNCHRONIZE; + CreateOptions = 0; + CreateDisposition = CD_FILE_OPEN; + + LOG_DEVEL(LOG_LEVEL_DEBUG, "statfs for device_id=%d CompletionId=%d", + device_id, irp->CompletionId); + + rval = devredir_send_drive_create_request(device_id, + irp->pathname, + DesiredAccess, CreateOptions, + 0, CreateDisposition, + irp->CompletionId); + } + + return rval; +} + int devredir_file_create(struct state_create *fusep, tui32 device_id, const char *path, int mode) @@ -2245,7 +2315,7 @@ devredir_proc_cid_lookup(IRP *irp, LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_BASIC_INFORMATION length" "%d, got len=%d", FILE_BASIC_INFORMATION_SIZE, Length); - IoStatus = STATUS_UNSUCCESSFUL; + IoStatus = STATUS_INFO_LENGTH_MISMATCH; lookup_done(irp, IoStatus); } else @@ -2264,7 +2334,7 @@ devredir_proc_cid_lookup(IRP *irp, LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_STD_INFORMATION length" "%d, got len=%d", FILE_STD_INFORMATION_SIZE, Length); - IoStatus = STATUS_UNSUCCESSFUL; + IoStatus = STATUS_INFO_LENGTH_MISMATCH; } else { @@ -2277,6 +2347,120 @@ devredir_proc_cid_lookup(IRP *irp, } +/*****************************************************************************/ +static void +devredir_proc_cid_statfs(IRP *irp, + struct stream *s_in, + enum NTSTATUS IoStatus) +{ + struct stream *s; + int bytes; + char size_info_struct[FILE_FS_FULL_SIZE_INFORMATION_SIZE] = {0}; + + if (IoStatus != STATUS_SUCCESS) + { + struct statvfs fss = {0}; + LOG_DEVEL(LOG_LEVEL_DEBUG, + "statfs returned with IoStatus=0x%x", IoStatus); + + xfuse_devredir_cb_statfs((struct state_statfs *)irp->fuse_info, + &fss, + IoStatus); + devredir_irp_delete(irp); + return; + } + + xstream_new(s, (int)(64 + sizeof(size_info_struct))); + + irp->completion_type = CID_STATFS_RESP; + devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, + irp->CompletionId, + IRP_MJ_QUERY_VOLUME_INFORMATION, + IRP_MN_NONE); + + xstream_wr_u32_le(s, FileFsFullSizeInformation); + xstream_wr_u32_le(s, sizeof(size_info_struct)); + /* number of bytes after padding */ + xstream_seek(s, 24); /* padding */ + xstream_copyin(s, size_info_struct, sizeof(size_info_struct)); + /* Queried structure */ + + /* send to client */ + bytes = xstream_len(s); + send_channel_data(g_rdpdr_chan_id, s->data, bytes); + xstream_free(s); +} + +/* [MS-RDPEFS] 2.2.3.4.6 */ +static void +devredir_proc_cid_statfs_resp(IRP *irp, + struct stream *s_in, + enum NTSTATUS IoStatus) +{ + struct statvfs fss = {0}; + tui32 Length; + if (IoStatus == STATUS_SUCCESS) + { + xstream_rd_u32_le(s_in, Length); + if (Length != FILE_FS_FULL_SIZE_INFORMATION_SIZE) + { + LOG_DEVEL(LOG_LEVEL_ERROR, + "Expected FILE_FS_FULL_SIZE_INFORMATION_SIZE length" + " %d, got len=%d", + FILE_FS_FULL_SIZE_INFORMATION_SIZE, Length); + IoStatus = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + tui64 TotalAllocationUnits; + tui64 CallerAvailableAllocationUnits; + tui64 ActualAvailableAllocationUnits; + tui32 SectorsPerAllocationUnit; + tui32 BytesPerSector; + unsigned int block_size; + + xstream_rd_u64_le(s_in, TotalAllocationUnits); + xstream_rd_u64_le(s_in, CallerAvailableAllocationUnits); + xstream_rd_u64_le(s_in, ActualAvailableAllocationUnits); + xstream_rd_u32_le(s_in, SectorsPerAllocationUnit); + xstream_rd_u32_le(s_in, BytesPerSector); + + block_size = SectorsPerAllocationUnit * BytesPerSector; + if (block_size < 512 || block_size > 131072) + { + LOG(LOG_LEVEL_ERROR, + "Unreasonable block size for file system : %u", + block_size); + } + else + { + fss.f_bsize = block_size; + fss.f_frsize = block_size; + fss.f_blocks = TotalAllocationUnits; + fss.f_bfree = ActualAvailableAllocationUnits; + fss.f_bavail = CallerAvailableAllocationUnits; + } + } + } + xfuse_devredir_cb_statfs((struct state_statfs *)irp->fuse_info, + &fss, IoStatus); + + if (IoStatus != STATUS_SUCCESS) + { + devredir_irp_delete(irp); + } + else + { + irp->completion_type = CID_CLOSE; + devredir_send_drive_close_request(RDPDR_CTYP_CORE, + PAKID_CORE_DEVICE_IOREQUEST, + irp->DeviceId, + irp->FileId, + irp->CompletionId, + IRP_MJ_CLOSE, IRP_MN_NONE, 32); + } +} + /* * Re-uses the specified IRP to issue a request to set basic file attributes * diff --git a/sesman/chansrv/devredir.h b/sesman/chansrv/devredir.h index 9035de23b3..651cb2a3d2 100644 --- a/sesman/chansrv/devredir.h +++ b/sesman/chansrv/devredir.h @@ -53,6 +53,7 @@ struct state_read; struct state_write; struct state_remove; struct state_close; +struct state_statfs; /* called from FUSE module */ @@ -95,4 +96,7 @@ int devredir_rmdir_or_file(struct state_remove *fusep, tui32 device_id, const char *path); +int +devredir_statfs(struct state_statfs *fusep, tui32 device_id, const char *path); + #endif From a03c74a5c693b6ded20a4c3a238bd3a87a4623ce Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:53:25 +0000 Subject: [PATCH 2/2] Add fix for chromium saving files to FUSE filesys Chromium 130 won't save to our filesystem if we don't return a max filename length. Dummy parameters were tried for inode counts, but these do not seem to be necessary. Not also that btrfs foes not return values for these fields. (cherry picked from commit 6a49ff9946ddc21ac07d3749e4c6e3fdaee3dcb6) --- sesman/chansrv/chansrv_xfs.h | 14 +++++++------- sesman/chansrv/devredir.c | 12 ++++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/sesman/chansrv/chansrv_xfs.h b/sesman/chansrv/chansrv_xfs.h index 4b82fd202c..e28a891ab5 100644 --- a/sesman/chansrv/chansrv_xfs.h +++ b/sesman/chansrv/chansrv_xfs.h @@ -20,7 +20,13 @@ #ifndef _CHANSRV_XFS #define _CHANSRV_XFS -/* Skip this include if there's no FUSE */ +/* Maximum length of filename supported (in bytes). + * This is a sensible limit to a filename length. It is not used by + * this module to allocate long-lived storage, so it can be increased + * if necessary */ +#define XFS_MAXFILENAMELEN 1023 + +/* Skip the rest of this include if there's no FUSE */ #ifdef XRDP_FUSE #include @@ -29,12 +35,6 @@ #include "arch.h" -/* Maximum length of filename supported (in bytes). - * This is a sensible limit to a filename length. It is not used by - * this module to allocate long-lived storage, so it can be increased - * if necessary */ -#define XFS_MAXFILENAMELEN 1023 - /* * Incomplete types for the public interface */ diff --git a/sesman/chansrv/devredir.c b/sesman/chansrv/devredir.c index 749a2c3653..e14f5cd6e6 100644 --- a/sesman/chansrv/devredir.c +++ b/sesman/chansrv/devredir.c @@ -1271,11 +1271,7 @@ devredir_proc_query_dir_response(IRP *irp, // Size the filename buffer so it's big enough for // storing the file in our filesystem if we need to. -#ifdef XFS_MAXFILENAMELEN char filename[XFS_MAXFILENAMELEN + 1]; -#else - char filename[256]; -#endif tui64 LastAccessTime; tui64 LastWriteTime; tui64 EndOfFile; @@ -2439,6 +2435,14 @@ devredir_proc_cid_statfs_resp(IRP *irp, fss.f_blocks = TotalAllocationUnits; fss.f_bfree = ActualAvailableAllocationUnits; fss.f_bavail = CallerAvailableAllocationUnits; + // Following values do not seem to be needed by + // any applications. btrfs also returns 0 for these + //fss.f_files = ???; + //fss.f_ffree = ???; + //fss.f_favail = fss.f_ffree; + // Chromium 130 needs this set, or the user can't save + // to our filesystem + fss.f_namemax = XFS_MAXFILENAMELEN; } } }