Hệ điều hành · 03/10/2026

Linux statx thực chiến: Đọc metadata file chính xác và an toàn

stat() đủ cho kích thước, mode và mtime cơ bản. Nhưng khi ứng dụng cần thời điểm tạo file, mount ID, alignment của O_DIRECT, thuộc tính immutable, subvolume hoặc giới hạn atomic write, Linux cung cấp giao diện giàu thông tin hơn: statx().

Linux statx thực chiến: Đọc metadata file chính xác và an toàn

stat() đủ cho kích thước, mode và mtime cơ bản. Nhưng khi ứng dụng cần thời điểm tạo file, mount ID, alignment của O_DIRECT, thuộc tính immutable, subvolume hoặc giới hạn atomic write, Linux cung cấp giao diện giàu thông tin hơn: statx().

Bài viết này xây một wrapper statx() đúng contract, giải thích cách đọc stx_mask, lựa chọn synchronization cho local/remote filesystem và tránh các giả định nguy hiểm về tính nhất quán. Nội dung được đối chiếu với statx(2), stat(2) và open(2).

1. Khi nào statx đáng dùng?

statx() xuất hiện từ Linux 4.11; wrapper glibc có từ 2.28. Nó giải quyết ba nhu cầu mà họ stat() truyền thống không biểu diễn tốt:

  • yêu cầu có chọn lọc các field để filesystem tránh công việc không cần thiết;
  • báo rõ field nào thực sự được kernel/filesystem cung cấp;
  • mở rộng metadata mà không phá ABI, như birth time, mount ID và direct-I/O alignment.

Use case điển hình gồm file indexer, backup, storage engine, container runtime, scanner mount tree và chương trình dùng O_DIRECT. Với ứng dụng portable POSIX chỉ cần metadata cơ bản, fstatat() vẫn đơn giản hơn.

2. Chữ ký và một lời gọi tối thiểu

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>

struct statx sx = {0};

if (statx(AT_FDCWD, "report.bin",
          AT_STATX_SYNC_AS_STAT,
          STATX_TYPE | STATX_SIZE | STATX_MTIME,
          &sx) == -1) {
    perror("statx");
}

dirfd, path và các cờ AT_* xác định object; mask mô tả dữ liệu mong muốn; kernel trả kết quả trong struct statx. Zero-initialize cấu trúc để tránh đọc nhầm padding hoặc field không được trả về.

3. stx_mask là nguồn sự thật

Call thành công không có nghĩa mọi field được yêu cầu đều hợp lệ. Kernel có thể trả thêm field rẻ, hoặc không trả field filesystem không hỗ trợ. Luôn kiểm tra bit tương ứng:

if (sx.stx_mask & STATX_BTIME) {
    printf("created: %lld.%09u\n",
           (long long)sx.stx_btime.tv_sec,
           sx.stx_btime.tv_nsec);
} else {
    puts("creation time unavailable");
}

Giá trị dummy trong field thiếu không mang ý nghĩa nghiệp vụ. Không kiểm tra stx_btime.tv_sec != 0: epoch là thời điểm hợp lệ. Không yêu cầu UINT_MAX; bit chưa biết có thể trở thành extension trong tương lai và STATX__RESERVED bị từ chối. STATX_ALL đã deprecated; hãy liệt kê đúng field cần dùng.

4. Chọn object bằng dirfd và flags

MẫuÝ nghĩa
AT_FDCWD, "a/b"Path tương đối từ current working directory.
rootfd, "a/b"Path tương đối từ directory descriptor.
fd, "", AT_EMPTY_PATHMetadata của object đã mở, kể cả FD O_PATH.
AT_SYMLINK_NOFOLLOWMetadata của symlink cuối thay vì target.
AT_NO_AUTOMOUNTKhông kích hoạt automount ở component cuối chưa mount.

Từ Linux 6.11, path có thể là NULL khi dùng AT_EMPTY_PATH; dùng chuỗi rỗng vẫn tương thích với kernel cũ hơn. Directory FD giúp tránh phụ thuộc vào CWD, nhưng statx() tự nó không cung cấp các ràng buộc resolution như openat2().

5. Metadata theo FD để giảm race

