Writing/·6 min read

Ubuntu 26.10's rust coreutils swap: it's time to check your scripts

Ubuntu 26.10 completes its Rust coreutils migration, including cp, mv, and rm. This guide offers a checklist for engineers to test CI pipelines and deploy scripts for subtle behavioral changes.

Share
ubuntu-rust-cover

The news dropped on September 13th: OMG! Ubuntu reported that 26.10, due October 15th, would ship with its coreutils fully rewritten in Rust. Not just ls and cat like before, but the big three we were warned about last year: cp, mv, and rm. For anyone running a CI pipeline or managing base images, that's not just an upgrade, it's a tremor under your infrastructure.

Memory safety is a good thing, absolutely. Microsoft even made Rust a Tier 1 internal language for Windows on September 11th. But forty years of GNU coreutils isn't just C code waiting to segfault. It's forty years of accumulated, often undocumented, behaviour. That behaviour, good or bad, is what your deploy scripts, build systems, and even some Makefile targets rely on. When the underlying binary changes, those scripts are running on shifting sand.

Canonical deserves credit for their process here. They commissioned a security audit from Zellic before 26.04 LTS. That audit found time-of-check to time-of-use (TOCTOU) issues in the Rust cp, mv, and rm implementations, so those three were held back. That's a good sign they care about more than just the language. It means they value logic safety and reliability, not just memory safety. Now, with 26.10, these tools are deemed ready. But your readiness is a different matter.

The shifting landscape of core utilities

Ubuntu's migration to Rust uutils has been ongoing. The 25.10 release already brought a Rust-based sudo and other uutils like ls and cat, though it also introduced a documented date command bug that later got ironed out. The 26.04 LTS release, crucially, kept cp, mv, and rm on their GNU counterparts. Now, 26.10 completes the swap.

Here's a quick overview of how the core utilities have transitioned:

UtilityStatus in 25.10Status in 26.04 LTSStatus in 26.10
lsRust uutilsRust uutilsRust uutils
catRust uutilsRust uutilsRust uutils
chmodRust uutilsRust uutilsRust uutils
duRust uutilsRust uutilsRust uutils
cpGNU coreutilsGNU coreutilsRust uutils
mvGNU coreutilsGNU coreutilsRust uutils
rmGNU coreutilsGNU coreutilsRust uutils
dateRust uutils (bug reported)Rust uutils (fixed)Rust uutils

Why cp, mv, and rm are the ones to test

These three tools are the workhorses of any Unix system. They're invoked with a huge variety of flags, their exit codes are branched on, and sometimes, their error messages are even grepped for specific text. Your application doesn't call ls in a way that breaks a build. It calls cp -a to preserve permissions, or mv to atomically replace a file, or rm -rf on a directory with tricky permissions. These are the operations that hit edge cases, and that's where the differences will show up.

The compatibility checklist: your CI pipeline is a testbed

This isn't just about whether a command runs. It's about whether it runs the same way. The only way to know is to test. Don't wait for a production incident. The Hacker News discussion for this change already has roughly 270 points and 295 comments, which tells you people expect friction.

Step 1: Prepare your test environment

Spin up a container running the Ubuntu 26.10 beta. This is your canary in the coal mine. You need a baseline too, so have a 24.04 LTS or 26.04 LTS container ready for comparison.

Step 2: Run your scripts defensively

Execute your deploy, build, and setup scripts inside the 26.10 container, but with strict shell settings. This forces your scripts to fail early on any unexpected behaviour.

set -euo pipefail
/path/to/your/deploy_script.sh
  • set -e: Exit immediately if a command exits with a non-zero status.
  • set -u: Treat unset variables as an error.
  • set -o pipefail: The return value of a pipeline is the status of the last command to exit with a non-zero status, or zero if all commands exit successfully.

Step 3: Compare command behaviour

This is where the real work happens. Focus on the nuances:

  • Flag coverage: Run your cp, mv, and rm commands with all the flags you typically use. Does cp --reflink=always still work as expected? Does cp -a correctly preserve special file types, timestamps, and extended attributes (xattr)? What about mv across different filesystems? Does rm -rf handle directories with mixed permissions, or files with immutable flags, the same way?
  • Exit codes: A command might succeed, but with a different exit code for a specific condition. Your scripts might branch on $? for nuanced error handling. Test those branches.
  • Stderr wording: Some scripts grep stderr for specific error messages. If the wording changes, your error parsing might break. While not ideal, it's a reality in many legacy systems.
  • Special contexts: If you deal with xattr or SELinux contexts, ensure cp and mv preserve them correctly.

Step 4: The escape hatch: knowing what you're running

If you find issues, you need to know which utility you're dealing with. The uutils and GNU versions will have different --version outputs. For example:

# uutils cp --version output will look different
cp --version
 
# GNU cp --version output (example from GNU coreutils 9.1)
# cp (GNU coreutils) 9.1
# Copyright (C) 2023 Free Software Foundation, Inc.
# License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

If you hit a blocker, you can explicitly install the GNU coreutils package and adjust your PATH or invoke the GNU binary directly. This isn't a long-term solution, but it's a critical rollback strategy to document before you need it in an incident.

A note on licensing

The original GNU coreutils are licensed under the GPL. The Rust uutils project, on the other hand, uses the MIT license. This distinction matters if you're redistributing container images or system builds. Ensure your legal team is aware of this change, particularly if your internal policies depend on specific open-source licenses.

This isn't about Rust versus C. It's about earned behaviour versus rewritten behaviour. Canonical made the right call to hold back cp, mv, and rm until the audit's findings were addressed. Now, it's your turn to make the right call and verify your systems. Behaviour has to be re-earned, and that starts with testing. The October 15th release date for 26.10 is coming fast; don't get caught off guard.

Share
Get the next one by email

Essays on shipping software and building products. No schedule, no filler.