coordinode-lsm-tree 6.0.0

Embedded LSM-tree storage engine in pure Rust, no C/C++ dependency. MVCC snapshots, BuRR filters, zstd dictionary compression, columnar PAX blocks, AES-256-GCM at rest, self-healing per-block ECC, compaction on a near-full disk, no_std support.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
use super::*;

// These tests require a Linux kernel with io_uring; they are compiled only
// under `cfg(target_os = "linux")` (the module gate) and run on the Linux
// CI / bench runner, not on the macOS dev host.

#[test]
fn raw_file_fsfile_round_trips() {
    // Exercises the full IoUringRawFile FsFile surface: sequential write
    // (ring), positioned read_at (ring, fill-or-EOF), metadata (statx),
    // Seek + sequential Read (cursor), set_len (ftruncate), fsync (ring),
    // and try_lock_exclusive (flock) — all over the raw no_std driver.
    use std::io::{Read, Seek, Write};

    let tmp = tempfile::tempdir().expect("tempdir");
    let path = tmp.path().join("iou_rawfile.bin");
    let cpath = std::ffi::CString::new(path.to_str().expect("utf8 path"))
        .expect("path has no interior NUL");

    let fd = open_raw(&cpath, O_CREAT | O_RDWR | O_TRUNC, 0o600).expect("openat should succeed");
    let ring = Arc::new(Mutex::new(IoUringRaw::new(8).expect("ring setup")));
    let mut file = IoUringRawFile::new(ring, fd, false);

    let payload = b"raw io_uring file round-trip payload";
    let written = Write::write(&mut file, payload).expect("write");
    assert_eq!(written, payload.len(), "short write");

    // statx-backed metadata reflects the written length.
    assert_eq!(
        FsFile::metadata(&file).expect("metadata").len,
        payload.len() as u64,
        "metadata len must match what was written"
    );

    // statx-backed hard-link count: a freshly created file has exactly one link
    // (not the `Unsupported` default that would make in-place heal skip reclaim).
    assert_eq!(
        FsFile::hard_link_count(&file).expect("hard_link_count"),
        1,
        "a freshly created file has a single hard link"
    );

    // Positioned fill-or-EOF read returns the exact bytes.
    let mut rb = vec![0u8; payload.len()];
    let n = FsFile::read_at(&file, &mut rb, 0).expect("read_at");
    assert_eq!(n, payload.len(), "short read_at");
    assert_eq!(&rb, payload, "read_at bytes must match");

    // Seek + sequential Read from an offset uses the userspace cursor.
    file.seek(std::io::SeekFrom::Start(4)).expect("seek");
    let mut tail = vec![0u8; payload.len() - 4];
    let mut got = 0;
    while got < tail.len() {
        let chunk = tail.get_mut(got..).expect("in-bounds");
        let r = Read::read(&mut file, chunk).expect("read");
        if r == 0 {
            break;
        }
        got += r;
    }
    assert_eq!(
        tail.get(..got).expect("in-bounds"),
        payload.get(4..).expect("in-bounds"),
        "seek+read bytes must match"
    );

    FsFile::sync_all(&file).expect("sync_all");
    FsFile::sync_data(&file).expect("sync_data");

    // ftruncate shrinks the file; metadata reflects it.
    FsFile::set_len(&file, 4).expect("set_len");
    assert_eq!(
        FsFile::metadata(&file).expect("metadata after set_len").len,
        4,
        "set_len must shrink the file"
    );

    // A fresh file is unlocked, so a non-blocking exclusive lock succeeds.
    assert!(
        FsFile::try_lock_exclusive(&file).expect("try_lock_exclusive"),
        "exclusive lock on a fresh file must succeed"
    );

    drop(file); // closes the fd; `tmp` drops at end of scope, removing the dir
}