Mẫu “stat theo path, sau đó open theo path” có race: tên có thể trỏ sang object khác giữa hai call. Khi quyết định liên quan đến object sẽ đọc, hãy mở trước bằng primitive phù hợp rồi query descriptor:

int fd = open("report.bin", O_RDONLY | O_CLOEXEC);
if (fd == -1)
    return -1;

struct statx sx = {0};
if (statx(fd, "", AT_EMPTY_PATH,
          STATX_TYPE | STATX_SIZE | STATX_MTIME,
          &sx) == -1) {
    close(fd);
    return -1;
}

Với path không tin cậy, dùng openat2() cùng policy resolution rồi statx(AT_EMPTY_PATH). Metadata không phải authorization token: ownership, mode, ACL hoặc attribute có thể đổi sau khi query, và kernel thực thi quyền tại operation thật.

6. Timestamp: btime không phải ctime

  • stx_btime: thời điểm tạo inode nếu filesystem ghi nhận.
  • stx_mtime: nội dung file thay đổi.
  • stx_ctime: trạng thái inode thay đổi, không phải creation time.
  • stx_atime: lần truy cập gần nhất, chịu ảnh hưởng của mount option như noatime/relatime.

Mỗi timestamp có giây 64-bit và nanosecond. Validate tv_nsec < 1,000,000,000 trước khi serialize từ nguồn không tin cậy, và giữ signed seconds để biểu diễn thời điểm trước epoch. Backup không nên giả định btime tồn tại hoặc được bảo toàn qua copy/restore.

7. Mount ID: phân biệt cùng inode trên các mount

STATX_MNT_ID yêu cầu ID của mount chứa file; ID này liên hệ với /proc/self/mountinfo. Từ Linux 6.8, STATX_MNT_ID_UNIQUE yêu cầu ID không tái sử dụng trong suốt thời gian hệ thống chạy và phù hợp hơn cho cache/telemetry dài hạn.

unsigned int wanted = STATX_BASIC_STATS |
                      STATX_MNT_ID_UNIQUE;

if (statx(fd, "", AT_EMPTY_PATH, wanted, &sx) == 0 &&
    (sx.stx_mask & STATX_MNT_ID_UNIQUE)) {
    printf("mount=%llu\n", (unsigned long long)sx.stx_mnt_id);
}

Mount ID chỉ có nghĩa trong mount namespace liên quan. Không dùng riêng stx_dev_major/minor + stx_ino làm định danh toàn cầu vĩnh viễn: inode có thể được tái sử dụng, bind mount tạo nhiều view và namespace thay đổi quan sát.

8. Query alignment trước khi dùng O_DIRECT

Từ Linux 6.1, STATX_DIOALIGN có thể trả:

  • stx_dio_mem_align: alignment của địa chỉ buffer;
  • stx_dio_offset_align: alignment của offset và độ dài I/O.
if ((sx.stx_mask & STATX_DIOALIGN) &&
    sx.stx_dio_mem_align != 0) {
    void *buf = NULL;
    int rc = posix_memalign(&buf,
                            sx.stx_dio_mem_align,
                            io_length);
    /* offset and io_length must also satisfy offset alignment */
}

Hỗ trợ phụ thuộc filesystem. Bit thiếu không có nghĩa O_DIRECT chắc chắn bị cấm; nó chỉ có nghĩa statx không cung cấp constraint. I/O lệch alignment có thể trả EINVAL hoặc rơi về buffered I/O tùy filesystem, nên storage engine phải có fallback được đo kiểm.

STATX_DIO_READ_ALIGN bổ sung alignment offset riêng cho read trên filesystem hỗ trợ. Luôn gate từng field bằng bit mask và headers lúc build.

9. Atomic-write capability

Kernel mới có STATX_WRITE_ATOMIC để mô tả torn-write protection cho direct I/O:

  • unit tối thiểu và tối đa;
  • unit tối đa tối ưu nếu có;
  • số segment tối đa cho vectored atomic write.

Đây là capability discovery, không tự biến write() bình thường thành atomic. Ứng dụng phải dùng operation/cờ ghi atomic tương ứng, đúng alignment và giới hạn filesystem/device. Nếu bit không được trả, hãy dùng WAL, copy-on-write, rename hoặc protocol durability hiện có; không suy diễn từ block size.

10. File attributes cần hai mask

