diff --git a/kmod/src/client.c b/kmod/src/client.c index acf9412f..98f14526 100644 --- a/kmod/src/client.c +++ b/kmod/src/client.c @@ -306,6 +306,14 @@ int scoutfs_client_resize_devices(struct super_block *sb, struct scoutfs_net_res nrd, sizeof(*nrd), NULL, 0); } +int scoutfs_client_statfs(struct super_block *sb, struct scoutfs_net_statfs *nst) +{ + struct client_info *client = SCOUTFS_SB(sb)->client_info; + + return scoutfs_net_sync_request(sb, client->conn, SCOUTFS_NET_CMD_STATFS, + NULL, 0, nst, sizeof(*nst)); +} + /* * The server is asking that we trigger a commit of the current log * trees so that they can ensure an item seq discontinuity between diff --git a/kmod/src/client.h b/kmod/src/client.h index e62eccae..07dab8a9 100644 --- a/kmod/src/client.h +++ b/kmod/src/client.h @@ -34,6 +34,7 @@ int scoutfs_client_get_volopt(struct super_block *sb, struct scoutfs_volume_opti int scoutfs_client_set_volopt(struct super_block *sb, struct scoutfs_volume_options *volopt); int scoutfs_client_clear_volopt(struct super_block *sb, struct scoutfs_volume_options *volopt); int scoutfs_client_resize_devices(struct super_block *sb, struct scoutfs_net_resize_devices *nrd); +int scoutfs_client_statfs(struct super_block *sb, struct scoutfs_net_statfs *nst); int scoutfs_client_setup(struct super_block *sb); void scoutfs_client_destroy(struct super_block *sb); diff --git a/kmod/src/format.h b/kmod/src/format.h index 825d7327..9151e4a9 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -1003,6 +1003,7 @@ enum scoutfs_net_cmd { SCOUTFS_NET_CMD_SET_VOLOPT, SCOUTFS_NET_CMD_CLEAR_VOLOPT, SCOUTFS_NET_CMD_RESIZE_DEVICES, + SCOUTFS_NET_CMD_STATFS, SCOUTFS_NET_CMD_FAREWELL, SCOUTFS_NET_CMD_UNKNOWN, }; @@ -1050,6 +1051,15 @@ struct scoutfs_net_resize_devices { __le64 new_total_data_blocks; }; +struct scoutfs_net_statfs { + __u8 uuid[SCOUTFS_UUID_BYTES]; + __le64 free_meta_blocks; + __le64 total_meta_blocks; + __le64 free_data_blocks; + __le64 total_data_blocks; + __le64 inode_count; +}; + struct scoutfs_net_lock { struct scoutfs_key key; __le64 write_seq; diff --git a/kmod/src/server.c b/kmod/src/server.c index acb16cf4..af994546 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -2921,6 +2921,68 @@ out: return scoutfs_net_response(sb, conn, cmd, id, ret, NULL, 0); }; +struct statfs_free_blocks { + u64 meta; + u64 data; +}; + +static int count_free_blocks(struct super_block *sb, void *arg, int owner, + u64 id, bool meta, bool avail, u64 blocks) +{ + struct statfs_free_blocks *sfb = arg; + + if (meta) + sfb->meta += blocks; + else + sfb->data += blocks; + + return 0; +} + +/* + * We calculate the total inode count and free blocks from the current in-memory dirty + * versions of the super block and log_trees structs, so we have to lock them. + */ +static int server_statfs(struct super_block *sb, struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) +{ + DECLARE_SERVER_INFO(sb, server); + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + struct scoutfs_net_statfs nst = {{0,}}; + struct statfs_free_blocks sfb = {0,}; + u64 inode_count; + int ret; + + if (arg_len != 0) { + ret = -EINVAL; + goto out; + } + + mutex_lock(&server->alloc_mutex); + ret = scoutfs_alloc_foreach_super(sb, super, count_free_blocks, &sfb); + mutex_unlock(&server->alloc_mutex); + if (ret < 0) + goto out; + + mutex_lock(&server->logs_mutex); + ret = scoutfs_forest_inode_count(sb, super, &inode_count); + mutex_unlock(&server->logs_mutex); + if (ret < 0) + goto out; + + BUILD_BUG_ON(sizeof(nst.uuid) != sizeof(super->uuid)); + memcpy(nst.uuid, super->uuid, sizeof(nst.uuid)); + nst.free_meta_blocks = cpu_to_le64(sfb.meta); + nst.total_meta_blocks = super->total_meta_blocks; + nst.free_data_blocks = cpu_to_le64(sfb.data); + nst.total_data_blocks = super->total_data_blocks; + nst.inode_count = cpu_to_le64(inode_count); + + ret = 0; +out: + return scoutfs_net_response(sb, conn, cmd, id, ret, &nst, sizeof(nst)); +} + static void init_mounted_client_key(struct scoutfs_key *key, u64 rid) { *key = (struct scoutfs_key) { @@ -3558,6 +3620,7 @@ static scoutfs_net_request_t server_req_funcs[] = { [SCOUTFS_NET_CMD_SET_VOLOPT] = server_set_volopt, [SCOUTFS_NET_CMD_CLEAR_VOLOPT] = server_clear_volopt, [SCOUTFS_NET_CMD_RESIZE_DEVICES] = server_resize_devices, + [SCOUTFS_NET_CMD_STATFS] = server_statfs, [SCOUTFS_NET_CMD_FAREWELL] = server_farewell, }; diff --git a/kmod/src/super.c b/kmod/src/super.c index 612c51fa..e22a0420 100644 --- a/kmod/src/super.c +++ b/kmod/src/super.c @@ -83,35 +83,34 @@ retry: return cpu_to_le64(ret); } -struct statfs_free_blocks { - u64 meta; - u64 data; -}; - -static int count_free_blocks(struct super_block *sb, void *arg, int owner, - u64 id, bool meta, bool avail, u64 blocks) +/* the statfs file fields can be small (and signed?) :/ */ +static __statfs_word saturate_truncated_word(u64 files) { - struct statfs_free_blocks *sfb = arg; + __statfs_word word = files; - if (meta) - sfb->meta += blocks; - else - sfb->data += blocks; + if (word != files) { + word = ~0ULL; + if (word < 0) + word = (unsigned long)word >> 1; + } - return 0; + return word; } /* - * Build the free block counts by having alloc read all the persistent - * blocks which contain allocators and calling us for each of them. - * Only the super block reads aren't cached so repeatedly calling statfs - * is like repeated O_DIRECT IO. We can add a cache and stale results - * if that IO becomes a problem. + * The server gives us the current sum of free blocks and the total + * inode count that it can see across all the clients' log trees. It + * won't see allocations and inode creations or deletions that are dirty + * in client memory as it builds a transaction. * - * We fake the number of free inodes value by assuming that we can fill - * free blocks with a certain number of inodes. We then the number of - * current inodes to that free count to determine the total possible - * inodes. + * We don't have static limits on the number of files so the statfs + * fields for the total possible files and the number free isn't + * particularly helpful. What we do want to report is the number of + * inodes, so we fake a max possible number of inodes given a + * conservative estimate of the total space consumption per file and + * then find the free by subtracting our precise count of active inodes. + * This seems like the least surprising compromise where the file max + * doesn't change and the caller gets the correct count of used inodes. * * The fsid that we report is constructed from the xor of the first two * and second two little endian u32s that make up the uuid bytes. @@ -119,41 +118,33 @@ static int count_free_blocks(struct super_block *sb, void *arg, int owner, static int scoutfs_statfs(struct dentry *dentry, struct kstatfs *kst) { struct super_block *sb = dentry->d_inode->i_sb; - struct scoutfs_super_block *super = NULL; - struct statfs_free_blocks sfb = {0,}; + struct scoutfs_net_statfs nst; + u64 files; + u64 ffree; __le32 uuid[4]; int ret; scoutfs_inc_counter(sb, statfs); - super = kzalloc(sizeof(struct scoutfs_super_block), GFP_NOFS); - if (!super) { - ret = -ENOMEM; - goto out; - } - - ret = scoutfs_read_super(sb, super); + ret = scoutfs_client_statfs(sb, &nst); if (ret) goto out; - ret = scoutfs_alloc_foreach(sb, count_free_blocks, &sfb); - if (ret < 0) - goto out; - - kst->f_bfree = (sfb.meta << SCOUTFS_BLOCK_SM_LG_SHIFT) + sfb.data; + kst->f_bfree = (le64_to_cpu(nst.free_meta_blocks) << SCOUTFS_BLOCK_SM_LG_SHIFT) + + le64_to_cpu(nst.free_data_blocks); kst->f_type = SCOUTFS_SUPER_MAGIC; kst->f_bsize = SCOUTFS_BLOCK_SM_SIZE; - kst->f_blocks = (le64_to_cpu(super->total_meta_blocks) << - SCOUTFS_BLOCK_SM_LG_SHIFT) + - le64_to_cpu(super->total_data_blocks); + kst->f_blocks = (le64_to_cpu(nst.total_meta_blocks) << SCOUTFS_BLOCK_SM_LG_SHIFT) + + le64_to_cpu(nst.total_data_blocks); kst->f_bavail = kst->f_bfree; - /* arbitrarily assume ~1K / empty file */ - kst->f_ffree = sfb.meta * (SCOUTFS_BLOCK_LG_SIZE / 1024); - kst->f_files = kst->f_ffree + le64_to_cpu(super->next_ino); + files = div_u64(le64_to_cpu(nst.total_meta_blocks) << SCOUTFS_BLOCK_LG_SHIFT, 2048); + ffree = files - le64_to_cpu(nst.inode_count); + kst->f_files = saturate_truncated_word(files); + kst->f_ffree = saturate_truncated_word(ffree); - BUILD_BUG_ON(sizeof(uuid) != sizeof(super->uuid)); - memcpy(uuid, super->uuid, sizeof(uuid)); + BUILD_BUG_ON(sizeof(uuid) != sizeof(nst.uuid)); + memcpy(uuid, nst.uuid, sizeof(uuid)); kst->f_fsid.val[0] = le32_to_cpu(uuid[0]) ^ le32_to_cpu(uuid[1]); kst->f_fsid.val[1] = le32_to_cpu(uuid[2]) ^ le32_to_cpu(uuid[3]); kst->f_namelen = SCOUTFS_NAME_LEN; @@ -162,8 +153,6 @@ static int scoutfs_statfs(struct dentry *dentry, struct kstatfs *kst) /* the vfs fills f_flags */ ret = 0; out: - kfree(super); - /* * We don't take cluster locks in statfs which makes it a very * convenient place to trigger lock reclaim for debugging. We