Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ What's new in ps5upload, written for humans.

---

## 3.3.21

- **Fixed: moving files from USB to the internal SSD crashed the console.** Cut
& paste from a USB drive to internal storage was reliably kernel-panicking the
PS5 (a hard crash + reboot). The tool was asking the console to *rename* the
file across drives, which this kernel can't do — it panics instead of
reporting the error. ps5upload now detects a cross-drive move up front and
completes it the safe way (copy, then remove the original) — the same thing it
already did for copies. **Hardware-verified: the move that used to crash the
console now completes without a hitch.** (Same fix applied to the shell tab's
`mv` command.)

## 3.3.20

- **Installs wait for the PS5 to be ready — fewer "couldn't be applied" errors.**
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.20
3.3.21
4 changes: 2 additions & 2 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ps5upload-client",
"private": true,
"version": "3.3.20",
"version": "3.3.21",
"description": "The all-in-one PS5 companion app.",
"homepage": "https://github.com/phantomptr/ps5upload",
"author": "PhantomPtr <phantomptr@gmail.com>",
Expand Down
10 changes: 5 additions & 5 deletions client/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ps5upload-desktop"
version = "3.3.20"
version = "3.3.21"
description = "The all-in-one PS5 companion app."
edition = "2021"
rust-version = "1.77"
Expand Down
2 changes: 1 addition & 1 deletion client/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "PS5Upload",
"version": "3.3.20",
"version": "3.3.21",
"identifier": "com.phantomptr.ps5upload",
"build": {
"beforeDevCommand": "npm run dev:vite",
Expand Down
12 changes: 7 additions & 5 deletions client/src/screens/FileSystem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ import { humanizePs5Error } from "../../lib/humanizeError";
* from the toolbar whenever the clipboard is non-empty, pasting into
* the currently-browsed directory.
*
* Cross-volume note: fsMove uses rename() which fails with EXDEV across
* mount points. fsCopy works across mounts because the payload reads +
* writes bytes explicitly. The UI falls back to "Copy, then delete"
* when a Cut+Paste fails with EXDEV — matches how file managers on
* Linux/macOS handle cross-filesystem moves.
* Cross-volume note: fsMove is rename()-based, which only works within one
* volume. Across mounts (e.g. USB → internal) the payload does NOT attempt the
* rename — a cross-device rename panics the PS5 kernel — and returns
* `fs_move_cross_mount`. fsCopy works across mounts because the payload reads +
* writes bytes explicitly, so the UI falls back to "Copy, then delete" on that
* error — matching how file managers on Linux/macOS handle cross-filesystem
* moves.
*/

interface DirEntry {
Expand Down
14 changes: 7 additions & 7 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ resolver = "2"
[workspace.package]
edition = "2021"
license = "GPL-3.0-or-later"
version = "3.3.20"
version = "3.3.21"

[workspace.dependencies]
anyhow = "1.0"
Expand Down
13 changes: 7 additions & 6 deletions engine/crates/ps5upload-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,12 +1301,13 @@ async fn ps5_fs_move(
crate::log_info!("fs_move: addr={addr} from={from} to={to}");
let from_for_log = from.clone();
let to_for_log = to.clone();
// 1-hour deadline: most fs_move calls return in milliseconds
// (rename(2) is metadata-only), but cross-volume moves that the
// payload retries via copy-then-delete inherit the same long
// bound as fs_copy. The default 30 s socket timeout would fire
// mid-copy of any multi-GiB file and surface as the cryptic "read
// frame header" 502.
// 1-hour deadline: an intra-volume fs_move returns in milliseconds
// (rename(2) is metadata-only). A CROSS-volume move can't rename (the
// payload refuses it — a cross-device rename panics this kernel) and
// returns `fs_move_cross_mount`; the CLIENT then completes it as
// copy-then-delete, which can run for minutes on a multi-GiB file. Keep the
// generous bound so the default 30 s socket timeout can't fire mid-op and
// surface as the cryptic "read frame header" 502.
let io_timeout = std::time::Duration::from_secs(60 * 60);
match tokio::task::spawn_blocking(move || {
fs_move_with_timeout(&addr, &from, &to, Some(io_timeout))
Expand Down
2 changes: 1 addition & 1 deletion payload/include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* UI tell apart an old payload still running from a build that includes
* a particular fix, without having to boot the console. Keep in sync
* with the desktop app's package.json during releases. */
#define PS5UPLOAD2_VERSION "3.3.20"
#define PS5UPLOAD2_VERSION "3.3.21"
/* Author credit — embedded in the startup toast so anyone looking at
* the console screen knows who wrote the software that just loaded.
* Kept separate from VERSION so release scripts can bump the version
Expand Down
74 changes: 64 additions & 10 deletions payload/src/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -7008,9 +7008,14 @@ static int handle_fs_delete(runtime_state_t *state, int client_fd,
}

/* ── FS_MOVE handler ────────────────────────────────────────────────────
* rename(2) only works intra-volume on POSIX. Cross-volume moves return
* EXDEV, which we surface as a specific error so the client can tell
* the user "move across mounts not supported". */
* rename(2) only works intra-volume on POSIX. Cross-volume moves are supposed
* to fail with EXDEV — but on this kernel a cross-DEVICE rename (e.g. a USB
* exFAT file → the internal SSD) does NOT return EXDEV: it KERNEL PANICS and
* crashes the console (a reproducible hard crash, reported on USB→internal cut
* & paste). So we must NEVER let rename() run across devices. We compare the
* source's device id against the destination directory's up front and surface
* `fs_move_cross_mount` instead — the client then completes the move safely as
* copy-then-delete (FS_COPY reads/writes bytes, which is cross-volume safe). */
static int handle_fs_move(runtime_state_t *state, int client_fd,
uint64_t trace_id, const char *request_body, uint64_t body_len) {
char from[512], to[512];
Expand All @@ -7025,6 +7030,31 @@ static int handle_fs_move(runtime_state_t *state, int client_fd,
return send_frame(client_fd, FTX2_FRAME_ERROR, 0, trace_id,
"fs_move_path_not_allowed", 24);
}
/* Cross-device guard — see the header comment. Compare st_dev of the source
* and of the destination's parent directory; if they differ, refuse the
* rename (it would panic) and report cross-mount. stat() on USB is safe
* (FS_COPY stats the same paths). If either stat fails we fall through to
* rename(), but only when devices can't be compared — a missing source/dest
* there fails with a normal errno, not the cross-device panic. */
{
struct stat sf, sdp;
char to_dir[512];
const char *slash = strrchr(to, '/');
if (slash && slash != to) {
size_t dlen = (size_t)(slash - to);
if (dlen >= sizeof(to_dir)) dlen = sizeof(to_dir) - 1;
memcpy(to_dir, to, dlen);
to_dir[dlen] = '\0';
} else {
to_dir[0] = '/';
to_dir[1] = '\0';
}
if (stat(from, &sf) == 0 && stat(to_dir, &sdp) == 0 &&
sf.st_dev != sdp.st_dev) {
return send_frame(client_fd, FTX2_FRAME_ERROR, 0, trace_id,
"fs_move_cross_mount", 19);
}
}
if (rename(from, to) != 0) {
if (errno == EXDEV) {
return send_frame(client_fd, FTX2_FRAME_ERROR, 0, trace_id,
Expand Down Expand Up @@ -12237,13 +12267,37 @@ static int handle_shell_builtin(const char *cmd_in, char **out_text,
any_err = 1;
continue;
}
if (rename(argv[i], target) == 0) continue;
if (errno != EXDEV) {
len = shell_appendf(&out, &cap, len,
"mv: %s -> %s: %s\n",
argv[i], target, strerror(errno));
any_err = 1;
continue;
/* A cross-DEVICE rename() panics this kernel instead of returning
* EXDEV (see handle_fs_move). Only attempt the rename when source
* and dest are on the SAME device; otherwise skip straight to the
* copy-then-unlink path below. Never call rename() across mounts. */
int mv_same_dev = 0;
{
struct stat mv_sf, mv_dd;
char mv_dpar[1024];
const char *mv_ds = strrchr(target, '/');
if (mv_ds && mv_ds != target) {
size_t dl = (size_t)(mv_ds - target);
if (dl >= sizeof(mv_dpar)) dl = sizeof(mv_dpar) - 1;
memcpy(mv_dpar, target, dl);
mv_dpar[dl] = '\0';
} else {
mv_dpar[0] = '/';
mv_dpar[1] = '\0';
}
mv_same_dev = (stat(argv[i], &mv_sf) == 0 &&
stat(mv_dpar, &mv_dd) == 0 &&
mv_sf.st_dev == mv_dd.st_dev);
}
if (mv_same_dev) {
if (rename(argv[i], target) == 0) continue;
if (errno != EXDEV) {
len = shell_appendf(&out, &cap, len,
"mv: %s -> %s: %s\n",
argv[i], target, strerror(errno));
any_err = 1;
continue;
}
}
/* Cross-FS — copy then unlink. Single file only; cross-FS
* directory mv is too complex for shell tab (use cp -r +
Expand Down
Loading