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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
*****************************************************************************/
|
||||
|
||||
@@ -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
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user