#[test]
fn raw_fs_directory_and_file_ops() {
    // Exercises the IoUringRawFs Fs surface end to end: create_dir_all
    // (mkdirat), open + write (ring), metadata + exists (statx), read_dir
    // (getdents64), rename (renameat2), remove_file + remove_dir_all
    // (unlinkat) — all over the raw no_std driver.
    use crate::fs::Fs;
    use crate::path::Path;
    use std::io::Write;

    // A subdirectory under a `tempfile::tempdir()` guard: `create_dir_all`
    // creates it, and the guard removes the whole tree on drop even if an
    // assertion panics before the explicit `remove_dir_all` below.
    let tmp = tempfile::tempdir().expect("tempdir");
    let base = tmp.path().join("rawfs");
    let base_s = base.to_str().expect("utf8 temp path");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let dir = Path::new(base_s);

    fs.create_dir_all(dir).expect("create_dir_all");
    assert!(fs.exists(dir).expect("dir exists"));
    assert!(fs.metadata(dir).expect("dir metadata").is_dir);

    // Open + write a file through the ring.
    let fpath = dir.join("a.txt");
    let mut file = fs
        .open(&fpath, &FsOpenOptions::new().write(true).create(true))
        .expect("open");
    file.write_all(b"hello").expect("write");
    file.sync_all().expect("sync");
    drop(file);

    // statx-backed metadata + getdents64 listing see the file.
    assert_eq!(fs.metadata(&fpath).expect("file metadata").len, 5);
    let entries = fs.read_dir(dir).expect("read_dir");
    assert!(
        entries.iter().any(|e| e.file_name == "a.txt" && !e.is_dir),
        "read_dir must list the written file"
    );

    // rename (renameat2) moves it.
    let fpath2 = dir.join("b.txt");
    fs.rename(&fpath, &fpath2).expect("rename");
    assert!(!fs.exists(&fpath).expect("old gone"));
    assert!(fs.exists(&fpath2).expect("new present"));

    // unlinkat removes the file, then the (now empty) directory.
    fs.remove_file(&fpath2).expect("remove_file");
    assert!(!fs.exists(&fpath2).expect("file gone"));
    fs.remove_dir_all(dir).expect("remove_dir_all");
    assert!(!fs.exists(dir).expect("dir gone"));
}

/// Two hard links of one inode are one file and a separate file with the same
/// bytes is not, by device and inode as the other kernel backends answer; a
/// name that does not exist has no identity, and the probe says so.
#[test]
fn raw_fs_same_file_answers_by_identity() {
    use crate::fs::Fs;

    let tmp = tempfile::tempdir().expect("tempdir");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let a = tmp.path().join("a");
    let b = tmp.path().join("b");
    let c = tmp.path().join("c");
    std::fs::write(&a, b"payload").expect("write a");
    std::fs::hard_link(&a, &b).expect("link b");
    std::fs::write(&c, b"payload").expect("write c");

    assert!(
        fs.same_file(&a, &b).expect("probe"),
        "two names of one inode"
    );
    assert!(!fs.same_file(&a, &c).expect("probe"), "two inodes");
    let missing = fs
        .same_file(&a, &tmp.path().join("missing"))
        .expect_err("a missing name has no identity");
    assert_eq!(missing.kind(), ErrorKind::NotFound);
}

/// A symlink's target is read as stored, without following it; a regular
/// file and a missing name are not symlinks.
#[test]
fn raw_fs_read_link_reports_only_symlinks() {
    use crate::fs::Fs;

    let tmp = tempfile::tempdir().expect("tempdir");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let file = tmp.path().join("file");
    let link = tmp.path().join("link");
    std::fs::write(&file, b"payload").expect("write");
    std::os::unix::fs::symlink("dangling", &link).expect("symlink");

    assert_eq!(
        fs.read_link(&link).expect("read_link"),
        Some(crate::path::PathBuf::from("dangling"))
    );
    assert_eq!(fs.read_link(&file).expect("read_link"), None);
    // A target that is not UTF-8 comes back as its bytes.
    let raw = tmp.path().join("raw");
    {
        use std::os::unix::ffi::OsStrExt as _;
        std::os::unix::fs::symlink(std::ffi::OsStr::from_bytes(b"t\xff"), &raw).expect("symlink");
        assert_eq!(
            fs.read_link(&raw).expect("read_link"),
            Some(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(
                b"t\xff"
            )))
        );
    }
    assert_eq!(
        fs.read_link(&tmp.path().join("missing"))
            .expect("read_link"),
        None
    );
}

