virtiofs: add support for statx syscall (#14087)

* virtiofs: add support for statx syscall * add statx drvfs test * skip statx drvfs test on wsl1 * skip stax tests on smb and fat --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Jan 23, 2026 at 14:09 UTC 081388c5f5a7e6468c394c2444ded8f2a6370dcc
3 files changed +191 -14
packages.config
+1 -1
@@ -18,7 +18,7 @@
18 <package id="Microsoft.WSL.bsdtar" version="0.0.2-2" />
19 <package id="Microsoft.WSL.Dependencies.amd64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
20 <package id="Microsoft.WSL.Dependencies.arm64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
21 - <package id="Microsoft.WSL.DeviceHost" version="1.1.8-0" />
21 + <package id="Microsoft.WSL.DeviceHost" version="1.1.10-0" />
22 <package id="Microsoft.WSL.Kernel" version="6.6.114.1-1" targetFramework="native" />
23 <package id="Microsoft.WSL.LinuxSdk" version="1.20.0" targetFramework="native" />
24 <package id="Microsoft.WSL.TestDistro" version="2.5.7-47" />
test/linux/unit_tests/drvfs.c
+179 -7
@@ -156,6 +156,8 @@ LXT_VARIATION_HANDLER DrvFsTestFatWslPath;
156
157 LXT_VARIATION_HANDLER DrvFsTestFstat;
158
159 +LXT_VARIATION_HANDLER DrvFsTestStatx;
160 +
161 LXT_VARIATION_HANDLER DrvFsTestGetDents64Alignment;
162
163 LXT_VARIATION_HANDLER DrvFsTestGetDentsAlignment;
@@ -246,6 +248,7 @@ static const LXT_VARIATION g_LxtVariations[] = {
248 {"DrvFs - hard links", DrvFsTestHardLinks},
249 {"DrvFs - block count", DrvFsTestBlockCount},
250 {"DrvFs - fstat", DrvFsTestFstat},
251 + {"DrvFs - statx", DrvFsTestStatx},
252 {"DrvFs - reopen unlinked file", DrvFsTestReopenUnlinked},
253 {"DrvFs - delete loop", DrvFsTestDeleteLoop},
254 {"DrvFs - seek", DrvFsTestSeek},
@@ -336,6 +339,7 @@ static const LXT_VARIATION g_LxtMetadataVariations[] = {
339 {"DrvFs - hard links", DrvFsTestHardLinks},
340 {"DrvFs - block count", DrvFsTestBlockCount},
341 {"DrvFs - fstat", DrvFsTestFstat},
342 + {"DrvFs - statx", DrvFsTestStatx},
343 {"DrvFs - reopen unlinked file", DrvFsTestReopenUnlinked},
344 {"DrvFs - delete loop", DrvFsTestDeleteLoop},
345 {"DrvFs - seek", DrvFsTestSeek},
@@ -368,6 +372,7 @@ static const LXT_VARIATION g_LxtReFsVariations[] = {
372 {"DrvFs - hard links", DrvFsTestHardLinks},
373 {"DrvFs - block count", DrvFsTestBlockCount},
374 {"DrvFs - fstat", DrvFsTestFstat},
375 + {"DrvFs - statx", DrvFsTestStatx},
376 {"DrvFs - reopen unlinked file", DrvFsTestReopenUnlinked},
377 {"DrvFs - delete loop", DrvFsTestDeleteLoop},
378 {"DrvFs - seek", DrvFsTestSeek},
@@ -1389,13 +1394,6 @@ Return Value:
1394
1395 int Result;
1396
1392 - if (g_LxtFsInfo.FsType == LxtFsTypeVirtioFs)
1393 - {
1394 - LxtLogInfo("TODO: debug this test on virtiofs.");
1395 - Result = 0;
1396 - goto ErrorExit;
1397 - }
1398 -
1397 LxtCheckErrno(LxtFsDeleteCurrentWorkingDirectoryCommon(DRVFS_PREFIX, FS_DELETE_DRVFS));
1398
1399 ErrorExit:
@@ -2027,6 +2025,180 @@ ErrorExit:
2025 return Result;
2026 }
2027
2028 +int DrvFsTestStatx(PLXT_ARGS Args)
2029 +
2030 +/*++
2031 +
2032 +Description:
2033 +
2034 + This routine tests the statx system call on drvfs files.
2035 +
2036 +Arguments:
2037 +
2038 + Args - Supplies the command line arguments.
2039 +
2040 +Return Value:
2041 +
2042 + Returns 0 on success, -1 on failure.
2043 +
2044 +--*/
2045 +
2046 +{
2047 +
2048 + int Fd;
2049 + int Result;
2050 + struct stat Stat1;
2051 + struct statx Statx1;
2052 + struct statx Statx2;
2053 +
2054 + Fd = -1;
2055 +
2056 + if (g_LxtFsInfo.FsType == LxtFsTypeDrvFs)
2057 + {
2058 + LxtLogInfo("statx is not supported on drvfs in WSL1.");
2059 + Result = 0;
2060 + goto ErrorExit;
2061 + }
2062 +
2063 + //
2064 + // Create a test file and symlink.
2065 + //
2066 +
2067 + LxtCheckErrnoZeroSuccess(mkdir(DRVFS_BASIC_PREFIX, 0777));
2068 + LxtCheckErrno(Fd = creat(DRVFS_BASIC_PREFIX "/testfile", 0666));
2069 + LxtCheckErrnoZeroSuccess(symlink(DRVFS_BASIC_PREFIX "/testfile", DRVFS_BASIC_PREFIX "/testlink"));
2070 +
2071 + //
2072 + // Stat and statx should have consistent results.
2073 + //
2074 +
2075 + LxtCheckErrnoZeroSuccess(stat(DRVFS_BASIC_PREFIX "/testfile", &Stat1));
2076 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_BASIC_STATS, &Statx1));
2077 + LxtCheckEqual(Stat1.st_ino, Statx1.stx_ino, "%llu");
2078 + LxtCheckEqual(Stat1.st_size, Statx1.stx_size, "%llu");
2079 + LxtCheckEqual(Stat1.st_mode, Statx1.stx_mode, "0%o");
2080 +
2081 + //
2082 + // Statx with AT_EMPTY_PATH on an fd.
2083 + //
2084 +
2085 + LxtCheckErrnoZeroSuccess(statx(Fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &Statx2));
2086 + LxtCheckEqual(Statx1.stx_ino, Statx2.stx_ino, "%llu");
2087 +
2088 + //
2089 + // Test AT_SYMLINK_NOFOLLOW flag.
2090 + //
2091 +
2092 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testlink", AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS, &Statx2));
2093 + LxtCheckTrue(S_ISLNK(Statx2.stx_mode));
2094 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testlink", 0, STATX_BASIC_STATS, &Statx2));
2095 + LxtCheckTrue(S_ISREG(Statx2.stx_mode));
2096 +
2097 + //
2098 + // Test STATX_BTIME (birth/creation time).
2099 + //
2100 +
2101 + memset(&Statx2, 0, sizeof(Statx2));
2102 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_BASIC_STATS | STATX_BTIME, &Statx2));
2103 + if (Statx2.stx_mask & STATX_BTIME)
2104 + {
2105 + LxtLogInfo("Birth time supported: tv_sec=%lld", (long long)Statx2.stx_btime.tv_sec);
2106 + }
2107 +
2108 + //
2109 + // Test sync flags.
2110 + //
2111 +
2112 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, &Statx2));
2113 + LxtCheckEqual(Statx1.stx_ino, Statx2.stx_ino, "%llu");
2114 +
2115 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", AT_STATX_DONT_SYNC, STATX_BASIC_STATS, &Statx2));
2116 + LxtCheckEqual(Statx1.stx_ino, Statx2.stx_ino, "%llu");
2117 +
2118 + //
2119 + // Test individual field masks.
2120 + //
2121 +
2122 + memset(&Statx2, 0, sizeof(Statx2));
2123 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_TYPE, &Statx2));
2124 + LxtCheckTrue((Statx2.stx_mask & STATX_TYPE) != 0);
2125 + LxtCheckTrue(S_ISREG(Statx2.stx_mode));
2126 +
2127 + memset(&Statx2, 0, sizeof(Statx2));
2128 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_MODE, &Statx2));
2129 + LxtCheckTrue((Statx2.stx_mask & STATX_MODE) != 0);
2130 + LxtCheckEqual((Statx1.stx_mode & 0777), (Statx2.stx_mode & 0777), "0%o");
2131 +
2132 + memset(&Statx2, 0, sizeof(Statx2));
2133 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_NLINK, &Statx2));
2134 + LxtCheckTrue((Statx2.stx_mask & STATX_NLINK) != 0);
2135 + LxtCheckEqual(Statx1.stx_nlink, Statx2.stx_nlink, "%u");
2136 +
2137 + memset(&Statx2, 0, sizeof(Statx2));
2138 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_UID, &Statx2));
2139 + LxtCheckTrue((Statx2.stx_mask & STATX_UID) != 0);
2140 + LxtCheckEqual(Statx1.stx_uid, Statx2.stx_uid, "%u");
2141 +
2142 + memset(&Statx2, 0, sizeof(Statx2));
2143 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_GID, &Statx2));
2144 + LxtCheckTrue((Statx2.stx_mask & STATX_GID) != 0);
2145 + LxtCheckEqual(Statx1.stx_gid, Statx2.stx_gid, "%u");
2146 +
2147 + memset(&Statx2, 0, sizeof(Statx2));
2148 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_ATIME, &Statx2));
2149 + LxtCheckTrue((Statx2.stx_mask & STATX_ATIME) != 0);
2150 + LxtCheckEqual(Statx1.stx_atime.tv_sec, Statx2.stx_atime.tv_sec, "%lld");
2151 +
2152 + memset(&Statx2, 0, sizeof(Statx2));
2153 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_MTIME, &Statx2));
2154 + LxtCheckTrue((Statx2.stx_mask & STATX_MTIME) != 0);
2155 + LxtCheckEqual(Statx1.stx_mtime.tv_sec, Statx2.stx_mtime.tv_sec, "%lld");
2156 +
2157 + memset(&Statx2, 0, sizeof(Statx2));
2158 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_CTIME, &Statx2));
2159 + LxtCheckTrue((Statx2.stx_mask & STATX_CTIME) != 0);
2160 + LxtCheckEqual(Statx1.stx_ctime.tv_sec, Statx2.stx_ctime.tv_sec, "%lld");
2161 +
2162 + memset(&Statx2, 0, sizeof(Statx2));
2163 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_INO, &Statx2));
2164 + LxtCheckTrue((Statx2.stx_mask & STATX_INO) != 0);
2165 + LxtCheckEqual(Statx1.stx_ino, Statx2.stx_ino, "%llu");
2166 +
2167 + memset(&Statx2, 0, sizeof(Statx2));
2168 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_SIZE, &Statx2));
2169 + LxtCheckTrue((Statx2.stx_mask & STATX_SIZE) != 0);
2170 + LxtCheckEqual(Statx1.stx_size, Statx2.stx_size, "%llu");
2171 +
2172 + memset(&Statx2, 0, sizeof(Statx2));
2173 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/testfile", 0, STATX_BLOCKS, &Statx2));
2174 + LxtCheckTrue((Statx2.stx_mask & STATX_BLOCKS) != 0);
2175 + LxtCheckEqual(Statx1.stx_blocks, Statx2.stx_blocks, "%llu");
2176 +
2177 + //
2178 + // Test on a directory.
2179 + //
2180 +
2181 + LxtCheckErrnoZeroSuccess(statx(AT_FDCWD, DRVFS_BASIC_PREFIX, 0, STATX_BASIC_STATS, &Statx2));
2182 + LxtCheckTrue(S_ISDIR(Statx2.stx_mode));
2183 +
2184 + //
2185 + // Test error case.
2186 + //
2187 +
2188 + LxtCheckErrnoFailure(statx(AT_FDCWD, DRVFS_BASIC_PREFIX "/nonexistent", 0, STATX_BASIC_STATS, &Statx2), ENOENT);
2189 +
2190 +ErrorExit:
2191 + if (Fd >= 0)
2192 + {
2193 + close(Fd);
2194 + }
2195 +
2196 + unlink(DRVFS_BASIC_PREFIX "/testlink");
2197 + unlink(DRVFS_BASIC_PREFIX "/testfile");
2198 + rmdir(DRVFS_BASIC_PREFIX);
2199 + return Result;
2200 +}
2201 +
2202 int DrvFsTestGetDents64Alignment(PLXT_ARGS Args)
2203
2204 /*++
test/linux/unit_tests/lxtfs.c
+11 -6
@@ -444,13 +444,18 @@ Return Value:
444 //
445 // Try to chdir to the parent.
446 //
447 + // N.B. This currently doesn't work on virtiofs.
448 + //
449
448 - LxtCheckErrnoZeroSuccess(chdir(".."));
449 - LxtCheckErrno(LxtGetcwd(Path, sizeof(Path)));
450 - LxtCheckStringEqual(Path, BaseDir);
451 - memset(Path, 0, sizeof(Path));
452 - LxtCheckErrno(readlink(FS_PROC_SELF_CWD, Path, sizeof(Path)));
453 - LxtCheckStringEqual(Path, BaseDir);
450 + if (g_LxtFsInfo.FsType != LxtFsTypeVirtioFs)
451 + {
452 + LxtCheckErrnoZeroSuccess(chdir(".."));
453 + LxtCheckErrno(LxtGetcwd(Path, sizeof(Path)));
454 + LxtCheckStringEqual(Path, BaseDir);
455 + memset(Path, 0, sizeof(Path));
456 + LxtCheckErrno(readlink(FS_PROC_SELF_CWD, Path, sizeof(Path)));
457 + LxtCheckStringEqual(Path, BaseDir);
458 + }
459
460 //
461 // Try to chdir back to the deleted directory.