Skip to content

Commit 94ce0ad

Browse files
committed
Add test for wrong case in data directory
1 parent 0eeb895 commit 94ce0ad

File tree

12 files changed

+171
-121
lines changed

12 files changed

+171
-121
lines changed

code/cfile/cfilesystem.cpp

Lines changed: 108 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <errno.h>
1616
#include <sstream>
1717
#include <algorithm>
18+
#include <memory>
1819

1920
#ifdef _WIN32
2021
#include <io.h>
@@ -31,7 +32,13 @@
3132
#include <sys/stat.h>
3233
#include <unistd.h>
3334
#include <libgen.h>
34-
#include <cstdio>
35+
36+
struct dir_deleter {
37+
void operator()(DIR* dirp) {
38+
closedir(dirp);
39+
}
40+
};
41+
typedef std::unique_ptr<DIR, dir_deleter> unique_dir_ptr;
3542
#endif
3643

3744
#include "cfile/cfile.h"
@@ -80,13 +87,12 @@ static int Num_path_roots = 0;
8087

8188
// Created by searching all roots in order. This means Files is then sorted by precedence.
8289
typedef struct cf_file {
83-
char name_ext[CF_MAX_FILENAME_LENGTH]; // Filename and extension
84-
int root_index; // Where in Roots this is located
85-
int pathtype_index; // Where in Paths this is located
86-
time_t write_time; // When it was last written
87-
int size; // How big it is in bytes
88-
int pack_offset; // For pack files, where it is at. 0 if not in a pack file. This can be used to tell if in a pack file.
89-
char* real_name; // For real files, the full path
90+
char name_ext[CF_MAX_FILENAME_LENGTH]; // Filename and extension
91+
int root_index; // Where in Roots this is located
92+
int pathtype_index; // Where in Paths this is located
93+
time_t write_time; // When it was last written
94+
int size; // How big it is in bytes
95+
int pack_offset; // For pack files, where it is at. 0 if not in a pack file. This can be used to tell if in a pack file.
9096
} cf_file;
9197