/// A symbolic link the raw backend makes holds its target as given, so a
/// crash simulator over this backend can put a lost link back.
#[test]
fn raw_fs_symlink_makes_a_link_read_link_reads_back() {
    use crate::fs::Fs;

    let tmp = tempfile::tempdir().expect("tempdir");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let link = tmp.path().join("link");
    fs.symlink(std::path::Path::new("elsewhere"), &link, false)
        .expect("symlink");
    assert_eq!(
        fs.read_link(&link).expect("read_link"),
        Some(crate::path::PathBuf::from("elsewhere"))
    );
}

#[test]
fn raw_file_append_writes_accumulate_at_eof() {
    // O_APPEND writes always land at end of file: two sequential appends
    // (across reopens) both persist, in order — exercising the kernel's
    // atomic-append path (offset -1) the write impl uses for append mode.
    use crate::fs::Fs;
    use crate::path::Path;
    use std::io::Write;

    let tmp = tempfile::tempdir().expect("tempdir");
    let p = tmp.path().join("log.bin");
    let ps = p.to_str().expect("utf8 temp path");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let path = Path::new(ps);

    {
        let mut f = fs
            .open(path, &FsOpenOptions::new().append(true).create(true))
            .expect("open append");
        f.write_all(b"aaa").expect("first append");
        f.sync_all().expect("sync");
    }
    {
        let mut f = fs
            .open(path, &FsOpenOptions::new().append(true).create(true))
            .expect("reopen append");
        f.write_all(b"bbb").expect("second append");
        f.sync_all().expect("sync");
    }

    let f = fs
        .open(path, &FsOpenOptions::new().read(true))
        .expect("open read");
    let mut buf = [0u8; 6];
    let n = f.read_at(&mut buf, 0).expect("read_at");
    assert_eq!(n, 6, "both appends must be present");
    assert_eq!(&buf, b"aaabbb", "appends land in order at EOF");
}

#[test]
fn raw_fs_no_wait_refuses_a_pipe_with_no_reader_and_opens_a_file() {
    // `no_wait` fails the open of a named pipe nothing reads rather than
    // block it, and still opens a regular file, which then writes as any.
    use crate::fs::Fs;
    use crate::path::Path;
    use std::io::Write;

    let tmp = tempfile::tempdir().expect("tempdir");
    let pipe = tmp.path().join("pipe");
    let file = tmp.path().join("file");
    let name = std::ffi::CString::new(pipe.to_str().expect("utf8")).expect("no NUL");
    // SAFETY: `name` is a NUL-terminated path the call only reads.
    assert_eq!(unsafe { libc::mkfifo(name.as_ptr(), 0o600) }, 0, "mkfifo");
    std::fs::write(&file, b"").expect("file");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let opts = FsOpenOptions::new().write(true).no_wait(true);

    assert!(
        fs.open(Path::new(pipe.to_str().expect("utf8")), &opts)
            .is_err(),
        "a pipe with no reader is refused"
    );
    let mut opened = fs
        .open(Path::new(file.to_str().expect("utf8")), &opts)
        .expect("a regular file opens");
    opened.write_all(b"a table").expect("write");
    drop(opened);
    assert_eq!(std::fs::read(&file).expect("read"), b"a table");
}

#[test]
fn raw_fs_no_follow_refuses_a_symlink_and_opens_a_file() {
    // `no_follow` refuses a symbolic link at the last component (`ELOOP`),
    // so nothing is written through it into the file it names, and still
    // opens a regular file.
    use crate::fs::Fs;
    use crate::path::Path;

    let tmp = tempfile::tempdir().expect("tempdir");
    let target = tmp.path().join("target");
    let link = tmp.path().join("link");
    std::fs::write(&target, b"").expect("target");
    std::os::unix::fs::symlink(&target, &link).expect("symlink");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let opts = FsOpenOptions::new().write(true).no_follow(true);

    assert!(
        fs.open(Path::new(link.to_str().expect("utf8")), &opts)
            .is_err(),
        "a symlink is refused"
    );
    assert!(
        fs.open(Path::new(target.to_str().expect("utf8")), &opts)
            .is_ok(),
        "a regular file opens"
    );
}

