Skip to content

Commit 7fcc07e

Browse files
bsberndhbirth
authored andcommitted
fuse: Optimize statx for permission checks by requesting only needed attributes
For permission checks via inode_permission(), we only need mode, uid, and gid attributes. Previously, we requested all STATX_BASIC_STATS, which was inefficient. This commit enables the optimization by: 1. Requesting only STATX_MODE | STATX_UID | STATX_GID for permission checks 2. Relaxing the condition in fuse_do_statx() from requiring all basic stats to accepting any subset of basic stats 3. Adding validation that the server returns at least what was requested The preparation commit ensures partial updates work correctly by only updating returned attributes and managing timeouts appropriately. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit 09ed47b)
1 parent 1118066 commit 7fcc07e

1 file changed

Lines changed: 45 additions & 18 deletions

File tree

fs/fuse/dir.c

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,8 +1182,13 @@ static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr)
11821182
attr->blksize = sx->blksize;
11831183
}
11841184

1185+
/*
1186+
* @param sx_mask request mask send to to fuse-server
1187+
* @param mandatory_sx_mask subset of (or complete) sx_mask that the server
1188+
* has to fulfill
1189+
*/
11851190
static int fuse_do_statx(struct inode *inode, struct file *file,
1186-
struct kstat *stat)
1191+
struct kstat *stat, u32 sx_mask, u32 mandatory_sx_mask)
11871192
{
11881193
int err;
11891194
struct fuse_attr attr;
@@ -1194,6 +1199,12 @@ static int fuse_do_statx(struct inode *inode, struct file *file,
11941199
u64 attr_version = fuse_get_attr_version(fm->fc);
11951200
FUSE_ARGS(args);
11961201

1202+
/*
1203+
* mandatory_sx_mask should be a subset of sx_mask.
1204+
* If it's not, we have a logic error somewhere in the call chain.
1205+
*/
1206+
WARN_ON_ONCE((mandatory_sx_mask & sx_mask) != mandatory_sx_mask);
1207+
11971208
memset(&inarg, 0, sizeof(inarg));
11981209
memset(&outarg, 0, sizeof(outarg));
11991210
/* Directories have separate file-handle space */
@@ -1203,9 +1214,12 @@ static int fuse_do_statx(struct inode *inode, struct file *file,
12031214
inarg.getattr_flags |= FUSE_GETATTR_FH;
12041215
inarg.fh = ff->fh;
12051216
}
1206-
/* For now leave sync hints as the default, request all stats. */
1217+
/*
1218+
* For permission checks, we only need mode, uid, gid.
1219+
* This is an optimization to avoid fetching all stats when not needed.
1220+
*/
12071221
inarg.sx_flags = 0;
1208-
inarg.sx_mask = STATX_BASIC_STATS | STATX_BTIME;
1222+
inarg.sx_mask = sx_mask;
12091223
args.opcode = FUSE_STATX;
12101224
args.nodeid = get_node_id(inode);
12111225
args.in_numargs = 1;
@@ -1219,6 +1233,17 @@ static int fuse_do_statx(struct inode *inode, struct file *file,
12191233
return err;
12201234

12211235
sx = &outarg.stat;
1236+
1237+
/*
1238+
* Verify the server returned at least what we requested.
1239+
* The server may return more attributes than requested (which is fine),
1240+
* but must not return fewer.
1241+
*/
1242+
if ((sx->mask & mandatory_sx_mask) != mandatory_sx_mask) {
1243+
fuse_make_bad(inode);
1244+
return -EIO;
1245+
}
1246+
12221247
if (((sx->mask & STATX_SIZE) && !fuse_valid_size(sx->size)) ||
12231248
((sx->mask & STATX_TYPE) && (!fuse_valid_type(sx->mode) ||
12241249
inode_wrong_type(inode, sx->mode)))) {
@@ -1227,7 +1252,7 @@ static int fuse_do_statx(struct inode *inode, struct file *file,
12271252
}
12281253

12291254
fuse_statx_to_attr(&outarg.stat, &attr);
1230-
if ((sx->mask & STATX_BASIC_STATS) == STATX_BASIC_STATS) {
1255+
if (sx->mask & STATX_BASIC_STATS) {
12311256
fuse_change_attributes(inode, &attr, &outarg.stat,
12321257
ATTR_TIMEOUT(&outarg), attr_version);
12331258
}
@@ -1292,30 +1317,31 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
12921317
bool sync;
12931318
u32 inval_mask = READ_ONCE(fi->inval_mask);
12941319
u32 cache_mask = fuse_get_cache_mask(inode);
1295-
1320+
u32 mandatory_sx_mask = request_mask & STATX_BASIC_STATS;
1321+
u32 sx_mask = request_mask;
12961322

12971323
/* FUSE only supports basic stats and possibly btime */
1298-
request_mask &= STATX_BASIC_STATS | STATX_BTIME;
1324+
sx_mask &= STATX_BASIC_STATS | STATX_BTIME;
12991325
retry:
13001326
if (fc->no_statx)
1301-
request_mask &= STATX_BASIC_STATS;
1327+
sx_mask &= STATX_BASIC_STATS;
13021328

1303-
if (!request_mask)
1329+
if (!sx_mask)
13041330
sync = false;
13051331
else if (flags & AT_STATX_FORCE_SYNC)
13061332
sync = true;
13071333
else if (flags & AT_STATX_DONT_SYNC)
13081334
sync = false;
1309-
else if (request_mask & inval_mask & ~cache_mask)
1335+
else if (sx_mask & inval_mask & ~cache_mask)
13101336
sync = true;
13111337
else
13121338
sync = time_before64(fi->i_time, get_jiffies_64());
13131339

13141340
if (sync) {
13151341
forget_all_cached_acls(inode);
1316-
/* Try statx if BTIME is requested */
1317-
if (!fc->no_statx && (request_mask & ~STATX_BASIC_STATS)) {
1318-
err = fuse_do_statx(inode, file, stat);
1342+
if (!fc->no_statx) {
1343+
err = fuse_do_statx(inode, file, stat, sx_mask,
1344+
mandatory_sx_mask);
13191345
if (err == -ENOSYS) {
13201346
fc->no_statx = 1;
13211347
err = 0;
@@ -1477,13 +1503,14 @@ static int fuse_access(struct inode *inode, int mask)
14771503
return err;
14781504
}
14791505

1480-
static int fuse_perm_getattr(struct inode *inode, int mask)
1506+
static int fuse_perm_getattr(struct inode *inode, int mask, int perm_mask)
14811507
{
14821508
if (mask & MAY_NOT_BLOCK)
14831509
return -ECHILD;
14841510

14851511
forget_all_cached_acls(inode);
1486-
return fuse_do_getattr(inode, NULL, NULL);
1512+
return fuse_update_get_attr(inode, NULL, NULL, perm_mask,
1513+
AT_STATX_FORCE_SYNC);
14871514
}
14881515

14891516
/*
@@ -1505,6 +1532,7 @@ static int fuse_permission(struct mnt_idmap *idmap,
15051532
struct fuse_conn *fc = get_fuse_conn(inode);
15061533
bool refreshed = false;
15071534
int err = 0;
1535+
int perm_mask = STATX_MODE | STATX_UID | STATX_GID;
15081536

15091537
if (fuse_is_bad(inode))
15101538
return -EIO;
@@ -1518,13 +1546,12 @@ static int fuse_permission(struct mnt_idmap *idmap,
15181546
if (fc->default_permissions ||
15191547
((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) {
15201548
struct fuse_inode *fi = get_fuse_inode(inode);
1521-
u32 perm_mask = STATX_MODE | STATX_UID | STATX_GID;
15221549

15231550
if (perm_mask & READ_ONCE(fi->inval_mask) ||
15241551
time_before64(fi->i_time, get_jiffies_64())) {
15251552
refreshed = true;
15261553

1527-
err = fuse_perm_getattr(inode, mask);
1554+
err = fuse_perm_getattr(inode, mask, perm_mask);
15281555
if (err)
15291556
return err;
15301557
}
@@ -1537,7 +1564,7 @@ static int fuse_permission(struct mnt_idmap *idmap,
15371564
attributes. This is also needed, because the root
15381565
node will at first have no permissions */
15391566
if (err == -EACCES && !refreshed) {
1540-
err = fuse_perm_getattr(inode, mask);
1567+
err = fuse_perm_getattr(inode, mask, perm_mask);
15411568
if (!err)
15421569
err = generic_permission(&nop_mnt_idmap,
15431570
inode, mask);
@@ -1554,7 +1581,7 @@ static int fuse_permission(struct mnt_idmap *idmap,
15541581
if (refreshed)
15551582
return -EACCES;
15561583

1557-
err = fuse_perm_getattr(inode, mask);
1584+
err = fuse_perm_getattr(inode, mask, perm_mask);
15581585
if (!err && !(inode->i_mode & S_IXUGO))
15591586
return -EACCES;
15601587
}

0 commit comments

Comments
 (0)