Skip to content
Snippets Groups Projects
Commit 8047d7bd authored by Deucе's avatar Deucе :ok_hand_tone4:
Browse files

When responding to an sftp readdir command, send up to 25 files

This should speed up directory listings over higher latency links
by approximately 25×.

You're welcome nelgin.
parent c5c005df
No related branches found
No related tags found
No related merge requests found
Pipeline #6018 passed
#include <memory> #include <memory>
#include <vector>
#include "sbbs.h" #include "sbbs.h"
#include "xpprintf.h" // for asprintf() on Win32 #include "xpprintf.h" // for asprintf() on Win32
...@@ -9,6 +10,7 @@ constexpr uint32_t users_gid {UINT32_MAX}; ...@@ -9,6 +10,7 @@ constexpr uint32_t users_gid {UINT32_MAX};
#define SLASH_FILES "/files" #define SLASH_FILES "/files"
#define SLASH_HOME "/home" #define SLASH_HOME "/home"
#define MAX_FILES_PER_READDIR 25
constexpr int32_t no_more_files = -3; constexpr int32_t no_more_files = -3;
constexpr int32_t dot = -2; constexpr int32_t dot = -2;
...@@ -54,6 +56,8 @@ static sftp_file_attr_t sshkeys_attrs(sbbs_t *sbbs, const char *path); ...@@ -54,6 +56,8 @@ static sftp_file_attr_t sshkeys_attrs(sbbs_t *sbbs, const char *path);
static char *sftp_parse_crealpath(sbbs_t *sbbs, const char *filename); static char *sftp_parse_crealpath(sbbs_t *sbbs, const char *filename);
static char *expand_slash(const char *orig); static char *expand_slash(const char *orig);
static bool is_in_filebase(const char *path); static bool is_in_filebase(const char *path);
static char * get_longname(sbbs_t *sbbs, const char *path, const char *link, sftp_file_attr_t attr);
static sftp_file_attr_t get_attrs(sbbs_t *sbbs, const char *path, char **link);
const char files_path[] = SLASH_FILES; const char files_path[] = SLASH_FILES;
constexpr size_t files_path_len = (sizeof(files_path) - 1); constexpr size_t files_path_len = (sizeof(files_path) - 1);
...@@ -339,6 +343,105 @@ public: ...@@ -339,6 +343,105 @@ public:
} }
}; };
class file_names {
uint32_t entries_{0};
sbbs_t * const sbbs{};
std::vector<char *> fnames;
std::vector<char *> lnames;
std::vector<sftp_file_attr_t> attrs;
public:
uint32_t entries(void) { return entries_; }
bool add_name(char *fname, char *lname, sftp_file_attr_t attr) {
unsigned added = 0;
try {
fnames.push_back(fname);
added++;
lnames.push_back(lname);
added++;
attrs.push_back(attr);
added++;
entries_ += 1;
}
catch(...) {
if (added < 1)
free(fname);
if (added < 2)
free(lname);
if (added < 3)
sftp_fattr_free(attr);
return false;
}
return true;
}
bool
generic_dot_attr_entry(char *fname, sftp_file_attr_t attr, char **link, int32_t& idx)
{
if (attr == nullptr) {
free(fname);
return false;
}
char *lname = get_longname(sbbs, fname, link ? *link : nullptr, attr);
if (lname == nullptr) {
free(fname);
sftp_fattr_free(attr);
return false;
}
bool ret = add_name(fname, lname, attr);
if (ret)
idx++;
return ret;
}
bool
generic_dot_entry(char *fname, const char *path, int32_t& idx)
{
char *link = nullptr;
sftp_file_attr_t attr = get_attrs(sbbs, path, &link);
if (attr == nullptr) {
free(link);
free(fname);
return false;
}
bool ret = generic_dot_attr_entry(fname, attr, &link, idx);
free(link);
return ret;
}
bool
generic_dot_realpath_entry(char *fname, const char *path, int32_t& idx)
{
char *vpath = sftp_parse_crealpath(sbbs, path);
if (vpath == nullptr) {
free(fname);
return false;
}
bool ret = generic_dot_entry(fname, vpath, idx);
free(vpath);
return ret;
}
bool send(void) {
if (entries_ < 1)
return false;
return sftps_send_name(sbbs->sftp_state, entries_, &fnames[0], &lnames[0], &attrs[0]);
}
file_names() = delete;
file_names(sbbs_t *sbbsptr) : sbbs(sbbsptr) {}
~file_names(void) {
for (auto&& name : fnames)
free(name);
for (auto&& name : lnames)
free(name);
for (auto&& attr : attrs)
sftp_fattr_free(attr);
}
};
static bool static bool
is_in_filebase(const char *path) is_in_filebase(const char *path)
{ {
...@@ -915,45 +1018,6 @@ copy_path_from_dir(char *p, const char *fp) ...@@ -915,45 +1018,6 @@ copy_path_from_dir(char *p, const char *fp)
*last = 0; *last = 0;
} }
static bool
generic_dot_attr_entry(sbbs_t *sbbs, char *fname, sftp_file_attr_t attr, char **link, int32_t *idx)
{
if (attr == nullptr)
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Attributes allocation failure");
char *lname = get_longname(sbbs, fname, link ? *link : nullptr, attr);
if (lname == nullptr) {
sftp_fattr_free(attr);
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure");
}
(*idx)++;
bool ret = sftps_send_name(sbbs->sftp_state, 1, &fname, &lname, &attr);
free(lname);
sftp_fattr_free(attr);
return ret;
}
static bool
generic_dot_entry(sbbs_t *sbbs, char *fname, const char *path, int32_t *idx)
{
char *link;
sftp_file_attr_t attr = get_attrs(sbbs, path, &link);
bool ret = generic_dot_attr_entry(sbbs, fname, attr, &link, idx);
free(link);
return ret;
}
static bool
generic_dot_realpath_entry(sbbs_t *sbbs, char *fname, const char *path, int32_t *idx)
{
char *vpath = sftp_parse_crealpath(sbbs, path);
if (vpath == nullptr) {
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Path allocation failure");
}
bool ret = generic_dot_entry(sbbs, fname, vpath, idx);
free(vpath);
return ret;
}
static void static void
record_transfer(sbbs_t *sbbs, sftp_filedescriptor_t desc, bool upload) record_transfer(sbbs_t *sbbs, sftp_filedescriptor_t desc, bool upload)
{ {
...@@ -1373,8 +1437,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1373,8 +1437,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
char *vpath; char *vpath;
char *lname; char *lname;
char *ename; char *ename;
bool ret;
struct pathmap *pm; struct pathmap *pm;
file_names fn(sbbs);
sbbs->lprintf(LOG_DEBUG, "SFTP readdir(%.*s)", handle->len, handle->c_str); sbbs->lprintf(LOG_DEBUG, "SFTP readdir(%.*s)", handle->len, handle->c_str);
if (didx == UINT_MAX) if (didx == UINT_MAX)
...@@ -1385,21 +1449,25 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1385,21 +1449,25 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
char *link; char *link;
if (dd->info.rootdir.idx == no_more_files) { if (dd->info.rootdir.idx == no_more_files) {
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (dd->info.rootdir.idx == dot) { if (dd->info.rootdir.idx == dot) {
char *dir = const_cast<char *>("."); const char *dir = ".";
snprintf(tmppath, sizeof(tmppath), pm->sftp_patt, sbbs->useron.alias); snprintf(tmppath, sizeof(tmppath), pm->sftp_patt, sbbs->useron.alias);
remove_trailing_slash(tmppath); remove_trailing_slash(tmppath);
return generic_dot_entry(sbbs, dir, tmppath, &dd->info.rootdir.idx); if (!fn.generic_dot_entry(strdup(dir), tmppath, dd->info.rootdir.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Unable to add dot dir");
} }
if (dd->info.rootdir.idx == dotdot) { if (dd->info.rootdir.idx == dotdot) {
if (pm->sftp_patt[1]) { if (pm->sftp_patt[1]) {
char *dir = const_cast<char *>(".."); const char *dir = "..";
snprintf(tmppath, sizeof(tmppath) - 3 /* for dir */, pm->sftp_patt, sbbs->useron.alias); snprintf(tmppath, sizeof(tmppath) - 3 /* for dir */, pm->sftp_patt, sbbs->useron.alias);
tmppath[sizeof(tmppath) - 2] = 0; tmppath[sizeof(tmppath) - 2] = 0;
strcat(tmppath, dir); strcat(tmppath, dir);
return generic_dot_realpath_entry(sbbs, dir, tmppath, &dd->info.rootdir.idx); if (!fn.generic_dot_realpath_entry(strdup(dir), tmppath, dd->info.rootdir.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Unable to add dotdot dir");
} }
else else
dd->info.rootdir.idx++; dd->info.rootdir.idx++;
...@@ -1416,10 +1484,12 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1416,10 +1484,12 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Corrupt directory handle"); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Corrupt directory handle");
} }
copy_path(cwd, pm->sftp_patt); copy_path(cwd, pm->sftp_patt);
while (static_files[dd->info.rootdir.idx].sftp_patt != nullptr) { while (static_files[dd->info.rootdir.idx].sftp_patt != nullptr && fn.entries() < MAX_FILES_PER_READDIR) {
dd->info.rootdir.idx++; dd->info.rootdir.idx++;
if (static_files[dd->info.rootdir.idx].sftp_patt == nullptr) { if (static_files[dd->info.rootdir.idx].sftp_patt == nullptr) {
dd->info.rootdir.idx = no_more_files; dd->info.rootdir.idx = no_more_files;
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
copy_path_from_dir(tmppath, static_files[dd->info.rootdir.idx].sftp_patt); copy_path_from_dir(tmppath, static_files[dd->info.rootdir.idx].sftp_patt);
...@@ -1444,31 +1514,34 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1444,31 +1514,34 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure"); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure");
} }
vpath = getfname(tmppath); vpath = getfname(tmppath);
ret = sftps_send_name(sbbs->sftp_state, 1, &vpath, &lname, &attr); fn.add_name(strdup(vpath), lname, attr);
free(lname);
sftp_fattr_free(attr);
return ret;
} }
} }
else { else {
if (dd->info.filebase.lib == -1) { if (dd->info.filebase.lib == -1) {
// /files/ (ie: list of libs) // /files/ (ie: list of libs)
if (dd->info.filebase.idx == no_more_files) { if (dd->info.filebase.idx == no_more_files) {
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (dd->info.filebase.idx == dot) { if (dd->info.filebase.idx == dot) {
char *dir = const_cast<char *>("."); const char *dir = ".";
strcpy(tmppath, SLASH_FILES); strcpy(tmppath, SLASH_FILES);
return generic_dot_entry(sbbs, dir, tmppath, &dd->info.filebase.idx); if (!fn.generic_dot_entry(strdup(dir), tmppath, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Error adding topdoot");
} }
if (dd->info.filebase.idx == dotdot) { if (dd->info.filebase.idx == dotdot) {
char *dir = const_cast<char *>(".."); const char *dir = "..";
strcpy(tmppath, "/"); strcpy(tmppath, "/");
return generic_dot_entry(sbbs, dir, tmppath, &dd->info.filebase.idx); if (!fn.generic_dot_entry(strdup(dir), tmppath, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Error adding topdootdoot");
} }
while (dd->info.filebase.idx < sbbs->cfg.total_libs) { while (dd->info.filebase.idx < sbbs->cfg.total_libs && fn.entries() < MAX_FILES_PER_READDIR) {
if (dd->info.filebase.idx >= sbbs->cfg.total_libs) { if (dd->info.filebase.idx >= sbbs->cfg.total_libs) {
dd->info.filebase.idx = no_more_files; dd->info.filebase.idx = no_more_files;
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (!can_user_access_lib(&sbbs->cfg, dd->info.filebase.idx, &sbbs->useron, &sbbs->client)) { if (!can_user_access_lib(&sbbs->cfg, dd->info.filebase.idx, &sbbs->useron, &sbbs->client)) {
...@@ -1489,32 +1562,35 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1489,32 +1562,35 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
sftp_fattr_free(attr); sftp_fattr_free(attr);
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure"); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure");
} }
ret = sftps_send_name(sbbs->sftp_state, 1, &ename, &lname, &attr); if (!fn.add_name(ename, lname, attr))
free(ename); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Error adding lib");
free(lname);
sftp_fattr_free(attr);
dd->info.filebase.idx++; dd->info.filebase.idx++;
return ret;
} }
} }
else if (dd->info.filebase.dir == -1) { else if (dd->info.filebase.dir == -1) {
// /files/somelib (ie: list of dirs) // /files/somelib (ie: list of dirs)
if (dd->info.filebase.idx == no_more_files) { if (dd->info.filebase.idx == no_more_files) {
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (dd->info.filebase.idx == dot) { if (dd->info.filebase.idx == dot) {
char *dir = const_cast<char *>("."); const char *dir = ".";
attr = get_lib_attrs(sbbs, dd->info.filebase.lib); attr = get_lib_attrs(sbbs, dd->info.filebase.lib);
return generic_dot_attr_entry(sbbs, dir, attr, nullptr, &dd->info.filebase.idx); if (!fn.generic_dot_attr_entry(strdup(dir), attr, nullptr, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Adding libdoot");
} }
if (dd->info.filebase.idx == dotdot) { if (dd->info.filebase.idx == dotdot) {
char *dir = const_cast<char *>(".."); const char *dir = "..";
strcpy(tmppath, SLASH_FILES); strcpy(tmppath, SLASH_FILES);
return generic_dot_entry(sbbs, dir, tmppath, &dd->info.filebase.idx); if (!fn.generic_dot_entry(strdup(dir), tmppath, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Adding libdootdoot");
} }
while (dd->info.filebase.idx < sbbs->cfg.total_dirs) { while (dd->info.filebase.idx < sbbs->cfg.total_dirs && fn.entries() < MAX_FILES_PER_READDIR) {
if (dd->info.filebase.idx >= sbbs->cfg.total_dirs) { if (dd->info.filebase.idx >= sbbs->cfg.total_dirs) {
dd->info.filebase.idx = no_more_files; dd->info.filebase.idx = no_more_files;
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (sbbs->cfg.dir[dd->info.filebase.idx]->lib != dd->info.filebase.lib) { if (sbbs->cfg.dir[dd->info.filebase.idx]->lib != dd->info.filebase.lib) {
...@@ -1539,28 +1615,29 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1539,28 +1615,29 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
sftp_fattr_free(attr); sftp_fattr_free(attr);
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure"); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Longname allocation failure");
} }
ret = sftps_send_name(sbbs->sftp_state, 1, &ename, &lname, &attr); if (!fn.add_name(ename, lname, attr))
free(ename); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Add dir name failure");
free(lname);
sftp_fattr_free(attr);
dd->info.filebase.idx++; dd->info.filebase.idx++;
return ret;
} }
} }
else { else {
// /files/somelib/somedir (ie: list of files) // /files/somelib/somedir (ie: list of files)
if (dd->info.filebase.idx == no_more_files) { if (dd->info.filebase.idx == no_more_files) {
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (dd->info.filebase.idx == dot) { if (dd->info.filebase.idx == dot) {
char *dir = const_cast<char *>("."); const char *dir = ".";
attr = get_dir_attrs(sbbs, dd->info.filebase.dir); attr = get_dir_attrs(sbbs, dd->info.filebase.dir);
return generic_dot_attr_entry(sbbs, dir, attr, nullptr, &dd->info.filebase.idx); if (!fn.generic_dot_attr_entry(strdup(dir), attr, nullptr, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Adding dirdoot");
} }
if (dd->info.filebase.idx == dotdot) { if (dd->info.filebase.idx == dotdot) {
char *dir = const_cast<char *>(".."); const char *dir = "..";
attr = get_lib_attrs(sbbs, dd->info.filebase.lib); attr = get_lib_attrs(sbbs, dd->info.filebase.lib);
return generic_dot_attr_entry(sbbs, dir, attr, nullptr, &dd->info.filebase.idx); if (!fn.generic_dot_attr_entry(strdup(dir), attr, nullptr, dd->info.filebase.idx))
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "Adding dirdootdoot");
} }
// Find the "next"* file number. // Find the "next"* file number.
smb_t smb{}; smb_t smb{};
...@@ -1574,6 +1651,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1574,6 +1651,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
if (smb_getfirstidx(&smb, &idx) != SMB_SUCCESS) { if (smb_getfirstidx(&smb, &idx) != SMB_SUCCESS) {
smb_close(&smb); smb_close(&smb);
dd->info.filebase.idx = no_more_files; dd->info.filebase.idx = no_more_files;
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No files at all"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No files at all");
} }
file.hdr.number = idx.number; file.hdr.number = idx.number;
...@@ -1591,6 +1670,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1591,6 +1670,8 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
if (result == SMB_ERR_HDR_OFFSET) { if (result == SMB_ERR_HDR_OFFSET) {
smb_close(&smb); smb_close(&smb);
dd->info.filebase.idx = no_more_files; dd->info.filebase.idx = no_more_files;
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
} }
if (result != SMB_SUCCESS) { if (result != SMB_SUCCESS) {
...@@ -1623,24 +1704,23 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data) ...@@ -1623,24 +1704,23 @@ sftp_readdir(sftp_dirhandle_t handle, void *cb_data)
sftp_fattr_free(attr); sftp_fattr_free(attr);
continue; continue;
} }
smb_close(&smb);
break;
} while (1);
char *lname = get_longname(sbbs, cwd, nullptr, attr); char *lname = get_longname(sbbs, cwd, nullptr, attr);
if (lname == nullptr) { if (lname == nullptr) {
sftp_fattr_free(attr); sftp_fattr_free(attr);
smb_close(&smb);
return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Can't get file header"); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Can't get file header");
} }
vpath = tmppath; if (!fn.add_name(strdup(tmppath), lname, attr)) {
ret = sftps_send_name(sbbs->sftp_state, 1, &vpath, &lname, &attr); smb_close(&smb);
free(lname); return sftps_send_error(sbbs->sftp_state, SSH_FX_FAILURE, "Can't get file header");
sftp_fattr_free(attr); }
return ret; } while (fn.entries() < MAX_FILES_PER_READDIR);
smb_close(&smb);
} }
} }
if (fn.entries() > 0)
return fn.send();
return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files"); return sftps_send_error(sbbs->sftp_state, SSH_FX_EOF, "No more files");
return true;
} }
static bool static bool
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment