Merge pull request #3395 from LeandroLP23/fix/kernel-mkdir-enotdir

fix(libkernel): report ENOTDIR from mkdir and rmdir when a path component is a file
This commit is contained in:
Adrià 2026-10-11 11:55:48 +02:00 committed by GitHub
commit ec77a616b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 10 deletions

View File

@ -408,6 +408,19 @@ bool DescriptorIsOpen_nid_no_patch(int descriptor) {
}
#endif
static std::filesystem::path WithoutTrailingSeparator(const std::filesystem::path& native) {
return native.has_filename() ? native : native.parent_path();
}
static bool HasNonDirectoryPrefix(const std::filesystem::path& native) {
std::error_code error;
for (auto prefix = WithoutTrailingSeparator(native).parent_path(); prefix.has_relative_path(); prefix = prefix.parent_path()) {
const auto status = std::filesystem::status(prefix, error);
if (std::filesystem::exists(status)) return !std::filesystem::is_directory(status);
}
return false;
}
extern "C" {
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
@ -553,7 +566,9 @@ int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
if (const int error = PathError(path)) return PosixFailure(error);
auto native = ResolvePath_nid_no_patch(path);
if (NativeMkdir(native, mode) != 0) {
return PosixResult(SceErrorFromErrno(errno));
const int nativeError = errno;
const bool blockedByFile = (nativeError == ENOENT || nativeError == EINVAL) && HasNonDirectoryPrefix(native);
return PosixResult(SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : nativeError));
}
RecordWrittenPath_nid_no_patch(native);
return 0;
@ -784,14 +799,10 @@ int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) {
auto native = ResolvePath_nid_no_patch(path);
while (!native.has_filename() && native.has_relative_path()) native = native.parent_path();
std::error_code error;
const auto status = std::filesystem::status(native, error);
if (error && error != std::errc::no_such_file_or_directory) return SceErrorFromErrno(error.value());
if (std::filesystem::exists(status)) return SceErrorFromErrno(GUEST_EEXIST);
error.clear();
const auto parent = std::filesystem::status(native.parent_path(), error);
if (error) return SceErrorFromErrno(error.value());
if (!std::filesystem::exists(parent)) return SceErrorFromErrno(GUEST_ENOENT);
if (!std::filesystem::is_directory(parent)) return SceErrorFromErrno(GUEST_ENOTDIR);
if (std::filesystem::exists(native, error)) return SceErrorFromErrno(GUEST_EEXIST);
if (!std::filesystem::is_directory(WithoutTrailingSeparator(native).parent_path(), error)) {
return SceErrorFromErrno(HasNonDirectoryPrefix(native) ? GUEST_ENOTDIR : GUEST_ENOENT);
}
if (!std::filesystem::create_directory(native, error)) return SceErrorFromErrno(error.value() ? error.value() : GUEST_EIO);
RecordWrittenPath_nid_no_patch(native);
return 0;
@ -992,7 +1003,10 @@ int APS5_VABI sceKernelRmdir(const char* path) {
if (path == nullptr) throw std::invalid_argument("sceKernelRmdir: path is null");
const auto native = ResolvePath_nid_no_patch(path);
std::error_code error;
if (!std::filesystem::is_directory(native, error)) return SceErrorFromErrno(std::filesystem::exists(native, error) ? GUEST_ENOTDIR : GUEST_ENOENT);
if (!std::filesystem::is_directory(native, error)) {
const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error);
return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT);
}
if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY);
if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO);
RecordWrittenPath_nid_no_patch(native);

View File

@ -48,6 +48,7 @@ int APS5_VABI sceKernelStat(const char*, FileStat*);
int APS5_VABI sceKernelFstat(int, FileStat*);
int APS5_VABI sceKernelUnlink(const char*);
int APS5_VABI sceKernelRmdir(const char*);
int APS5_VABI sceKernelMkdir(const char*, unsigned short);
int* APS5_VABI __error_nid_postfix();
int APS5_VABI pipe_nid_postfix(int*);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::size_t);
@ -447,6 +448,32 @@ int main() {
Require(rmdir_nid_postfix(lockedDirectoryPosix.string().c_str()) == 0 && !std::filesystem::exists(lockedDirectoryPosix));
const auto lockedDirectoryKernel = lockedDirectory("locked-directory-kernel");
Require(sceKernelRmdir(lockedDirectoryKernel.string().c_str()) == 0 && !std::filesystem::exists(lockedDirectoryKernel));
const auto underFile = presentName + "/child";
const auto deepUnderFile = presentName + "/child/leaf";
Require(mkdir_nid_postfix(underFile.c_str(), 0700) == -1 && *__error_nid_postfix() == 20);
Require(mkdir_nid_postfix(deepUnderFile.c_str(), 0700) == -1 && *__error_nid_postfix() == 20);
Require(sceKernelMkdir(underFile.c_str(), 0700) == static_cast<int>(0x80020014u));
Require(sceKernelMkdir(deepUnderFile.c_str(), 0700) == static_cast<int>(0x80020014u));
Require(rmdir_nid_postfix(underFile.c_str()) == -1 && *__error_nid_postfix() == 20);
Require(sceKernelRmdir(deepUnderFile.c_str()) == static_cast<int>(0x80020014u));
Require(rmdir_nid_postfix((presentName + "/").c_str()) == -1 && *__error_nid_postfix() == 20);
Require(rmdir_nid_postfix((missingName + "/").c_str()) == -1 && *__error_nid_postfix() == 2);
Require(sceKernelRmdir((presentName + "/").c_str()) == static_cast<int>(0x80020014u));
Require(sceKernelRmdir((missingName + "/").c_str()) == static_cast<int>(0x80020002u));
const auto missingParentLeaf = rootName + "/missing/leaf";
Require(sceKernelMkdir(missingParentLeaf.c_str(), 0700) == static_cast<int>(0x80020002u));
const auto invalidNameUnderFile = presentName + "/a:b/x";
Require(mkdir_nid_postfix(invalidNameUnderFile.c_str(), 0700) == -1 && *__error_nid_postfix() == 20);
Require(sceKernelMkdir(invalidNameUnderFile.c_str(), 0700) == static_cast<int>(0x80020014u));
const auto trailingPosix = root / "trailing_posix";
const auto trailingKernel = root / "trailing_kernel";
const auto trailingPosixName = trailingPosix.string() + "/";
const auto trailingKernelName = trailingKernel.string() + "/";
Require(mkdir_nid_postfix(trailingPosixName.c_str(), 0700) == 0 && std::filesystem::is_directory(trailingPosix));
Require(sceKernelMkdir(trailingKernelName.c_str(), 0700) == 0 && std::filesystem::is_directory(trailingKernel));
Require(rmdir_nid_postfix(trailingPosix.string().c_str()) == 0 && sceKernelRmdir(trailingKernel.string().c_str()) == 0);
Require(!std::filesystem::exists(trailingPosix) && !std::filesystem::exists(trailingKernel));
Require(std::filesystem::is_regular_file(present));
Require(rmdir_nid_postfix("") == -1 && *__error_nid_postfix() == 2);
Require(rmdir_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 14);
Require(unlink_nid_postfix(missingName.c_str()) == -1 && *__error_nid_postfix() == 2);