9298
#define CF_NUM_FILES_PER_BLOCK 512
@@ -596,77 +602,72 @@ void cf_search_root_path(int root_index)
596602
_findclose( find_handle );
597603
}
598604
#elif defined SCP_UNIX
599-
DIR *dirp = nullptr;
600-
SCP_string search_dir;
605+
auto dirp = unique_dir_ptr(opendir(search_path));
606+
if (!dirp)
601607
{
602-
if (i == CF_TYPE_ROOT) {
603-
// Don't search for the same name for the root case since we would be searching in other mod directories in that case
604-
dirp = opendir (search_path);
605-
search_dir.assign(search_path);
606-
} else {
607-
// On Unix we can have a different case for the search paths so we also need to account for that
608-
// We do that by looking at the parent of search_path and enumerating all directories and the check if any of
609-
// them are a case-insensitive match
610-
SCP_string directory_name;
611-
612-
auto parentPathIter = pathTypeToRealPath.find(Pathtypes[i].parent_index);
613-
614-
if (parentPathIter == pathTypeToRealPath.end()) {
615-
// No parent known yet, use the standard dirname
616-
char dirname_copy[CF_MAX_PATHNAME_LENGTH];
617-
memcpy(dirname_copy, search_path, sizeof(search_path));
618-
// According to the documentation of directory_name and basename, the return value does not need to be freed
619-
directory_name.assign(dirname(dirname_copy));
620-
} else {
621-
// we have a valid parent path -> use that
622-
directory_name = parentPathIter->second;
623-
}
624-
625-
char basename_copy[CF_MAX_PATHNAME_LENGTH];
626-
memcpy(basename_copy, search_path, sizeof(search_path));
627-
// According to the documentation of dirname and basename, the return value does not need to be freed
628-
auto search_name = basename(basename_copy);
629-
630-
auto parentDirP = opendir(directory_name.c_str());
631-
632-
if (parentDirP) {
633-
struct dirent *dir = nullptr;
634-
while ((dir = readdir (parentDirP)) != nullptr) {
635-
636-
if (stricmp(search_name, dir->d_name)) {
637-
continue;
638-
}
608+
// If the directory does not exist then check if it might exist with a different case. If that's the case
609+
// we bail out with an error so inform the user that this is not valid.
610+
611+
// On Unix we can have a different case for the search paths so we also need to account for that
612+
// We do that by looking at the parent of search_path and enumerating all directories and then check if any of
613+
// them are a case-insensitive match
614+
SCP_string directory_name;
615+
616+
auto parentPathIter = pathTypeToRealPath.find(Pathtypes[i].parent_index);
617+
618+
// if (parentPathIter == pathTypeToRealPath.end()) {
619+
// No parent known yet, use the standard dirname
620+
char dirname_copy[CF_MAX_PATHNAME_LENGTH];
621+
memcpy(dirname_copy, search_path, sizeof(search_path));
622+
// According to the documentation of directory_name and basename, the return value does not need to be freed
623+
directory_name.assign(dirname(dirname_copy));
624+
// } else {
625+
// // we have a valid parent path -> use that
626+
// directory_name = parentPathIter->second;
627+
// }
628+
629+
char basename_copy[CF_MAX_PATHNAME_LENGTH];
630+
memcpy(basename_copy, search_path, sizeof(search_path));
631+
// According to the documentation of dirname and basename, the return value does not need to be freed
632+
auto search_name = basename(basename_copy);
633+
634+
auto parentDirP = unique_dir_ptr(opendir(directory_name.c_str()));
635+
if (parentDirP) {
636+
struct dirent *dir = nullptr;
637+
while ((dir = readdir (parentDirP.get())) != nullptr) {
638+
639+
if (stricmp(search_name, dir->d_name)) {
640+
continue;
641+
}
639642

640-
SCP_string fn;
641-
sprintf(fn, "%s/%s", directory_name.c_str(), dir->d_name);
643+
SCP_string fn;
644+
sprintf(fn, "%s/%s", directory_name.c_str(), dir->d_name);
642645

643-
struct stat buf;
644-
if (stat(fn.c_str(), &buf) == -1) {
645-
continue;
646-
}
646+
struct stat buf;
647+
if (stat(fn.c_str(), &buf) == -1) {
648+
continue;
649+
}
647650

648-
if (S_ISDIR(buf.st_mode)) {
649-
// Found a case insensitive match
650-
dirp = opendir(fn.c_str());
651-
search_dir = fn;
652-
// We also need to store this in our mapping since we may need it in the future
653-
pathTypeToRealPath.insert(std::make_pair(i, fn));
654-
break;
651+
if (S_ISDIR(buf.st_mode)) {
652+
// Found a case insensitive match
653+
// If the name is not exactly the same as the one we are currently searching then it's an error
654+
if (strcmp(search_name, dir->d_name)) {
655+
Error(LOCATION, "Data directory '%s' for path type '%s' does not match the required case! "
656+
"All data directories must exactly match the case specified by the engine or your mod "
657+
"will not work on other platforms.", fn.c_str(), Pathtypes[i].path);
655658
}
659+
break;
656660
}
657-
closedir(parentDirP);
658661
}
659662
}
660-
}
661-
662-
if ( dirp ) {
663+
} else {
663664
struct dirent *dir = nullptr;
664-
while ((dir = readdir (dirp)) != NULL)
665+
while ((dir = readdir (dirp.get())) != NULL)
665666
{
666667
if (!fnmatch ("*.*", dir->d_name, 0))
667668
{
668669
SCP_string fn;
669-
sprintf(fn, "%s/%s", search_dir.c_str(), dir->d_name);
670+
sprintf(fn, "%s/%s", search_path, dir->d_name);
670671

671672
struct stat buf;
672673
if (stat(fn.c_str(), &buf) == -1) {
@@ -693,15 +694,12 @@ void cf_search_root_path(int root_index)
693694

694695
file->pack_offset = 0; // Mark as a non-packed file
695696

696-
file->real_name = vm_strdup(fn.c_str());
697-
698697
num_files++;
699698
//mprintf(( "Found file '%s'\n", file->name_ext ));
700699
}
701700
}
702701
}
703702
}
704-
closedir(dirp);
705703
}
706704
#endif
707705
}
@@ -896,14 +894,6 @@ void cf_free_secondary_filelist()
896894
// Init the file blocks
897895
for (i=0; i<CF_MAX_FILE_BLOCKS; i++ ) {
898896
if ( File_blocks[i] ) {
899-
// Free file paths
900-
for (auto& f : File_blocks[i]->files) {
901-
if (f.real_name) {
902-
vm_free(f.real_name);
903-
f.real_name = nullptr;
904-
}
905-
}
906-
907897
vm_free( File_blocks[i] );
908898
File_blocks[i] = NULL;
909899
}
@@ -1062,14 +1052,17 @@ int cf_find_file_location( const char *filespec, int pathtype, int max_out, char
10621052
*offset = (size_t)f->pack_offset;
10631053

10641054
if (pack_filename) {
1055+
cf_root *r = cf_get_root(f->root_index);
1056+
1057+
strncpy( pack_filename, r->path, max_out );
1058+
10651059
if (f->pack_offset < 1) {
1066-
// This is a real file, return the actual file path
1067-
strncpy( pack_filename, f->real_name, max_out );
1068-
} else {
1069-
// File is in a pack file
1070-
cf_root *r = cf_get_root(f->root_index);
1060+
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
10711061

1072-
strncpy( pack_filename, r->path, max_out );
1062+
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1063+
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1064+
1065+
strcat_s( pack_filename, max_out, f->name_ext );
10731066
}
10741067
}
10751068

@@ -1087,14 +1080,19 @@ int cf_find_file_location( const char *filespec, int pathtype, int max_out, char
10871080
*offset = (size_t)f->pack_offset;
10881081

10891082
if (pack_filename) {
1083+
cf_root *r = cf_get_root(f->root_index);
1084+
1085+
strcpy( pack_filename, r->path );
1086+
10901087
if (f->pack_offset < 1) {
1091-
// This is a real file, return the actual file path
1092-
strncpy( pack_filename, f->real_name, max_out );
1093-
} else {
1094-
// File is in a pack file
1095-
cf_root *r = cf_get_root(f->root_index);
1088+
if ( strlen(Pathtypes[f->pathtype_index].path) ) {
1089+
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
10961090

1097-
strncpy( pack_filename, r->path, max_out );
1091+
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1092+
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1093+
}
1094+
1095+
strcat_s( pack_filename, max_out, f->name_ext );
10981096
}
10991097
}
11001098

@@ -1329,14 +1327,17 @@ int cf_find_file_location_ext( const char *filename, const int ext_num, const ch
13291327
*offset = f->pack_offset;
13301328

13311329
if (pack_filename) {
1330+
cf_root *r = cf_get_root(f->root_index);
1331+
1332+
strncpy( pack_filename, r->path, max_out );
1333+
13321334
if (f->pack_offset < 1) {
1333-
// This is a real file, return the actual file path
1334-
strncpy( pack_filename, f->real_name, max_out );
1335-
} else {
1336-
// File is in a pack file
1337-
cf_root *r = cf_get_root(f->root_index);
1335+
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
13381336

1339-
strncpy( pack_filename, r->path, max_out );
1337+
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1338+
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1339+
1340+
strcat_s( pack_filename, max_out, f->name_ext );
13401341
}
13411342
}
13421343

@@ -1357,14 +1358,20 @@ int cf_find_file_location_ext( const char *filename, const int ext_num, const ch
13571358
*offset = f->pack_offset;
13581359

13591360
if (pack_filename) {
1361+
cf_root *r = cf_get_root(f->root_index);
1362+
1363+
strcpy( pack_filename, r->path );
1364+
13601365
if (f->pack_offset < 1) {
1361-
// This is a real file, return the actual file path
1362-
strncpy( pack_filename, f->real_name, max_out );
1363-
} else {
1364-
// File is in a pack file
1365-
cf_root *r = cf_get_root(f->root_index);
13661366

1367-
strncpy( pack_filename, r->path, max_out );
1367+
if ( strlen(Pathtypes[f->pathtype_index].path) ) {
1368+
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
1369+
1370+
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1371+
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1372+
}
1373+
1374+
strcat_s( pack_filename, max_out, f->name_ext );
13681375
}
13691376
}
13701377

test/src/cfile/cfile.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
#include <gtest/gtest.h>
3+
#include <graphics/font.h>
4+
5+
#include "util/FSTestFixture.h"
6+
7+
class CFileTest : public test::FSTestFixture {
8+
public:
9+
CFileTest() : test::FSTestFixture(INIT_NONE) {
10+
pushModDir("cfile");
11+
}
12+
13+
protected:
14+
virtual void SetUp() override {
15+
test::FSTestFixture::SetUp();
16+
}
17+
virtual void TearDown() override {
18+
test::FSTestFixture::TearDown();
19+
20+
cfile_close();
21+
}
22+
};
23+
24+
25+
TEST_F(CFileTest, wrong_data_case) {
26+
SCP_string cfile_dir(TEST_DATA_PATH);
27+
cfile_dir += DIR_SEPARATOR_CHAR;
28+
cfile_dir += "test"; // Cfile expects something after the path
29+
30+
ASSERT_THROW(cfile_init(cfile_dir.c_str()), os::dialogs::ErrorException);
31+
}

test/src/menuui/test_intel_parse.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class IntelParseTest : public test::FSTestFixture {
99
public:
10-
IntelParseTest() : test::FSTestFixture(0) {
10+
IntelParseTest() : test::FSTestFixture(INIT_CFILE) {
1111
pushModDir("menuui");
1212
pushModDir("intel_parse");
1313
}

test/src/parse/test_parselo.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class ParseloTest : public test::FSTestFixture {
99
public:
10-
ParseloTest() : test::FSTestFixture(0) {
10+
ParseloTest() : test::FSTestFixture(INIT_CFILE) {
1111
pushModDir("parselo");
1212
}
1313

@@ -162,4 +162,4 @@ TEST(ParseloUtilTest, drop_whitespace) {
162162
test_str = " ";
163163
drop_white_space(test_str);
164164
ASSERT_EQ(test_str, "");
165-
}
165+
}

test/src/scripting/ScriptingTestFixture.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extern "C" {
1111
namespace test {
1212
namespace scripting {
1313

14-
ScriptingTestFixture::ScriptingTestFixture(uint64_t init_flags) : FSTestFixture(init_flags) {
14+
ScriptingTestFixture::ScriptingTestFixture(uint64_t init_flags) : FSTestFixture(init_flags | INIT_CFILE) {
1515
pushModDir("scripting");
1616
}
1717
void ScriptingTestFixture::SetUp() {

test/src/scripting/api/base.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class ScriptingBaseTest : public test::scripting::ScriptingTestFixture {
55
public:
6-
ScriptingBaseTest() : test::scripting::ScriptingTestFixture(0) {
6+
ScriptingBaseTest() : test::scripting::ScriptingTestFixture(INIT_NONE) {
77
pushModDir("base");
88
}
99
};

test/src/scripting/api/bitops.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class BitOpsTest : public test::scripting::ScriptingTestFixture {
55
public:
6-
BitOpsTest() : test::scripting::ScriptingTestFixture(0) {
6+
BitOpsTest() : test::scripting::ScriptingTestFixture(INIT_NONE) {
77
pushModDir("bitops");
88
}
99
};
@@ -30,4 +30,4 @@ TEST_F(BitOpsTest, checkBit) {
3030

3131
TEST_F(BitOpsTest, addBit) {
3232
this->EvalTestScript();
33-
}
33+
}

0 commit comments

Comments
 (0)