stx_attributes_mask cho biết attribute nào có ý nghĩa; stx_attributes cho biết attribute hỗ trợ nào đang bật:

bool immutable_supported =
    sx.stx_attributes_mask & STATX_ATTR_IMMUTABLE;
bool immutable_enabled = immutable_supported &&
    (sx.stx_attributes & STATX_ATTR_IMMUTABLE);

Các bit có thể mô tả compressed, immutable, append-only, encrypted, verity, DAX, mount root và atomic-write support. “Bit không bật” khác với “filesystem không biết bit này”. UI và API nên biểu diễn ba trạng thái: true, false và unavailable.

11. Chính sách đồng bộ trên filesystem từ xa

CờTrade-off
AT_STATX_SYNC_AS_STATHành vi mặc định, tùy filesystem.
AT_STATX_FORCE_SYNCCố đồng bộ với server/device; chính xác hơn nhưng có thể phát sinh I/O và latency.
AT_STATX_DONT_SYNCƯu tiên cache; nhanh hơn nhưng dữ liệu có thể xấp xỉ/cũ.

File browser hoặc crawler có thể dùng DONT_SYNC và hiển thị dữ liệu eventual. Backup manifest trước khi commit có thể cần FORCE_SYNC. Đừng bật FORCE_SYNC cho một scan hàng triệu file mà không đo latency, tải metadata server và khả năng retry.

12. Snapshot không nguyên tử giữa mọi field

Ngay trong một lần gọi, các field có thể phản ánh những thời điểm hơi khác nhau nếu process khác đồng thời chmod(), chown(), truncate hoặc ghi file. Vì vậy:

  • không xem tuple size+mtime là hash nội dung;
  • không dùng metadata trước open làm bằng chứng object sau open giống hệt;
  • đọc nội dung qua FD ổn định và hash nếu cần integrity;
  • dùng file lease, lock hoặc application protocol nếu cần snapshot nghiệp vụ;
  • query lại sau operation khi cần phát hiện thay đổi.

statx() giàu metadata hơn, không phải transaction filesystem.

13. Wrapper có fallback hợp lý

int query_basic(int dirfd, const char *path, struct statx *sx)
{
    memset(sx, 0, sizeof(*sx));

    if (statx(dirfd, path, AT_STATX_SYNC_AS_STAT,
              STATX_TYPE | STATX_MODE | STATX_SIZE |
              STATX_MTIME | STATX_BTIME,
              sx) == 0)
        return 0;

    if (errno != ENOSYS)
        return -1;

    /* Fall back to fstatat() only for fields it can represent. */
    errno = ENOTSUP;
    return -1;
}

Trên hệ thống rất cũ hoặc sandbox chặn syscall, ENOSYS có thể yêu cầu fallback. Fallback phải hạ capability minh bạch: không bịa btime, mount ID hoặc DIO alignment. Cache feature detection theo môi trường chạy, nhưng vẫn xử lý filesystem cụ thể không hỗ trợ từng field.

Checklist production

  • Yêu cầu đúng các bit cần thiết; không dùng UINT_MAX hoặc STATX_ALL.
  • Kiểm tra stx_mask trước mỗi field tùy chọn.
  • Kiểm tra stx_attributes_mask trước stx_attributes.
  • Dùng dirfd/AT_EMPTY_PATH để gắn metadata với object đã mở.
  • Chọn sync policy theo SLA và loại filesystem.
  • Không nhầm ctime với creation time.
  • Không xem metadata là snapshot nguyên tử hay authorization token.
  • Gate feature mới theo headers, kernel và filesystem.
  • Fallback với trạng thái unavailable, không tạo giá trị giả.
  • Test trên ext4/XFS/Btrfs, tmpfs, overlayfs và network filesystem thực tế.

Kết luận

statx() không chỉ là “stat có thêm field”. Contract quan trọng nhất của nó là negotiation: ứng dụng yêu cầu một tập metadata, kernel trả một tập có thể khác, và stx_mask quyết định field nào được dùng. Khi kết hợp descriptor ổn định, synchronization policy có chủ đích và fallback minh bạch, statx trở thành nền tảng tốt cho công cụ filesystem và storage engine hiện đại mà không đánh đổi tính đúng đắn lấy những giả định ngầm.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.