Skip to content

Commit

Permalink
Merge pull request scp-fs2open#5976 from notimaginative/fix_cmdline_m…
Browse files Browse the repository at this point in the history
…od_on_unix

refactor unix modlist handling to fix serveral issues
  • Loading branch information
Goober5000 authored Feb 5, 2024
2 parents af93922 + 7576d4f commit 6593565
Showing 1 changed file with 96 additions and 55 deletions.
151 changes: 96 additions & 55 deletions code/cmdline/cmdline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#ifdef SCP_UNIX
#include "osapi/osapi.h"
#include <dirent.h>
#include <sys/stat.h>
#endif

#include <cstring>
Expand Down Expand Up @@ -1208,93 +1209,133 @@ bool cmdline_parm::has_param() {
}

#ifdef SCP_UNIX
// Return a vector with all filesystem names of "parent/dir" relative to parent.
// dir must not contain a slash.
static SCP_vector<SCP_string> unix_get_single_dir_names(const SCP_string& parent, const SCP_string& dir)
extern void normalize_directory_separators(SCP_string &str);

static SCP_string get_real_mod_path(const char *mod)
{
SCP_vector<SCP_string> ret;
DIR *dirp;
SCP_string fullpath;
SCP_string mod_path = mod;

normalize_directory_separators(mod_path);

fullpath = SCP_string(".") + DIR_SEPARATOR_STR;
fullpath += mod_path;

// check if path already exists
dirp = opendir(fullpath.c_str());

if (dirp) {
// found it, return unchanged
closedir(dirp);

DIR *dp;
if ((dp = opendir(parent.c_str())) == NULL) {
Warning(LOCATION, "Can't open directory '%s' when searching mod paths. Ignoring. errno=%d", parent.c_str(), errno);
return ret;
return mod_path;
}

dirent *dirp;
while ((dirp = readdir(dp)) != NULL) {
if (!stricmp(dirp->d_name, dir.c_str())) {
ret.push_back(dirp->d_name);
// not found, check for a difference in case
SCP_string new_mod_path;
SCP_string part;
struct dirent *dir;
SCP_string::size_type separator;
SCP_string::size_type offset = 0;

fullpath = SCP_string(".") + DIR_SEPARATOR_STR;

do {
separator = mod_path.find(DIR_SEPARATOR_CHAR, offset);

if (separator != SCP_string::npos) {
part = mod_path.substr(offset, separator-offset);
offset += separator-offset + 1;
} else {
part = mod_path.substr(offset);
offset += part.length();
}
}
(void)closedir(dp);

return ret;
}
dirp = opendir(fullpath.c_str());

// Return a vector with all filesystem names of "parent/dir" relative to parent.
// Recurses to deal with slashes in dir.
static SCP_vector<SCP_string> unix_get_dir_names(const SCP_string& parent, const SCP_string& dir)
{
size_t slash = dir.find_first_of("/\\");
if ( !dirp ) {
break;
}

// no subdirectories, no need to recurse
if (slash == std::string::npos) {
return unix_get_single_dir_names(parent, dir);
}
while ((dir = readdir(dirp)) != nullptr) {
if (stricmp(dir->d_name, part.c_str())) {
continue;
}

// get the names of the first component of dir
SCP_vector<SCP_string> this_dir_names = unix_get_single_dir_names(parent, dir.substr(0, slash));
SCP_string fn = fullpath + dir->d_name;

SCP_string rest = dir.substr(slash + 1);
// make sure it accessible and a directory
struct stat buf;

SCP_vector<SCP_string> ret;
if (stat(fn.c_str(), &buf) == -1) {
continue;
}

// search for the rest of dir in each of these
SCP_vector<SCP_string>::iterator ii, end = this_dir_names.end();
for (ii = this_dir_names.begin(); ii != end; ++ii) {
SCP_string this_dir_path = parent + "/" + *ii;
SCP_vector<SCP_string> mod_path = unix_get_dir_names(this_dir_path, rest);
if ( !S_ISDIR(buf.st_mode) ) {
continue;
}

if ( !new_mod_path.empty() ) {
new_mod_path += DIR_SEPARATOR_STR;
}

new_mod_path += dir->d_name;

fullpath += dir->d_name;
fullpath += DIR_SEPARATOR_STR;

break;
}

// add all found paths relative to parent
SCP_vector<SCP_string>::iterator ii2, end2 = mod_path.end();
for (ii2 = mod_path.begin(); ii2 != end2; ++ii2) {
ret.push_back(*ii + "/" + *ii2);
if (dirp) {
closedir(dirp);
}
} while (offset < mod_path.length());

// Append anything left from mod_path onto the new path. This will catch trailing separators
// as well as mod folders which don't exist so that the mod string remains unchanged except
// for the directory name case.
if (new_mod_path.length() != mod_path.length()) {
new_mod_path += mod_path.substr(new_mod_path.length());
}

return ret;
return new_mod_path;
}

// For case sensitive filesystems (e.g. Linux/BSD) perform case-insensitive dir matches.
//
// NOTE: Aside from change in case no alterations should be made to the modlist. Missing
// mod folders should be left in place and no errors or warnings should be generated.
// There should be no difference between Windows and non-Windows modlist handling.
static void handle_unix_modlist(char **modlist, size_t *len)
{
// search filesystem for given paths
SCP_vector<SCP_string> mod_paths;
for (char *cur_mod = strtok(*modlist, ","); cur_mod != NULL; cur_mod = strtok(NULL, ","))
{
SCP_vector<SCP_string> this_mod_paths = unix_get_dir_names(".", cur_mod);
// Ignore non-existing mods for unit tests
if (!running_unittests && this_mod_paths.empty()) {
ReleaseWarning(LOCATION, "Can't find mod '%s'. Ignoring.", cur_mod);
}
mod_paths.insert(mod_paths.end(), this_mod_paths.begin(), this_mod_paths.end());

for (char *cur_mod = strtok(*modlist, ","); cur_mod != NULL; cur_mod = strtok(NULL, ",")) {
SCP_string path = get_real_mod_path(cur_mod);
mod_paths.push_back(path);
}

// create new char[] to replace modlist
size_t total_len = 0;
SCP_vector<SCP_string>::iterator ii, end = mod_paths.end();
for (ii = mod_paths.begin(); ii != end; ++ii) {
total_len += ii->length() + 1;

for (auto &path : mod_paths) {
total_len += path.length() + 1;
}

char *new_modlist = new char[total_len + 1];
memset(new_modlist, 0, total_len + 1);
end = mod_paths.end();
for (ii = mod_paths.begin(); ii != end; ++ii) {
strcat_s(new_modlist, total_len + 1, ii->c_str());
strcat_s(new_modlist, total_len + 1, ","); // replace later with NUL

for (auto &path : mod_paths) {
strcat_s(new_modlist, total_len + 1, path.c_str());
strcat_s(new_modlist, total_len + 1, ",");
}

// remove trailing comma
new_modlist[total_len-1] = '\0';

// make the rest of the modlist manipulation unaware that anything happened here
delete [] *modlist;
*modlist = new_modlist;
Expand Down Expand Up @@ -1799,7 +1840,7 @@ bool SetCmdlineParams()
if(mod_arg.found() ) {
Cmdline_mod = mod_arg.str();

// strip off blank space it it's there
// strip off blank space if it's there
if ( Cmdline_mod[strlen(Cmdline_mod)-1] == ' ' ) {
Cmdline_mod[strlen(Cmdline_mod)-1] = '\0';
}
Expand Down

0 comments on commit 6593565

Please sign in to comment.