#[test]
fn ring_setup_and_nop_round_trips() {
    let mut ring = IoUringRaw::new(8).expect("io_uring_setup + mmap should succeed");
    assert!(
        ring.sq_entries() >= 8,
        "kernel rounds entries up to >= request"
    );
    // A NOP completes immediately with res == 0 and echoes our user_data
    // slot through the full submit -> enter -> complete cycle.
    let res = ring
        .nop(0xDEAD_BEEF)
        .expect("NOP submit/complete should succeed");
    assert_eq!(res, 0, "NOP res must be 0");
}

#[test]
fn multiple_nops_reuse_slots() {
    let mut ring = IoUringRaw::new(4).expect("setup");
    // Submit more NOPs than the ring depth to exercise tail/head wraparound
    // and slot reuse across several enter calls.
    for i in 0..16u64 {
        let res = ring.nop(i).expect("nop");
        assert_eq!(res, 0);
    }
}

#[test]
fn file_write_fsync_read_round_trips_through_the_ring() {
    // Real data path: open a file (raw openat), write a payload at an
    // offset through the ring, fsync it through the ring, read it back
    // through the ring, and verify the bytes — then close (raw) + clean up.
    let path = std::env::temp_dir().join(format!(
        "iou_raw_rt_{}_{}.bin",
        std::process::id(),
        // a per-test suffix so parallel runs do not collide
        line!()
    ));
    let cpath = std::ffi::CString::new(path.to_str().expect("utf8 path"))
        .expect("path has no interior NUL");

    let fd = open_raw(&cpath, O_CREAT | O_RDWR | O_TRUNC, 0o600).expect("openat should succeed");

    let mut ring = IoUringRaw::new(8).expect("ring setup");

    let payload = b"io_uring raw round-trip payload";
    let offset = 4096u64; // a non-zero offset to exercise positioned I/O

    let written = ring
        .write_at(fd, payload, offset)
        .expect("write_at should succeed");
    assert_eq!(written, payload.len(), "short write");

    ring.fsync(fd, false).expect("fsync should succeed");

    let mut readback = vec![0u8; payload.len()];
    let read = ring
        .read_at(fd, &mut readback, offset)
        .expect("read_at should succeed");
    assert_eq!(read, payload.len(), "short read");
    assert_eq!(
        &readback, payload,
        "read-back bytes must match what we wrote"
    );

    // Reading past EOF returns 0 bytes (the file is exactly offset+len long).
    let mut tail = [0u8; 8];
    let eof = ring
        .read_at(fd, &mut tail, offset + payload.len() as u64)
        .expect("read at EOF should succeed");
    assert_eq!(eof, 0, "read past EOF must return 0");

    close_raw(fd).expect("close should succeed");
    let _ = std::fs::remove_file(&path);
}

#[test]
fn errno_maps_to_expected_error_kinds() {
    // Every explicitly-mapped errno lands on its kind; anything else folds
    // into `Other`. Covers the whole `errno_to_kind` table without needing
    // to provoke each real failure.
    assert_eq!(errno_to_kind(1), ErrorKind::PermissionDenied); // EPERM
    assert_eq!(errno_to_kind(2), ErrorKind::NotFound); // ENOENT
    assert_eq!(errno_to_kind(4), ErrorKind::Interrupted); // EINTR
    assert_eq!(errno_to_kind(9), ErrorKind::InvalidInput); // EBADF
    assert_eq!(errno_to_kind(11), ErrorKind::WouldBlock); // EAGAIN
    assert_eq!(errno_to_kind(13), ErrorKind::PermissionDenied); // EACCES
    assert_eq!(errno_to_kind(17), ErrorKind::AlreadyExists); // EEXIST
    assert_eq!(errno_to_kind(22), ErrorKind::InvalidInput); // EINVAL
    // Arch-specific (95 asm-generic, 122 MIPS, 45 SPARC).
    assert_eq!(errno_to_kind(libc::EOPNOTSUPP), ErrorKind::Unsupported);
    assert_eq!(errno_to_kind(132), ErrorKind::Other); // unmapped
}

