sync FILE hangs because of a USB disk I/O errors, applies global sync for no apparent reasons.

Open
#14,591 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
30/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
linux, rust

Research direction

Start in src/uu/sync/src/sync.rs and trace the no-flags dispatch, then inspect the platform sync helpers and tests/by-util/test_sync.rs. Run the sync test command named in the issue; done means file operands use the documented per-file behavior without changing bare sync, and the regression test passes.

Written by the indexing model from the issue text.

Description

U - sync

I have a ksmbd server serving three USB devices on the network,
then, I tried installing stuff with apt install,
then, initramfs got stuck,
I did some investigation about what was causing it, because no way initramfs should hang just because of my USB devices,
turns out some transcribing incoherence is present, at least according to Big Pickle, I am not an expert about linux, but the bug was real,
Big pickle identified a path that differs in the old GNU rustutils vs rust coreutils.

I noticed a maybe duplicate according to the agent but there isnt much details/info :
https://github.com/uutils/coreutils/issues/12776

Ubuntu bug report: sync FILE performs a global sync() and ignores the file argument

Package: rust-coreutils 0.8.0-0ubuntu3

Binary shipped as: /usr/bin/sync -> uutils coreutils 0.8.0
Distribution: Ubuntu 26.04.1 LTS, kernel 7.0.0-31-generic (x86_64)


Summary

On Ubuntu 26.04, sync is the Rust uutils implementation (package
rust-coreutils), meant as a drop-in for GNU coreutils sync. It is not.

sync FILE with no flags performs a global sync(2) — flushing every
mounted filesystem and waiting on every block device — instead of the
documented fsync(2) on the given file. The file operand is opened, then
discarded. This violates the documented GNU contract:

If one or more files are specified, sync only them, or their containing file
systems. -- sync(1)

Behavior matrix

invocation GNU (reference) rust-coreutils (shipped) match?
sync sync() sync() ok
sync FILE fsync(FILE) sync() BUG
sync -f FILE syncfs(FILE) syncfs(FILE) ok
sync -d FILE fdatasync(FILE) fdatasync(FILE) ok

Only the no-flags + file operand combination is wrong — also the most
common way scripts call sync. strace proof on the same file:

$ strace -e trace=sync,fsync sync /boot/initrd.img-$(uname -r)
gnusync  -> fsync(3)     (GNU 9.7, correct)
sync     -> sync()       (rust-coreutils 0.8.0, the bug)

The bug, end to end (1 → 2 → 3 → 4)

1 — the call (/usr/bin/dracut:3606):

if ! sync "$outfile" 2> /dev/null; then

2 — the bug (src/uu/sync/src/sync.rs, 0.8.0 / upstream main):

    } else {
        sync()?;          // ✗ global sync — file operand ignored
    }

3 — the fix (sync.rs corrected):

    } else if !files.is_empty() {
        fsync(&files)?;   // ✓ per-file fsync
    } else {
        sync()?;          // bare `sync` only
    }

4 — the reference (src/sync.c, GNU coreutils master):

else if (! arg_data)
  mode = MODE_FILE;            // ← sync FILE lands here
...
case MODE_FILE:
  sync_status = fsync (fd);    // → one file flushed

1 → 2 = the trap. 3 ≡ 4 = the fix.

Root cause

The unix platform module implements do_sync, do_syncfs, do_fdatasync but
no do_fsync — a per-file fsync(2) path does not exist on Linux. The
default branch is the pre-file-operand full-sync stub:

if matches.get_flag(options::FILE_SYSTEM) {
    syncfs(&files)?;
} else if matches.get_flag(options::DATA) {
    fdatasync(&files)?;
} else {
    sync()?;    // ← fires even when `files` is non-empty
}

