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 f8b41e29c0..4cc246aca3 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; @@ -303,6 +308,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; @@ -409,6 +423,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); @@ -611,6 +627,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, "-o"); @@ -1570,6 +1587,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 * @@ -2686,6 +2723,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/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 e3d58b2457..47904d0158 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); @@ -213,6 +222,8 @@ static 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 */ ""; }; #endif @@ -1206,6 +1217,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", @@ -1254,11 +1273,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; @@ -1544,6 +1559,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) @@ -2247,7 +2313,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 @@ -2266,7 +2332,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 { @@ -2279,6 +2345,128 @@ 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; + // 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; + } + } + } + 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