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]
This commit is contained in:
matt335672
2024-11-06 17:35:04 +00:00
parent 9c0a0a8c16
commit 5dc4fdbc2c
6 changed files with 309 additions and 4 deletions
+1
View File
@@ -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,
+21 -1
View File
@@ -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
+90
View File
@@ -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
*****************************************************************************/
+6
View File
@@ -72,6 +72,8 @@ struct state_write;
struct state_remove;
struct state_rename;
struct state_close;
struct statvfs; // OS structure defined in <sys/statvfs.h>
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
*
+187 -3
View File
@@ -43,6 +43,7 @@
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <fcntl.h>
#include <string.h>
@@ -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 */ "<unknown>";
};
#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",
@@ -1544,6 +1563,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 +2317,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 +2336,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 +2349,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
*
+4
View File
@@ -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