/// A failure of the environment rather than of the file (no descriptors, no
/// memory, no space, a dropped network mount) must reach recovery grading as
/// an environmental kind: read as `Other`, it condemns an intact table.
#[test]
fn environment_errnos_map_to_environmental_kinds() {
    for (errno, kind) in [
        (libc::ENOMEM, ErrorKind::OutOfMemory),
        (libc::EMFILE, ErrorKind::TooManyOpenFiles),
        (libc::ENFILE, ErrorKind::TooManyOpenFiles),
        (libc::ENOSPC, ErrorKind::StorageFull),
        (libc::EROFS, ErrorKind::ReadOnlyFilesystem),
        (libc::EDQUOT, ErrorKind::QuotaExceeded),
        (libc::EPIPE, ErrorKind::BrokenPipe),
        (libc::ENETDOWN, ErrorKind::NetworkDown),
        (libc::ENETUNREACH, ErrorKind::NetworkUnreachable),
        (libc::ECONNABORTED, ErrorKind::ConnectionAborted),
        (libc::ECONNRESET, ErrorKind::ConnectionReset),
        (libc::ENOTCONN, ErrorKind::NotConnected),
        (libc::ETIMEDOUT, ErrorKind::TimedOut),
        (libc::ECONNREFUSED, ErrorKind::ConnectionRefused),
        (libc::EHOSTUNREACH, ErrorKind::HostUnreachable),
        (libc::ESTALE, ErrorKind::StaleNetworkFileHandle),
    ] {
        assert_eq!(errno_to_kind(errno), kind, "errno {errno}");
        assert!(kind.is_environmental(), "{kind:?} for errno {errno}");
    }
    assert_eq!(errno_to_kind(libc::EXDEV), ErrorKind::CrossesDevices);
}

#[test]
fn ring_setup_with_zero_entries_is_rejected() {
    // `io_uring_setup` rejects a zero-entry ring with EINVAL; this covers
    // the setup error path (and the fd guard never arming). `IoUringRaw`
    // is not `Debug`, so match rather than `expect_err`.
    match IoUringRaw::new(0) {
        Ok(_) => panic!("zero-entry ring must be rejected"),
        Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput),
    }
}

#[test]
fn open_raw_missing_file_returns_not_found() {
    // Opening a missing path read-only (no O_CREAT) fails with ENOENT,
    // exercising `open_raw`'s error path and the `err` -> `ErrorKind`
    // conversion.
    let cpath =
        std::ffi::CString::new("/proc/does-not-exist/iou_raw_missing").expect("no interior NUL");
    let err = open_raw(&cpath, O_RDONLY, 0).expect_err("missing file must fail to open");
    assert_eq!(err.kind(), ErrorKind::NotFound);
}

#[test]
fn ring_op_on_bad_fd_surfaces_completion_error() {
    // A write against a descriptor that is not open completes with -EBADF;
    // the reaper must surface that negative `res` as an error (covering the
    // `res < 0` branch of `submit_and_reap_one`).
    let mut ring = IoUringRaw::new(4).expect("ring setup");
    let err = ring
        .write_at(1 << 30, b"x", 0)
        .expect_err("write to a non-open fd must fail");
    assert_eq!(err.kind(), ErrorKind::InvalidInput); // EBADF -> InvalidInput
}

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[test]
fn raw_available_space_reports_plausible_free_bytes() {
    // The raw `statfs` syscall path: the filesystem backing the tempdir must
    // report a plausible, non-zero free figure below the unbounded sentinel.
    use crate::fs::Fs;
    use crate::path::Path;

    let tmp = tempfile::tempdir().expect("tempdir");
    let fs = IoUringRawFs::new(8).expect("fs setup");
    let free = fs
        .available_space(Path::new(tmp.path().to_str().expect("utf8 path")))
        .expect("statfs must succeed on a real filesystem");
    assert!(
        free > 0,
        "a writable tempdir filesystem must report free space"
    );
    assert!(
        free < u64::MAX,
        "a real probe must not return the unbounded sentinel"
    );
}

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[test]
fn raw_statfs_on_missing_path_errors() {
    // `statfs` on a path that does not exist surfaces an error, not a silent
    // zero or the unbounded sentinel — exercising the syscall error branch.
    let cpath =
        std::ffi::CString::new("/proc/does-not-exist/iou_raw_statfs").expect("no interior NUL");
    assert!(statfs_available_raw(&cpath).is_err());
}