mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
FTP: everything in the title folder is 0777, and the repair finishes
Users reported FTP permission refusals again. Since v0.6.7 the title usually hands itself over at once (to the picker or ES-DE, through LoadExec, which ends the process), so the start-up repair that opens /app0 to FTP was cut short; and the picker and ES-DE, other executables, wrote with their own modes. - Files are 0777 as folders are (were 0666): the link-time wrappers and the repair (src/permissions_ps5.cpp). - ps5_permissions_settle waits for the repair (at most a minute) before every handover: to a frontend, closing the title, the relaunch test. - ES-DE's library files and folders, the remembered frontend (src/ps5_frontend_choice.h) and the picker's test record are 0777; the picker opens RADV's shader cache to FTP after its first 30 frames and again when it hands over. Verified on the base PS5: the trace reads "permissions: /app0 checked=17992 repaired=174 refused=0" then "handover waited 596.2 ms"; after a home-screen launch held at the picker and closed there, an FTP walk of /data/homebrew/PPSA99169 finds 17992 entries, 0 not 0777 (before: 175 files at 0666) (evidence/permissions-0777). tools/verify.sh format, unit and evidence PASS.
This commit is contained in:
1 parent
cc5ded25ff
commit
4812ffdee0
11 files changed
+121
-8
No files matched your search
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"step": "permissions-0777",
|
||||
"recorded": "2026-10-07T04:00:46Z",
|
||||
"console_run": "base PS5 13.40, home-screen launches (picker), FTP audit of /data/homebrew/PPSA99169",
|
||||
"revision": "working tree on cc5ded2 (this commit)",
|
||||
"command": "tools/run-title.sh --no-build --home-launch --watch 30; FTP LIST walk of the title folder counting modes other than rwxrwxrwx",
|
||||
"raw_capture": "klog/permissions-0777/audit-20261007.txt",
|
||||
"raw_lines": 4,
|
||||
"raw_bytes": 257,
|
||||
"lines": [
|
||||
"before (console, 2026-10-07): 17993 entries, 175 not 0777: -rw-rw-rw- 175",
|
||||
"permissions: /app0 checked=17992 repaired=174 refused=0 in 589.8 ms",
|
||||
"permissions: handover waited 596.2 ms for the repair",
|
||||
"after picker-held launch and close: 17992 entries, 0 not 0777"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"step": "permissions-0777",
|
||||
"note": "Every file and folder the title, a core or a frontend makes is 0777; the start-up repair finishes before any handover (it was cut short by LoadExec into the picker or ES-DE); the picker opens RADV's shader cache to FTP after its first frames.",
|
||||
"must_contain": [
|
||||
"permissions: handover waited",
|
||||
"after picker-held launch and close: 17992 entries, 0 not 0777"
|
||||
],
|
||||
"must_not_contain": [],
|
||||
"human_check": "FTP audit by the agent over the whole title folder."
|
||||
}
|
||||
@@ -172,7 +172,7 @@ bool save(pugi::xml_document &document, const std::string &path)
|
||||
const std::string temporary = path + ".tmp";
|
||||
if (!document.save_file(temporary.c_str(), "\t", pugi::format_default, pugi::encoding_utf8))
|
||||
return false;
|
||||
chmod(temporary.c_str(), 0666);
|
||||
chmod(temporary.c_str(), 0777); /* FTP's reach: the title's files are 0777 */
|
||||
if (std::rename(temporary.c_str(), path.c_str()) != 0)
|
||||
{
|
||||
std::remove(temporary.c_str());
|
||||
@@ -195,8 +195,12 @@ extern "C" int ps5_esde_write_library_to(const struct ps5_esde_library_paths *pa
|
||||
}
|
||||
const std::map<std::string, Reference> reference = read_reference(paths->reference);
|
||||
const std::string data = paths->data;
|
||||
mkdir((data + "/custom_systems").c_str(), 0777);
|
||||
mkdir((data + "/gamelists").c_str(), 0777);
|
||||
/* Folders 0777 whatever the umask, for FTP's reach (src/permissions_ps5.cpp). */
|
||||
for (const char *folder : {"/custom_systems", "/gamelists"})
|
||||
{
|
||||
mkdir((data + folder).c_str(), 0777);
|
||||
chmod((data + folder).c_str(), 0777);
|
||||
}
|
||||
enable_collections(data);
|
||||
/* RetroArch's favourites as they were at the last start. */
|
||||
const std::string favorites_record = data + "/ps5-favorites.txt";
|
||||
@@ -243,6 +247,7 @@ extern "C" int ps5_esde_write_library_to(const struct ps5_esde_library_paths *pa
|
||||
const std::string folder = data + "/gamelists/" + name;
|
||||
const std::string gamelist = folder + "/gamelist.xml";
|
||||
mkdir(folder.c_str(), 0777);
|
||||
chmod(folder.c_str(), 0777);
|
||||
pugi::xml_document previous, games;
|
||||
previous.load_file(gamelist.c_str());
|
||||
std::map<std::string, pugi::xml_node> known_games;
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
#include "kit.hpp"
|
||||
#include "ps5_frontend_choice.h"
|
||||
#include <ps5platform/libc.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
extern "C" int sceSystemServiceLoadExec(const char *path, const char *const *argv);
|
||||
extern "C" int sceKernelUsleep(uint32_t microseconds);
|
||||
@@ -109,8 +111,11 @@ std::vector<std::uint8_t> readPreview(const char *path, int &width, int &height)
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void openForFtp(const char *path, int depth); // below
|
||||
|
||||
class VulkanExample : public ps5ui::KitExample
|
||||
{
|
||||
unsigned framesSinceStart = 0; // for opening the shader cache to FTP
|
||||
public:
|
||||
int focus{ 0 };
|
||||
int chosen{ -2 }; // -2: still choosing, -1: close, else the frontend
|
||||
@@ -327,6 +332,11 @@ public:
|
||||
compose();
|
||||
buildKitCommandBuffer();
|
||||
submitFrame();
|
||||
// Once the first frames are drawn RADV has written its shader cache: open it
|
||||
// to FTP now, as the title may be closed from here without a choice.
|
||||
if (++framesSinceStart == 30) {
|
||||
openForFtp("/app0/radv-shader-cache", 6);
|
||||
}
|
||||
if (chosen != -2) {
|
||||
nextFrontend = chosen;
|
||||
rememberChoice = remember;
|
||||
@@ -342,6 +352,30 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// What the picker wrote below a folder is given 0777, as eboot.bin gives everything
|
||||
// under /app0 (src/permissions_ps5.cpp): this executable has none of the title's
|
||||
// wrappers, and RADV writes its shader cache 0666. Without it FTP met those files
|
||||
// as the picker left them until eboot.bin's next start.
|
||||
void openForFtp(const char *path, int depth)
|
||||
{
|
||||
chmod(path, 0777);
|
||||
DIR *directory = depth > 0 ? ps5_opendir(path) : nullptr;
|
||||
if (!directory) {
|
||||
return;
|
||||
}
|
||||
while (const struct dirent *entry = ps5_readdir(directory)) {
|
||||
if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char child[1024];
|
||||
const int length = std::snprintf(child, sizeof(child), "%s/%s", path, entry->d_name);
|
||||
if (length > 0 && static_cast<size_t>(length) < sizeof(child)) {
|
||||
openForFtp(child, depth - 1);
|
||||
}
|
||||
}
|
||||
ps5_closedir(directory);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Called by the title's main once the program has ended and released its device
|
||||
@@ -349,6 +383,7 @@ public:
|
||||
extern "C" void ps5_title_next(void)
|
||||
{
|
||||
const Frontend *next = nextFrontend >= 0 && nextFrontend < cardCount ? &frontends[nextFrontend] : nullptr;
|
||||
openForFtp("/app0/radv-shader-cache", 6); // the device is released: the cache is final
|
||||
// Starting a frontend records Remember: that frontend, or "ask"; closing leaves it
|
||||
if (next) {
|
||||
const int written = ps5_frontend_choice_write(PS5_FRONTEND_CHOICE_PATH, rememberChoice ? next->id : "ask");
|
||||
@@ -358,7 +393,7 @@ extern "C" void ps5_title_next(void)
|
||||
if (FILE *record = std::fopen(testRecord, "a")) {
|
||||
std::fprintf(record, "{%s,\"next\":\"%s\"}\n", testLine.c_str(), next ? next->next : "");
|
||||
std::fclose(record);
|
||||
chmod(testRecord, 0666);
|
||||
chmod(testRecord, 0777);
|
||||
}
|
||||
}
|
||||
if (!next) {
|
||||
|
||||
@@ -68,6 +68,8 @@ struct ps5_game game;
|
||||
bool game_running = false;
|
||||
std::time_t game_started = 0;
|
||||
|
||||
extern "C" void ps5_permissions_settle(); /* src/permissions_ps5.cpp */
|
||||
|
||||
bool exists(const std::string &path)
|
||||
{
|
||||
std::FILE *file = std::fopen(path.c_str(), "rb");
|
||||
@@ -82,6 +84,7 @@ void restart_as(const std::string &image, const char *argument, unsigned replace
|
||||
const char *failure)
|
||||
{
|
||||
const char *const arguments[] = {argument, nullptr};
|
||||
ps5_permissions_settle();
|
||||
std::fflush(nullptr);
|
||||
const int result = sceSystemServiceLoadExec(image.c_str(), arguments);
|
||||
if (result >= 0)
|
||||
@@ -99,6 +102,7 @@ void restart_as(const std::string &image, unsigned replaced_wait_seconds, const
|
||||
* that did not happen. */
|
||||
void close_title(unsigned replaced_wait_seconds)
|
||||
{
|
||||
ps5_permissions_settle();
|
||||
std::fflush(nullptr);
|
||||
const int result = sceSystemServiceLoadExec("exit", nullptr);
|
||||
if (result >= 0)
|
||||
|
||||
@@ -68,6 +68,7 @@ extern "C" void ps5_crash_report_install();
|
||||
extern "C" void ps5_core_threads_start();
|
||||
extern "C" void ps5_sampler_start();
|
||||
extern "C" void ps5_open_permissions();
|
||||
extern "C" void ps5_permissions_settle();
|
||||
/* ../PS5_Vulkan's driver/ps5vk_debug.h: whether VideoOut outlives a swapchain.
|
||||
* Weak, so a build without the driver links. */
|
||||
extern "C" void ps5vk_display_retain(bool retain) __attribute__((weak));
|
||||
@@ -636,6 +637,7 @@ extern "C" void catchReturnFromMain(int status)
|
||||
/* A game mode game goes back to its frontend, and RetroArch the picker started to
|
||||
* the picker (src/frontend_mode_ps5.cpp). */
|
||||
ps5_frontend_after_retroarch(status, ps5_update::exit_requested() ? 1 : 0);
|
||||
ps5_permissions_settle();
|
||||
std::fflush(nullptr);
|
||||
const int result = sceSystemServiceLoadExec("exit", nullptr);
|
||||
ps5::debug::mark_value("native quit: system service result", result);
|
||||
|
||||
+34
-3
@@ -14,14 +14,22 @@
|
||||
* (2026-09-25). src/frontend_ps5.cpp already gave the seven top-level folders
|
||||
* 0777; everything below them kept RetroArch's modes.
|
||||
*
|
||||
* So every folder the title or a core creates is 0777 and every file at least
|
||||
* 0666: mkdir, open and fopen are wrapped at link time (tools/build-title.sh; a
|
||||
* So every folder and every file the title or a core creates is 0777 (files were
|
||||
* 0666 until users of v0.6.7 met FTP refusals again, 2026-10-07): mkdir, open
|
||||
* and fopen are wrapped at link time (tools/build-title.sh; a
|
||||
* core's own imports of them bind to the same wrappers) and give what they
|
||||
* create those bits after creating it, which the umask cannot take away. The
|
||||
* console's libraries do not resolve umask itself: a first build that cleared
|
||||
* it jumped to address 0 at start. What an earlier build made is repaired at
|
||||
* start: every folder and file under /app0 that lacks those bits is given
|
||||
* them. Only permission bits change; no file's contents are touched.
|
||||
*
|
||||
* The repair runs beside the frontend's start, and since v0.6.7 the title usually
|
||||
* hands itself over at once - to the picker or EmulationStation, through
|
||||
* LoadExec, which ends this process - so the walk was cut short and what
|
||||
* EmulationStation and the picker wrote (they are other executables, without
|
||||
* these wrappers) stayed out of FTP's reach. ps5_permissions_settle waits for the
|
||||
* walk to finish; every handover calls it first.
|
||||
*/
|
||||
|
||||
#include <cerrno>
|
||||
@@ -31,7 +39,9 @@
|
||||
#include <ctime>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <atomic>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <ps5platform/libc.h>
|
||||
@@ -45,7 +55,7 @@ extern "C" std::FILE *__real_fopen(const char *path, const char *mode);
|
||||
namespace
|
||||
{
|
||||
constexpr mode_t kFolderMode = 0777;
|
||||
constexpr mode_t kFileMode = 0666;
|
||||
constexpr mode_t kFileMode = 0777;
|
||||
/* Deeper than any folder the title or a core makes (Dolphin's user folder is
|
||||
* the deepest, about six below /app0). */
|
||||
constexpr int kRepairDepth = 16;
|
||||
@@ -158,6 +168,8 @@ namespace
|
||||
* console, 389 ms -- so it runs beside the frontend's start rather than before
|
||||
* it. Which of them the title made cannot be told from inside it: /app0's
|
||||
* stat reports every entry as uid 0, the FTP account's own as well. */
|
||||
std::atomic<int> repair_state{0}; /* 0 not started, 1 walking, 2 done */
|
||||
|
||||
void *repair_thread(void *)
|
||||
{
|
||||
const double start = now_ms();
|
||||
@@ -168,6 +180,7 @@ void *repair_thread(void *)
|
||||
"permissions: /app0 checked=%u repaired=%u refused=%u in %.1f ms", counts.checked,
|
||||
counts.repaired, counts.refused, now_ms() - start);
|
||||
ps5::debug::mark(line);
|
||||
repair_state.store(2);
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
@@ -176,8 +189,26 @@ void *repair_thread(void *)
|
||||
extern "C" void ps5_open_permissions()
|
||||
{
|
||||
pthread_t thread;
|
||||
repair_state.store(1);
|
||||
if (create_title_thread(&thread, repair_thread, nullptr) == 0)
|
||||
pthread_detach(thread);
|
||||
else
|
||||
{
|
||||
repair_state.store(0);
|
||||
ps5::debug::mark("permissions: the repair thread could not start");
|
||||
}
|
||||
}
|
||||
|
||||
/* Waits, at most a minute, for the repair to finish; before any LoadExec. */
|
||||
extern "C" void ps5_permissions_settle()
|
||||
{
|
||||
if (repair_state.load() != 1)
|
||||
return;
|
||||
const double start = now_ms();
|
||||
while (repair_state.load() == 1 && now_ms() - start < 60000.0)
|
||||
usleep(10000);
|
||||
char line[96];
|
||||
std::snprintf(line, sizeof(line), "permissions: handover waited %.1f ms for the repair%s",
|
||||
now_ms() - start, repair_state.load() == 2 ? "" : " (gave up)");
|
||||
ps5::debug::mark(line);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ static inline int ps5_frontend_choice_write(const char *path, const char *choice
|
||||
return -1;
|
||||
}
|
||||
/* FTP runs as another user: the file stays reachable, as the title's others are. */
|
||||
chmod(temporary, 0666);
|
||||
chmod(temporary, 0777); /* FTP's reach: the title's files are 0777 */
|
||||
if (rename(temporary, path) != 0)
|
||||
{
|
||||
remove(temporary);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
* error, or that is accepted but leaves this process running, is recorded too, and
|
||||
* the test is disarmed so the launch still ends in RetroArch.
|
||||
*/
|
||||
extern "C" void ps5_permissions_settle(); /* src/permissions_ps5.cpp */
|
||||
#include "relaunch_ps5.h"
|
||||
|
||||
#include <cerrno>
|
||||
@@ -242,6 +243,7 @@ bool run_test(const Paths &paths, int argc, char **argv, unsigned replaced_wait_
|
||||
const char *const arguments[] = {next.c_str(), nullptr};
|
||||
std::fflush(nullptr);
|
||||
const std::string &image = arm.image.empty() ? paths.image : arm.image;
|
||||
ps5_permissions_settle();
|
||||
const int result = sceSystemServiceLoadExec(image.c_str(), arguments);
|
||||
std::string event = line_start(arm.run, current) +
|
||||
",\"event\":\"loadexec\",\"result\":" + std::to_string(result);
|
||||
|
||||
@@ -86,6 +86,10 @@ std::string launch(const ps5::frontend_mode::Paths &paths, std::vector<const cha
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/* src/permissions_ps5.cpp: the repair is the title's; here it has nothing to wait for. */
|
||||
extern "C" void ps5_permissions_settle()
|
||||
{
|
||||
}
|
||||
extern "C" int sceSystemServiceLoadExec(const char *path, const char *const *argv)
|
||||
{
|
||||
exec_calls++;
|
||||
|
||||
@@ -70,6 +70,10 @@ bool launch(const ps5::relaunch::Paths &paths, std::vector<const char *> argv, b
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/* src/permissions_ps5.cpp: the repair is the title's; here it has nothing to wait for. */
|
||||
extern "C" void ps5_permissions_settle()
|
||||
{
|
||||
}
|
||||
extern "C" int sceSystemServiceLoadExec(const char *path, const char *const *argv)
|
||||
{
|
||||
exec_calls++;
|
||||
|
||||
Reference in new issue
Block a user