Introduced in commit 11ecf80 (2020-11-29, PR #1639) when -d/-f were
added; still present in main as of 2026-09-15 (~6 years old). Nothing in the
history documents this as intentional.

Real-world impact

dracut's sync "$outfile" becomes a global sync(), which synchronously waits
on every attached device. On the affected machine a USB-attached NTFS disk
was in an error state; the global sync blocked in the kernel's
ntfs_sync_fs -> blkdev_issue_flush -> submit_bio_wait path and dpkg --configure -a / initramfs regeneration hung indefinitely.

Separate I/O issue noted, not investigated here (the trigger that made the
hang possible):

kernel: sd 1:0:0:0: [sdb] tag#0 CDB: Synchronize Cache(10) 35 00...
kernel: I/O error, dev sdb, sector 0 op 0x1:(WRITE) flags 0x800 phys_seg 0 prio class 2
kernel: hung tasks (udisksd, udev) blocked for >120s in submit_bio_wait

A correct sync FILE (fsync) would only have flushed the initramfs file on the
root ext4 filesystem and never touched the USB disk.

Workaround / local fix (applied on this box)

Two options, both restoring sync FILE -> fsync(file):

  1. Symlink to the shipped GNU binary (no build required):

    ln -s /usr/bin/gnusync /usr/local/bin/sync
    

    Does not survive removal of coreutils.

  2. Self-built patched uutils 0.8.0 (preferred; current state here):
    /usr/local/bin/sync is uutils coreutils 0.8.0 rebuilt with the fix
    below, verified by strace (sync FILE -> fsync(3) = 0). The previous
    symlink is backed up at /usr/local/bin/sync.gnusymlink.bak; revert with
    rm /usr/local/bin/sync && mv /usr/local/bin/sync.gnusymlink.bak /usr/local/bin/sync.

Suggested fix

// platform (unix)
pub fn do_fsync(files: &[String]) -> UResult<()> {
    do_sync_with(files, rustix::fs::fsync)
}

// uumain dispatch
} else if !files.is_empty() {
    fsync(&files)?;   // GNU MODE_FILE behavior
} else {
    sync()?;          // only for bare `sync`
}

Fix status: commits, patch, and regression test (checked 2026-09-15)

Prepared against uutils/coreutils main (base commit 4f11d2b17), as a
standalone patch (a PR is planned separately):

  • Fix commit: b9dfbeaee — "sync: fsync() file operands when no -d/-f flags
    (GNU MODE_FILE compat)", on branch fix-sync-fsync-file-operands.
  • Patch: git format-patch output in ~/Desktop/pr-sync-fsync/0001-….patch
    (applies with git am).
  • 0.8.0 backport (this box's shipped version, nix-based code):
    ~/Desktop/pr-sync-fsync/backport-0.8.0-sync.patch; prebuilt x86_64 binary
    in the same folder.

The change follows the existing fdatasync/syncfs structure, three parts:

  • dispatch: else if !files.is_empty() { fsync(&files)?; } — the decision
  • glue: fn fsync(files) { platform::do_fsync(files) } — pass-through
  • worker: pub fn do_fsync(files) { do_sync_with(files, rustix::fs::fsync) }
    — the per-file fsync(2) loop

Regression test added to tests/by-util/test_sync.rs:

#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_sync_file_operands_fsync_each_file() {
    new_ucmd!().arg("/proc/self/mem").fails().stderr_contains("error syncing");
}
  • Discriminates the bug: on unpatched code sync /proc/self/mem exits 0 via
    the global sync() (test fails "Command was expected to fail"); with the fix
    it hits fsync(2), gets EINVAL, exits 1 -> test passes.
  • Run with: cargo test --features sync --test tests test_sync_
  • Result: full sync suite 15/15 on patched main and on the 0.8.0 backport.

Upstream status (checked 2026-09-15, uutils/coreutils main)

  • Bug still present in main; no issue reports this specific case.
  • Fix exists only as the standalone patch above (b9dfbeaee), not yet merged.
  • Adjacent: PR #11393 (merged 2026-04-03) fixed sync -f with no operands
    (different corner of the same dispatch); issue #12776 (open) covers sync FILE swallowing per-file fsync errors on /proc/self/mem, /dev/full
    both consequences of the missing per-file fsync path.

Reproduction (root)

strace -e trace=sync,syncfs,fsync,fdatasync sync /boot/initrd.img-$(uname -r)
# Expected (GNU): fsync(3)   Actual (rust-coreutils 0.8.0): sync()
Dominant language
Rust
Stars
24.1k
Forks
2k
Avg merge
1d 5h
Merged PRs (30d)
365

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from uutils/coreutils

All issues in uutils/coreutils

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.