fs: fix CreateDirectoryRecursivelyWithPath() for root files. save: fix restore detection. devoptab: return proper errno codes.

This commit is contained in:
ITotalJustice
2025-09-07 14:40:45 +01:00
parent 6e1eabbe0f
commit 43969a773e
7 changed files with 327 additions and 130 deletions

View File

@@ -235,7 +235,7 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
// strip file name form path. // strip file name form path.
const auto last_slash = std::strrchr(_path, '/'); const auto last_slash = std::strrchr(_path, '/');
if (!last_slash) { if (!last_slash || last_slash == _path.s) {
R_SUCCEED(); R_SUCCEED();
} }

View File

@@ -223,7 +223,6 @@ long minizip_seek_file_func_stdio(voidpf opaque, voidpf stream, ZPOS64_T offset,
uLong minizip_read_file_func_stdio(voidpf opaque, voidpf stream, void* buf, uLong size) { uLong minizip_read_file_func_stdio(voidpf opaque, voidpf stream, void* buf, uLong size) {
auto file = static_cast<std::FILE*>(stream); auto file = static_cast<std::FILE*>(stream);
log_write("[ZIP] doing read\n");
return std::fread(buf, 1, size, file); return std::fread(buf, 1, size, file);
} }

View File

@@ -777,9 +777,14 @@ Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs
continue; continue;
} }
const auto path_len = std::strlen(path);
if (!path_len) {
continue;
}
pbox->NewTransfer(name); pbox->NewTransfer(name);
if (path[std::strlen(path) -1] == '/') { if (path[path_len -1] == '/') {
Result rc; Result rc;
if (R_FAILED(rc = fs->CreateDirectoryRecursively(path)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs->CreateDirectoryRecursively(path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", path.s, rc); log_write("failed to create folder: %s 0x%04X\n", path.s, rc);

View File

@@ -730,17 +730,23 @@ void Menu::BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries) {
void Menu::RestoreSave() { void Menu::RestoreSave() {
dump::DumpGetLocation("Select restore location"_i18n, dump::DumpLocationFlag_SdCard|dump::DumpLocationFlag_Stdio, [this](const dump::DumpLocation& location){ dump::DumpGetLocation("Select restore location"_i18n, dump::DumpLocationFlag_SdCard|dump::DumpLocationFlag_Stdio, [this](const dump::DumpLocation& location){
std::unique_ptr<fs::Fs> fs; std::unique_ptr<fs::Fs> fs{};
fs::FsPath mount{};
if (location.entry.type == dump::DumpLocationType_Stdio) { if (location.entry.type == dump::DumpLocationType_Stdio) {
mount = fs::AppendPath(location.stdio[location.entry.index].mount, location.stdio[location.entry.index].dump_path);
fs = std::make_unique<fs::FsStdio>(true, location.stdio[location.entry.index].mount); fs = std::make_unique<fs::FsStdio>(true, location.stdio[location.entry.index].mount);
} else if (location.entry.type == dump::DumpLocationType_SdCard) { } else if (location.entry.type == dump::DumpLocationType_SdCard) {
fs = std::make_unique<fs::FsNativeSd>(); fs = std::make_unique<fs::FsNativeSd>();
} else {
App::PushErrorBox(MAKERESULT(Module_Libnx, LibnxError_BadInput), "Invalid location type!"_i18n);
return;
} }
// get saves in /Saves/Name and /Saves/app_id // get saves in /Saves/Name and /Saves/app_id
filebrowser::FsDirCollection collections[2]{}; filebrowser::FsDirCollection collections[2]{};
for (auto i = 0; i < std::size(collections); i++) { for (auto i = 0; i < std::size(collections); i++) {
const auto save_path = fs::AppendPath(fs->Root(), BuildSaveBasePath(m_entries[m_index], i != 0)); const auto save_path = fs::AppendPath(mount, BuildSaveBasePath(m_entries[m_index], i != 0));
filebrowser::FsView::get_collection(fs.get(), save_path, "", collections[i], true, false, false); filebrowser::FsView::get_collection(fs.get(), save_path, "", collections[i], true, false, false);
// reverse as they will be sorted in oldest -> newest. // reverse as they will be sorted in oldest -> newest.
// todo: better impl when both id and normal app folders are used. // todo: better impl when both id and normal app folders are used.
@@ -763,7 +769,7 @@ void Menu::RestoreSave() {
if (paths.empty()) { if (paths.empty()) {
App::Push<ui::OptionBox>( App::Push<ui::OptionBox>(
"No saves found in "_i18n + fs::AppendPath(fs->Root(), BuildSaveBasePath(m_entries[m_index])).toString(), "No saves found in "_i18n + fs::AppendPath(mount, BuildSaveBasePath(m_entries[m_index])).toString(),
"OK"_i18n "OK"_i18n
); );
return; return;

View File

@@ -58,13 +58,13 @@ private:
static bool ftp_parse_mlist(std::string_view chunk, struct stat* st); static bool ftp_parse_mlist(std::string_view chunk, struct stat* st);
std::pair<bool, long> ftp_quote(std::span<const std::string> commands, bool is_dir, std::vector<char>* response_data = nullptr); std::pair<bool, long> ftp_quote(std::span<const std::string> commands, bool is_dir, std::vector<char>* response_data = nullptr);
bool ftp_dirlist(const std::string& path, DirEntries& out); int ftp_dirlist(const std::string& path, DirEntries& out);
bool ftp_stat(const std::string& path, struct stat* st, bool is_dir); int ftp_stat(const std::string& path, struct stat* st, bool is_dir);
bool ftp_remove_file_folder(const std::string& path, bool is_dir); int ftp_remove_file_folder(const std::string& path, bool is_dir);
bool ftp_unlink(const std::string& path); int ftp_unlink(const std::string& path);
bool ftp_rename(const std::string& old_path, const std::string& new_path, bool is_dir); int ftp_rename(const std::string& old_path, const std::string& new_path, bool is_dir);
bool ftp_mkdir(const std::string& path); int ftp_mkdir(const std::string& path);
bool ftp_rmdir(const std::string& path); int ftp_rmdir(const std::string& path);
private: private:
bool mounted{}; bool mounted{};
@@ -267,7 +267,7 @@ std::pair<bool, long> Device::ftp_quote(std::span<const std::string> commands, b
return {true, response_code}; return {true, response_code};
} }
bool Device::ftp_dirlist(const std::string& path, DirEntries& out) { int Device::ftp_dirlist(const std::string& path, DirEntries& out) {
const auto url = build_url(path, true); const auto url = build_url(path, true);
std::vector<char> chunk; std::vector<char> chunk;
@@ -279,51 +279,82 @@ bool Device::ftp_dirlist(const std::string& path, DirEntries& out) {
const auto res = curl_easy_perform(this->curl); const auto res = curl_easy_perform(this->curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
log_write("[FTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); log_write("[FTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
return false; return -EIO;
}
long response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
switch (response_code) {
case 125: // Data connection already open; transfer starting.
case 150: // File status okay; about to open data connection.
case 226: // Closing data connection. Requested file action successful.
break;
case 450: // Requested file action not taken. File unavailable (e.g., file busy).
case 550: // Requested action not taken. File unavailable (e.g., file not found, no access).
return -ENOENT;
default:
return -EIO;
} }
ftp_parse_mlsd({chunk.data(), chunk.size()}, out); ftp_parse_mlsd({chunk.data(), chunk.size()}, out);
return true; return 0;
} }
bool Device::ftp_stat(const std::string& path, struct stat* st, bool is_dir) { int Device::ftp_stat(const std::string& path, struct stat* st, bool is_dir) {
std::memset(st, 0, sizeof(*st)); std::memset(st, 0, sizeof(*st));
std::vector<char> chunk; std::vector<char> chunk;
const auto [success, response_code] = ftp_quote({"MLST " + path}, is_dir, &chunk); const auto [success, response_code] = ftp_quote({"MLST " + path}, is_dir, &chunk);
if (!success) { if (!success) {
return false; return -EIO;
} }
if (!success || response_code >= 400) { switch (response_code) {
log_write("[FTP] MLST command failed with response code: %ld\n", response_code); case 250: // Requested file action okay, completed.
return false; break;
case 450: // Requested file action not taken. File unavailable (e.g., file busy).
case 550: // Requested action not taken. File unavailable (e.g., file not found, no access).
return -ENOENT;
default:
return -EIO;
} }
if (!ftp_parse_mlist({chunk.data(), chunk.size()}, st)) { if (!ftp_parse_mlist({chunk.data(), chunk.size()}, st)) {
log_write("[FTP] Failed to parse MLST response for path: %s\n", path.c_str()); log_write("[FTP] Failed to parse MLST response for path: %s\n", path.c_str());
return false; return -EIO;
} }
return true; return 0;
} }
bool Device::ftp_remove_file_folder(const std::string& path, bool is_dir) { int Device::ftp_remove_file_folder(const std::string& path, bool is_dir) {
const auto cmd = (is_dir ? "RMD " : "DELE ") + path; const auto cmd = (is_dir ? "RMD " : "DELE ") + path;
const auto [success, response_code] = ftp_quote({cmd}, is_dir); const auto [success, response_code] = ftp_quote({cmd}, is_dir);
if (!success || response_code >= 400) {
log_write("[FTP] MLST command failed with response code: %ld\n", response_code); if (!success) {
return false; return -EIO;
} }
return true; switch (response_code) {
case 250: // Requested file action okay, completed.
case 200: // Command okay.
break;
case 450: // Requested file action not taken. File unavailable (e.g., file busy).
case 550: // Requested action not taken. File unavailable (e.g., file not found, no access).
return -ENOENT;
default:
return -EIO;
}
return 0;
} }
bool Device::ftp_unlink(const std::string& path) { int Device::ftp_unlink(const std::string& path) {
return ftp_remove_file_folder(path, false); return ftp_remove_file_folder(path, false);
} }
bool Device::ftp_rename(const std::string& old_path, const std::string& new_path, bool is_dir) { int Device::ftp_rename(const std::string& old_path, const std::string& new_path, bool is_dir) {
const auto url = build_url("/", is_dir); const auto url = build_url("/", is_dir);
std::vector<std::string> commands; std::vector<std::string> commands;
@@ -331,31 +362,50 @@ bool Device::ftp_rename(const std::string& old_path, const std::string& new_path
commands.emplace_back("RNTO " + new_path); commands.emplace_back("RNTO " + new_path);
const auto [success, response_code] = ftp_quote(commands, is_dir); const auto [success, response_code] = ftp_quote(commands, is_dir);
if (!success || response_code >= 400) { if (!success) {
log_write("[FTP] MLST command failed with response code: %ld\n", response_code); return -EIO;
return false;
} }
return true; switch (response_code) {
case 250: // Requested file action okay, completed.
case 200: // Command okay.
break;
case 450: // Requested file action not taken. File unavailable (e.g., file busy).
case 550: // Requested action not taken. File unavailable (e.g., file not found, no access).
return -ENOENT;
case 553: // Requested action not taken. File name not allowed.
return -EEXIST;
default:
return -EIO;
}
return 0;
} }
bool Device::ftp_mkdir(const std::string& path) { int Device::ftp_mkdir(const std::string& path) {
std::vector<char> chunk; std::vector<char> chunk;
const auto [success, response_code] = ftp_quote({"MKD " + path}, true); const auto [success, response_code] = ftp_quote({"MKD " + path}, true);
if (!success) { if (!success) {
return false; return -EIO;
} }
// todo: handle result if directory already exists. switch (response_code) {
if (response_code >= 400) { case 257: // "PATHNAME" created.
log_write("[FTP] MLST command failed with response code: %ld\n", response_code); case 250: // Requested file action okay, completed.
return false; case 200: // Command okay.
break;
case 550: // Requested action not taken. File unavailable (e.g., file not found, no access).
return -ENOENT; // Parent directory does not exist or no permission.
case 521: // Directory already exists.
return -EEXIST;
default:
return -EIO;
} }
return true; return 0;
} }
bool Device::ftp_rmdir(const std::string& path) { int Device::ftp_rmdir(const std::string& path) {
return ftp_remove_file_folder(path, true); return ftp_remove_file_folder(path, true);
} }
@@ -401,9 +451,9 @@ int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mod
if ((flags & O_ACCMODE) == O_RDONLY || (flags & O_APPEND)) { if ((flags & O_ACCMODE) == O_RDONLY || (flags & O_APPEND)) {
// ensure the file exists and get its size. // ensure the file exists and get its size.
if (!ftp_stat(path, &st, false)) { const auto ret = ftp_stat(path, &st, false);
log_write("[FTP] File not found: %s\n", path); if (ret < 0) {
return -ENOENT; return ret;
} }
if (st.st_mode & S_IFDIR) { if (st.st_mode & S_IFDIR) {
@@ -522,32 +572,44 @@ int Device::devoptab_fstat(void *fd, struct stat *st) {
} }
int Device::devoptab_unlink(const char *path) { int Device::devoptab_unlink(const char *path) {
if (!ftp_unlink(path)) { const auto ret = ftp_unlink(path);
return -EIO; if (ret < 0) {
log_write("[FTP] ftp_unlink() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_rename(const char *oldName, const char *newName) { int Device::devoptab_rename(const char *oldName, const char *newName) {
if (!ftp_rename(oldName, newName, false) && !ftp_rename(oldName, newName, true)) { auto ret = ftp_rename(oldName, newName, false);
return -EIO; if (ret == -ENOENT) {
ret = ftp_rename(oldName, newName, true);
}
if (ret < 0) {
log_write("[FTP] ftp_rename() failed: %s -> %s errno: %s\n", oldName, newName, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_mkdir(const char *path, int mode) { int Device::devoptab_mkdir(const char *path, int mode) {
if (!ftp_mkdir(path)) { const auto ret = ftp_mkdir(path);
return -EIO; if (ret < 0) {
log_write("[FTP] ftp_mkdir() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_rmdir(const char *path) { int Device::devoptab_rmdir(const char *path) {
if (!ftp_rmdir(path)) { const auto ret = ftp_rmdir(path);
return -EIO; if (ret < 0) {
log_write("[FTP] ftp_rmdir() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
@@ -557,9 +619,11 @@ int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd); auto dir = static_cast<Dir*>(fd);
auto entries = new DirEntries(); auto entries = new DirEntries();
if (!ftp_dirlist(path, *entries)) { const auto ret = ftp_dirlist(path, *entries);
if (ret < 0) {
log_write("[FTP] ftp_dirlist() failed: %s errno: %s\n", path, std::strerror(-ret));
delete entries; delete entries;
return -ENOENT; return ret;
} }
dir->entries = entries; dir->entries = entries;
@@ -602,8 +666,14 @@ int Device::devoptab_dirclose(void* fd) {
} }
int Device::devoptab_lstat(const char *path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
if (!ftp_stat(path, st, false) && !ftp_stat(path, st, true)) { auto ret = ftp_stat(path, st, false);
return -ENOENT; if (ret == -ENOENT) {
ret = ftp_stat(path, st, true);
}
if (ret < 0) {
log_write("[FTP] ftp_stat() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
@@ -614,7 +684,7 @@ int Device::devoptab_ftruncate(void *fd, off_t len) {
if (!file->write_mode) { if (!file->write_mode) {
log_write("[FTP] Attempt to truncate a read-only file\n"); log_write("[FTP] Attempt to truncate a read-only file\n");
return EBADF; return -EBADF;
} }
file->entry->st.st_size = len; file->entry->st.st_size = len;

View File

@@ -59,14 +59,14 @@ private:
int devoptab_dirclose(void* fd) override; int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override; int devoptab_lstat(const char *path, struct stat *st) override;
bool http_dirlist(const std::string& path, DirEntries& out); int http_dirlist(const std::string& path, DirEntries& out);
bool http_stat(const std::string& path, struct stat* st, bool is_dir); int http_stat(const std::string& path, struct stat* st, bool is_dir);
private: private:
bool mounted{}; bool mounted{};
}; };
bool Device::http_dirlist(const std::string& path, DirEntries& out) { int Device::http_dirlist(const std::string& path, DirEntries& out) {
const auto url = build_url(path, true); const auto url = build_url(path, true);
std::vector<char> chunk; std::vector<char> chunk;
@@ -79,7 +79,29 @@ bool Device::http_dirlist(const std::string& path, DirEntries& out) {
const auto res = curl_easy_perform(this->curl); const auto res = curl_easy_perform(this->curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
return false; return -EIO;
}
long response_code = 0;
curl_easy_getinfo(this->curl, CURLINFO_RESPONSE_CODE, &response_code);
switch (response_code) {
case 200: // OK
case 206: // Partial Content
break;
case 301: // Moved Permanently
case 302: // Found
case 303: // See Other
case 307: // Temporary Redirect
case 308: // Permanent Redirect
return -EIO;
case 401: // Unauthorized
case 403: // Forbidden
return -EACCES;
case 404: // Not Found
return -ENOENT;
default:
return -EIO;
} }
log_write("[HTTP] Received %zu bytes for directory listing\n", chunk.size()); log_write("[HTTP] Received %zu bytes for directory listing\n", chunk.size());
@@ -147,10 +169,10 @@ bool Device::http_dirlist(const std::string& path, DirEntries& out) {
log_write("[HTTP] Parsed %zu entries from directory listing\n", out.size()); log_write("[HTTP] Parsed %zu entries from directory listing\n", out.size());
return true; return 0;
} }
bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) { int Device::http_stat(const std::string& path, struct stat* st, bool is_dir) {
std::memset(st, 0, sizeof(*st)); std::memset(st, 0, sizeof(*st));
const auto url = build_url(path, is_dir); const auto url = build_url(path, is_dir);
@@ -161,7 +183,7 @@ bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) {
const auto res = curl_easy_perform(this->curl); const auto res = curl_easy_perform(this->curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
return false; return -EIO;
} }
long response_code = 0; long response_code = 0;
@@ -179,10 +201,23 @@ bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) {
const char* effective_url{}; const char* effective_url{};
curl_easy_getinfo(this->curl, CURLINFO_EFFECTIVE_URL, &effective_url); curl_easy_getinfo(this->curl, CURLINFO_EFFECTIVE_URL, &effective_url);
// handle error codes. switch (response_code) {
if (response_code != 200 && response_code != 206) { case 200: // OK
log_write("[HTTP] Unexpected HTTP response code: %ld\n", response_code); case 206: // Partial Content
return false; break;
case 301: // Moved Permanently
case 302: // Found
case 303: // See Other
case 307: // Temporary Redirect
case 308: // Permanent Redirect
return -EIO;
case 401: // Unauthorized
case 403: // Forbidden
return -EACCES;
case 404: // Not Found
return -ENOENT;
default:
return -EIO;
} }
if (effective_url) { if (effective_url) {
@@ -207,7 +242,7 @@ bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) {
st->st_ctime = st->st_mtime; st->st_ctime = st->st_mtime;
st->st_nlink = 1; st->st_nlink = 1;
return true; return 0;
} }
bool Device::Mount() { bool Device::Mount() {
@@ -229,9 +264,10 @@ int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mod
auto file = static_cast<File*>(fileStruct); auto file = static_cast<File*>(fileStruct);
struct stat st; struct stat st;
if (!http_stat(path, &st, false)) { const auto ret = http_stat(path, &st, false);
log_write("[HTTP] http_stat() failed for file: %s\n", path); if (ret < 0) {
return -ENOENT; log_write("[HTTP] http_stat() failed for file: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
if (st.st_mode & S_IFDIR) { if (st.st_mode & S_IFDIR) {
@@ -306,9 +342,11 @@ int Device::devoptab_diropen(void* fd, const char *path) {
log_write("[HTTP] Opening directory: %s\n", path); log_write("[HTTP] Opening directory: %s\n", path);
auto entries = new DirEntries(); auto entries = new DirEntries();
if (!http_dirlist(path, *entries)) { const auto ret = http_dirlist(path, *entries);
if (ret < 0) {
log_write("[HTTP] http_dirlist() failed for directory: %s errno: %s\n", path, std::strerror(-ret));
delete entries; delete entries;
return -ENOENT; return ret;
} }
log_write("[HTTP] Opened directory: %s with %zu entries\n", path, entries->size()); log_write("[HTTP] Opened directory: %s with %zu entries\n", path, entries->size());
@@ -352,8 +390,14 @@ int Device::devoptab_dirclose(void* fd) {
} }
int Device::devoptab_lstat(const char *path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
if (!http_stat(path, st, false) && !http_stat(path, st, true)) { auto ret = http_stat(path, st, false);
return -ENOENT; if (ret < 0) {
ret = http_stat(path, st, true);
}
if (ret < 0) {
log_write("[HTTP] http_stat() failed for path: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;

View File

@@ -73,13 +73,13 @@ private:
int devoptab_fsync(void *fd) override; int devoptab_fsync(void *fd) override;
std::pair<bool, long> webdav_custom_command(const std::string& path, const std::string& cmd, std::string_view postfields, std::span<const std::string> headers, bool is_dir, std::vector<char>* response_data = nullptr); std::pair<bool, long> webdav_custom_command(const std::string& path, const std::string& cmd, std::string_view postfields, std::span<const std::string> headers, bool is_dir, std::vector<char>* response_data = nullptr);
bool webdav_dirlist(const std::string& path, DirEntries& out); int webdav_dirlist(const std::string& path, DirEntries& out);
bool webdav_stat(const std::string& path, struct stat* st, bool is_dir); int webdav_stat(const std::string& path, struct stat* st, bool is_dir);
bool webdav_remove_file_folder(const std::string& path, bool is_dir); int webdav_remove_file_folder(const std::string& path, bool is_dir);
bool webdav_unlink(const std::string& path); int webdav_unlink(const std::string& path);
bool webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir); int webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir);
bool webdav_mkdir(const std::string& path); int webdav_mkdir(const std::string& path);
bool webdav_rmdir(const std::string& path); int webdav_rmdir(const std::string& path);
}; };
size_t dummy_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata) { size_t dummy_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
@@ -126,7 +126,7 @@ std::pair<bool, long> Device::webdav_custom_command(const std::string& path, con
return {true, response_code}; return {true, response_code};
} }
bool Device::webdav_dirlist(const std::string& path, DirEntries& out) { int Device::webdav_dirlist(const std::string& path, DirEntries& out) {
const std::string_view post_fields = const std::string_view post_fields =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<d:propfind xmlns:d=\"DAV:\">" "<d:propfind xmlns:d=\"DAV:\">"
@@ -143,9 +143,20 @@ bool Device::webdav_dirlist(const std::string& path, DirEntries& out) {
std::vector<char> chunk; std::vector<char> chunk;
const auto [success, response_code] = webdav_custom_command(path, "PROPFIND", post_fields, custom_headers, true, &chunk); const auto [success, response_code] = webdav_custom_command(path, "PROPFIND", post_fields, custom_headers, true, &chunk);
if (!success || response_code != 207) { if (!success) {
log_write("[WEBDAV] PROPFIND failed or returned HTTP error code: %ld\n", response_code); return -EIO;
return false; }
switch (response_code) {
case 207: // Multi-Status
break;
case 404: // Not Found
return -ENOENT;
case 403: // Forbidden
return -EACCES;
default:
log_write("[WEBDAV] Unexpected HTTP response code: %ld\n", response_code);
return -EIO;
} }
SCOPED_TIMESTAMP("webdav_dirlist parse"); SCOPED_TIMESTAMP("webdav_dirlist parse");
@@ -154,7 +165,7 @@ bool Device::webdav_dirlist(const std::string& path, DirEntries& out) {
const auto result = doc.load_buffer_inplace(chunk.data(), chunk.size()); const auto result = doc.load_buffer_inplace(chunk.data(), chunk.size());
if (!result) { if (!result) {
log_write("[WEBDAV] Failed to parse XML: %s\n", result.description()); log_write("[WEBDAV] Failed to parse XML: %s\n", result.description());
return false; return -EIO;
} }
log_write("\n[WEBDAV] XML parsed successfully\n"); log_write("\n[WEBDAV] XML parsed successfully\n");
@@ -219,11 +230,11 @@ bool Device::webdav_dirlist(const std::string& path, DirEntries& out) {
log_write("[WEBDAV] Parsed %zu entries from directory listing\n", out.size()); log_write("[WEBDAV] Parsed %zu entries from directory listing\n", out.size());
return true; return 0;
} }
// todo: use PROPFIND to get file size and time, although it is slower... // todo: use PROPFIND to get file size and time, although it is slower...
bool Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir) { int Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir) {
std::memset(st, 0, sizeof(*st)); std::memset(st, 0, sizeof(*st));
const auto url = build_url(path, is_dir); const auto url = build_url(path, is_dir);
@@ -234,7 +245,7 @@ bool Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir)
const auto res = curl_easy_perform(this->curl); const auto res = curl_easy_perform(this->curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
log_write("[WEBDAV] curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); log_write("[WEBDAV] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
return false; return -EIO;
} }
long response_code = 0; long response_code = 0;
@@ -252,10 +263,17 @@ bool Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir)
const char* effective_url{}; const char* effective_url{};
curl_easy_getinfo(this->curl, CURLINFO_EFFECTIVE_URL, &effective_url); curl_easy_getinfo(this->curl, CURLINFO_EFFECTIVE_URL, &effective_url);
// handle error codes. switch (response_code) {
if (response_code != 200 && response_code != 206) { case 200: // OK
log_write("[WEBDAV] Unexpected HTTP response code: %ld\n", response_code); case 206: // Partial Content
return false; break;
case 404: // Not Found
return -ENOENT;
case 403: // Forbidden
return -EACCES;
default:
log_write("[WEBDAV] Unexpected HTTP response code: %ld\n", response_code);
return -EIO;
} }
if (effective_url) { if (effective_url) {
@@ -280,24 +298,35 @@ bool Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir)
st->st_ctime = st->st_mtime; st->st_ctime = st->st_mtime;
st->st_nlink = 1; st->st_nlink = 1;
return true; return 0;
} }
bool Device::webdav_remove_file_folder(const std::string& path, bool is_dir) { int Device::webdav_remove_file_folder(const std::string& path, bool is_dir) {
const auto [success, response_code] = webdav_custom_command(path, "DELETE", "", {}, is_dir); const auto [success, response_code] = webdav_custom_command(path, "DELETE", "", {}, is_dir);
if (!success || (response_code != 200 && response_code != 204)) { if (!success) {
log_write("[WEBDAV] DELETE command failed with response code: %ld\n", response_code); return -EIO;
return false;
} }
return true; switch (response_code) {
case 200: // OK
case 204: // No Content
return 0;
case 404: // Not Found
return -ENOENT;
case 403: // Forbidden
return -EACCES;
case 409: // Conflict
return -ENOTEMPTY; // Directory not empty
default:
return -EIO;
}
} }
bool Device::webdav_unlink(const std::string& path) { int Device::webdav_unlink(const std::string& path) {
return webdav_remove_file_folder(path, false); return webdav_remove_file_folder(path, false);
} }
bool Device::webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir) { int Device::webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir) {
log_write("[WEBDAV] Renaming %s to %s\n", old_path.c_str(), new_path.c_str()); log_write("[WEBDAV] Renaming %s to %s\n", old_path.c_str(), new_path.c_str());
const std::string custom_headers[] = { const std::string custom_headers[] = {
@@ -306,25 +335,49 @@ bool Device::webdav_rename(const std::string& old_path, const std::string& new_p
}; };
const auto [success, response_code] = webdav_custom_command(old_path, "MOVE", "", custom_headers, is_dir); const auto [success, response_code] = webdav_custom_command(old_path, "MOVE", "", custom_headers, is_dir);
if (!success || (response_code != 200 && response_code != 201 && response_code != 204)) {
log_write("[WEBDAV] MOVE command failed with response code: %ld\n", response_code); if (!success) {
return false; return -EIO;
} }
return true; switch (response_code) {
case 201: // Created
case 204: // No Content
return 0;
case 404: // Not Found
return -ENOENT;
case 403: // Forbidden
return -EACCES;
case 412: // Precondition Failed
return -EEXIST; // Destination already exists and Overwrite is F
case 409: // Conflict
return -ENOENT; // Parent directory of destination does not exist
default:
return -EIO;
}
} }
bool Device::webdav_mkdir(const std::string& path) { int Device::webdav_mkdir(const std::string& path) {
const auto [success, response_code] = webdav_custom_command(path, "MKCOL", "", {}, true); const auto [success, response_code] = webdav_custom_command(path, "MKCOL", "", {}, true);
if (!success || response_code != 201) { if (!success) {
log_write("[WEBDAV] MKCOL command failed with response code: %ld\n", response_code); return -EIO;
return false;
} }
return true; switch (response_code) {
case 201: // Created
return 0;
case 405: // Method Not Allowed
return -EEXIST; // Collection already exists
case 409: // Conflict
return -ENOENT; // Parent collection does not exist
case 403: // Forbidden
return -EACCES;
default:
return -EIO;
}
} }
bool Device::webdav_rmdir(const std::string& path) { int Device::webdav_rmdir(const std::string& path) {
return webdav_remove_file_folder(path, true); return webdav_remove_file_folder(path, true);
} }
@@ -339,9 +392,9 @@ int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mod
if ((flags & O_ACCMODE) == O_RDONLY) { if ((flags & O_ACCMODE) == O_RDONLY) {
// ensure the file exists and get its size. // ensure the file exists and get its size.
if (!webdav_stat(path, &st, false)) { const auto ret = webdav_stat(path, &st, false);
log_write("[WEBDAV] File not found: %s\n", path); if (ret < 0) {
return -ENOENT; return ret;
} }
if (st.st_mode & S_IFDIR) { if (st.st_mode & S_IFDIR) {
@@ -456,32 +509,44 @@ int Device::devoptab_fstat(void *fd, struct stat *st) {
} }
int Device::devoptab_unlink(const char *path) { int Device::devoptab_unlink(const char *path) {
if (!webdav_unlink(path)) { const auto ret = webdav_unlink(path);
return -EIO; if (ret < 0) {
log_write("[WEBDAV] webdav_unlink() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_rename(const char *oldName, const char *newName) { int Device::devoptab_rename(const char *oldName, const char *newName) {
if (!webdav_rename(oldName, newName, false) && !webdav_rename(oldName, newName, true)) { auto ret = webdav_rename(oldName, newName, false);
return -EIO; if (ret == -ENOENT) {
ret = webdav_rename(oldName, newName, true);
}
if (ret < 0) {
log_write("[WEBDAV] webdav_rename() failed: %s to %s errno: %s\n", oldName, newName, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_mkdir(const char *path, int mode) { int Device::devoptab_mkdir(const char *path, int mode) {
if (!webdav_mkdir(path)) { const auto ret = webdav_mkdir(path);
return -EIO; if (ret < 0) {
log_write("[WEBDAV] webdav_mkdir() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
} }
int Device::devoptab_rmdir(const char *path) { int Device::devoptab_rmdir(const char *path) {
if (!webdav_rmdir(path)) { const auto ret = webdav_rmdir(path);
return -EIO; if (ret < 0) {
log_write("[WEBDAV] webdav_rmdir() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
@@ -491,9 +556,11 @@ int Device::devoptab_diropen(void* fd, const char *path) {
auto dir = static_cast<Dir*>(fd); auto dir = static_cast<Dir*>(fd);
auto entries = new DirEntries(); auto entries = new DirEntries();
if (!webdav_dirlist(path, *entries)) { const auto ret = webdav_dirlist(path, *entries);
if (ret < 0) {
log_write("[WEBDAV] webdav_dirlist() failed: %s errno: %s\n", path, std::strerror(-ret));
delete entries; delete entries;
return -ENOENT; return ret;
} }
dir->entries = entries; dir->entries = entries;
@@ -536,8 +603,14 @@ int Device::devoptab_dirclose(void* fd) {
} }
int Device::devoptab_lstat(const char *path, struct stat *st) { int Device::devoptab_lstat(const char *path, struct stat *st) {
if (!webdav_stat(path, st, false) && !webdav_stat(path, st, true)) { auto ret = webdav_stat(path, st, false);
return -ENOENT; if (ret == -ENOENT) {
ret = webdav_stat(path, st, true);
}
if (ret < 0) {
log_write("[WEBDAV] webdav_stat() failed: %s errno: %s\n", path, std::strerror(-ret));
return ret;
} }
return 0; return 0;
@@ -548,7 +621,7 @@ int Device::devoptab_ftruncate(void *fd, off_t len) {
if (!file->write_mode) { if (!file->write_mode) {
log_write("[WEBDAV] Attempt to truncate a read-only file\n"); log_write("[WEBDAV] Attempt to truncate a read-only file\n");
return EBADF; return -EBADF;
} }
file->entry->st.st_size = len; file->entry->st.st_size = len;