Skip to content

Commit f0fbea6

Browse files
committed
FileSystem: ignore files listed in DELETED from delta pak dependencies
1 parent 2b62a54 commit f0fbea6

1 file changed

Lines changed: 194 additions & 32 deletions

File tree

src/common/FileSystem.cpp

Lines changed: 194 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ std::error_code throw_err;
146146
// Dependencies file in packages
147147
#define PAK_DEPS_FILE "DEPS"
148148

149+
// Deleted file list in packages
150+
#define PAK_DELETED_FILE "DELETED"
151+
149152
// Whether the search paths have been initialized yet. This can be used to delay
150153
// writing log files until the filesystem is initialized.
151154
static bool isInitialized = false;
@@ -1052,11 +1055,91 @@ struct LoadedPakGuard {
10521055
};
10531056
static LoadedPakGuard loadedPaksGuard;
10541057

1058+
// std::unordered_set uses std::hash which does not
1059+
// hash pair of std::string.
1060+
struct stdStringPairHasher
1061+
{
1062+
std::size_t operator()(const std::pair<std::string, std::string>& p) const
1063+
{
1064+
std::hash<std::string> string_hasher;
1065+
return string_hasher(p.first) ^ string_hasher(p.second);
1066+
}
1067+
};
1068+
1069+
// List of deleted filenames per pak files.
1070+
// first string is pak name, second string is deleted file name
1071+
static std::unordered_set<std::pair<std::string, std::string>, stdStringPairHasher> deletedFileSet;
1072+
10551073
// Map of filenames to pak files. The size_t is an offset into loadedPaks and
10561074
// the offset_t is the position within the zip archive (unused for PAK_DIR).
10571075
static std::unordered_map<std::string, std::pair<uint32_t, offset_t>> fileMap;
10581076

10591077
#ifndef BUILD_VM
1078+
/* Parse the deleted file list file of a package.
1079+
1080+
Each line of the file is the pak basename a file must
1081+
be deleted from, followed by the deleted file name.
1082+
1083+
Example:
1084+
map-chasm chasm-level0.navMesh
1085+
1086+
This would prevent loading any chasm-level0.navMesh file
1087+
from any dependencies with map-chasm basename.
1088+
1089+
Current pak name is known as parent.name so it would be
1090+
possible to implement a format variant that assumes pak name
1091+
to be the same as the one containing the DELETED file. */
1092+
static void ParseDeleted(const PakInfo& parent, Str::StringRef deletedData)
1093+
{
1094+
auto lineStart = deletedData.begin();
1095+
int line = 0;
1096+
while (lineStart != deletedData.end()) {
1097+
// Get the end of the line or the end of the file.
1098+
line++;
1099+
auto lineEnd = std::find(lineStart, deletedData.end(), '\n');
1100+
1101+
// Skip spaces.
1102+
while (lineStart != lineEnd && Str::cisspace(*lineStart)) {
1103+
lineStart++;
1104+
}
1105+
1106+
if (lineStart == lineEnd) {
1107+
lineStart = lineEnd == deletedData.end() ? lineEnd : lineEnd + 1;
1108+
continue;
1109+
}
1110+
1111+
// Read the pak name.
1112+
std::string pakName;
1113+
while (lineStart != lineEnd && !Str::cisspace(*lineStart)) {
1114+
pakName.push_back(*lineStart++);
1115+
}
1116+
1117+
// Skip spaces.
1118+
while (lineStart != lineEnd && Str::cisspace(*lineStart)) {
1119+
lineStart++;
1120+
}
1121+
1122+
if (lineStart != lineEnd) {
1123+
// Read the file name.
1124+
std::string fileName;
1125+
1126+
while (lineStart != lineEnd) {
1127+
fileName.push_back(*lineStart++);
1128+
}
1129+
1130+
// If this is the end of the line, add the path to the deleted file list.
1131+
if (lineStart == lineEnd) {
1132+
fsLogs.Debug("Deleted file %s from %s in %s", fileName, pakName, parent.path);
1133+
deletedFileSet.emplace(std::pair<std::string, std::string>(pakName, fileName));
1134+
continue;
1135+
}
1136+
}
1137+
1138+
fsLogs.Warn("Invalid deleted file list specification on line %d in %s", line, Path::Build(parent.path, PAK_DELETED_FILE));
1139+
lineStart = lineEnd == deletedData.end() ? lineEnd : lineEnd + 1;
1140+
}
1141+
}
1142+
10601143
// Parse the dependencies file of a package
10611144
// Each line of the dependencies file is a name followed by an optional version
10621145
static void ParseDeps(const PakInfo& parent, Str::StringRef depsData, std::error_code& err)
@@ -1130,9 +1213,34 @@ static void ParseDeps(const PakInfo& parent, Str::StringRef depsData, std::error
11301213
}
11311214
}
11321215

1216+
/* The code is expected to be only reliable for ignoring deleted files
1217+
in dependencies. For example if the unvanquished_0.52.2.dpk pak lists the
1218+
scripts/colors.shader file in the DELETED file and has unvanquished_0.52.1.dpk
1219+
in DEPS, the scripts/colors.shader file from unvanquished_0.52.1.dpk will
1220+
be ignored.
1221+
1222+
If the scripts/colors.shader file is still shipped in unvanquished_0.52.2.dpk
1223+
and listed in the DELETED file from unvanquished_0.52.2.dpk you cannot expect
1224+
the file to be ignored, it is expected the user deletes the file for real in
1225+
the parent pak. Making sure the DELETED file applies on files of the same pak
1226+
would increase code complexity while packager can just delete the file, in
1227+
some cases the file may be ignored (if DELETED file is read first) but you
1228+
must not rely on it and not expect it.
1229+
1230+
This feature means it's possible to only delete a file from a repository but
1231+
also to move a file from a pak repository to another pak repository and not
1232+
get the older version of the file being loaded from the old pak instead of
1233+
the new file from the new pak. */
1234+
static bool FileIsDeleted(const PakInfo& pak, Str::StringRef filename)
1235+
{
1236+
return deletedFileSet.find(std::pair<std::string, std::string>(pak.name, filename)) != deletedFileSet.end();
1237+
}
1238+
11331239
static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expectedChecksum, Str::StringRef pathPrefix, std::error_code& err)
11341240
{
11351241
Util::optional<uint32_t> realChecksum;
1242+
bool hasDeleted = false;
1243+
offset_t deletedOffset = 0;
11361244
bool hasDeps = false;
11371245
offset_t depsOffset = 0;
11381246
ZipArchive zipFile;
@@ -1177,10 +1285,19 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
11771285
if (err)
11781286
return;
11791287
for (auto it = dirRange.begin(); it != dirRange.end();) {
1180-
if (!isLegacy && (*it == PAK_DEPS_FILE))
1288+
if (!isLegacy && *it == PAK_DELETED_FILE) {
1289+
hasDeleted = true;
1290+
}
1291+
else if (!isLegacy && *it == PAK_DEPS_FILE) {
11811292
hasDeps = true;
1293+
}
11821294
else if (!Str::IsSuffix("/", *it) && Str::IsPrefix(pathPrefix, *it)) {
1183-
fileMap.emplace(*it, std::pair<uint32_t, offset_t>(loadedPaks.size() - 1, 0));
1295+
if (FileIsDeleted(pak, *it)) {
1296+
Log::Debug("Ignoring deleted file %s from %s", *it, pak.path);
1297+
}
1298+
else {
1299+
fileMap.emplace(*it, std::pair<uint32_t, offset_t>(loadedPaks.size() - 1, 0));
1300+
}
11841301
}
11851302
it.increment(err);
11861303
if (err)
@@ -1201,9 +1318,11 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12011318

12021319
// Get the file list and calculate the checksum of the package (checksum of all file checksums)
12031320
realChecksum = crc32(0, Z_NULL, 0);
1204-
zipFile.ForEachFile([&pak, &realChecksum, &pathPrefix, &hasDeps, &depsOffset, &isLegacy](Str::StringRef filename, offset_t offset, uint32_t crc) {
1321+
zipFile.ForEachFile([&pak, &realChecksum, &pathPrefix, &hasDeps, &hasDeleted, &depsOffset, &deletedOffset, &isLegacy](Str::StringRef filename, offset_t offset, uint32_t crc) {
12051322
// Note that 'return' is effectively 'continue' since we are in a lambda
1206-
if (!Str::IsPrefix(pathPrefix, filename) && filename != PAK_DEPS_FILE)
1323+
if (!Str::IsPrefix(pathPrefix, filename)
1324+
&& filename != PAK_DELETED_FILE
1325+
&& filename != PAK_DEPS_FILE)
12071326
return;
12081327
if (Str::IsSuffix("/", filename))
12091328
return;
@@ -1217,12 +1336,23 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12171336
realChecksum = crc32(*realChecksum, reinterpret_cast<const Bytef*>(&crc), sizeof(crc));
12181337
}
12191338

1220-
if (!isLegacy && (filename == PAK_DEPS_FILE)) {
1339+
if (!isLegacy && filename == PAK_DELETED_FILE) {
1340+
hasDeleted = true;
1341+
deletedOffset = offset;
1342+
return;
1343+
}
1344+
else if (!isLegacy && filename == PAK_DEPS_FILE) {
12211345
hasDeps = true;
12221346
depsOffset = offset;
12231347
return;
12241348
}
1225-
fileMap.emplace(filename, std::pair<uint32_t, offset_t>(loadedPaks.size() - 1, offset));
1349+
1350+
if (FileIsDeleted(pak, filename)) {
1351+
Log::Debug("Ignoring deleted file %s from %s", filename, pak.path);
1352+
}
1353+
else {
1354+
fileMap.emplace(filename, std::pair<uint32_t, offset_t>(loadedPaks.size() - 1, offset));
1355+
}
12261356
}, err);
12271357
if (err)
12281358
return;
@@ -1258,33 +1388,64 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12581388
fsLogs.Warn("Pak checksum doesn't match filename: %s", pak.path);
12591389
}
12601390

1261-
// Load dependencies, but not if a checksum was specified
1262-
// Do not look for dependencies if it's a legacy pak (pk3)
1263-
if (!isLegacy && hasDeps && !expectedChecksum) {
1264-
std::string depsData;
1265-
if (pak.type == pakType_t::PAK_DIR) {
1266-
File depsFile = RawPath::OpenRead(Path::Build(pak.path, PAK_DEPS_FILE), err);
1267-
if (err)
1268-
return;
1269-
depsData = depsFile.ReadAll(err);
1270-
if (err)
1271-
return;
1272-
} else if (pak.type == pakType_t::PAK_ZIP) {
1273-
zipFile.OpenFile(depsOffset, err);
1274-
if (err)
1275-
return;
1276-
offset_t length = zipFile.FileLength(err);
1277-
if (err)
1278-
return;
1279-
depsData.resize(length);
1280-
auto read = zipFile.ReadFile(&depsData[0], length, err);
1281-
depsData.resize(read);
1282-
if (err)
1283-
return;
1284-
} else {
1285-
ASSERT_UNREACHABLE();
1391+
// Load deleted file list
1392+
// Do not look for deleted file list if it's a legacy pak (pk3)
1393+
if (!isLegacy) {
1394+
if (hasDeleted) {
1395+
std::string deletedData;
1396+
if (pak.type == pakType_t::PAK_DIR) {
1397+
File depsFile = RawPath::OpenRead(Path::Build(pak.path, PAK_DELETED_FILE), err);
1398+
if (err)
1399+
return;
1400+
deletedData = depsFile.ReadAll(err);
1401+
if (err)
1402+
return;
1403+
} else if (pak.type == pakType_t::PAK_ZIP) {
1404+
zipFile.OpenFile(depsOffset, err);
1405+
if (err)
1406+
return;
1407+
offset_t length = zipFile.FileLength(err);
1408+
if (err)
1409+
return;
1410+
deletedData.resize(length);
1411+
auto read = zipFile.ReadFile(&deletedData[0], length, err);
1412+
deletedData.resize(read);
1413+
if (err)
1414+
return;
1415+
} else {
1416+
ASSERT_UNREACHABLE();
1417+
}
1418+
ParseDeleted(pak, deletedData);
1419+
}
1420+
1421+
// Load dependencies, but not if a checksum was specified
1422+
// Do not look for dependencies if it's a legacy pak (pk3)
1423+
if (hasDeps && !expectedChecksum) {
1424+
std::string depsData;
1425+
if (pak.type == pakType_t::PAK_DIR) {
1426+
File depsFile = RawPath::OpenRead(Path::Build(pak.path, PAK_DEPS_FILE), err);
1427+
if (err)
1428+
return;
1429+
depsData = depsFile.ReadAll(err);
1430+
if (err)
1431+
return;
1432+
} else if (pak.type == pakType_t::PAK_ZIP) {
1433+
zipFile.OpenFile(depsOffset, err);
1434+
if (err)
1435+
return;
1436+
offset_t length = zipFile.FileLength(err);
1437+
if (err)
1438+
return;
1439+
depsData.resize(length);
1440+
auto read = zipFile.ReadFile(&depsData[0], length, err);
1441+
depsData.resize(read);
1442+
if (err)
1443+
return;
1444+
} else {
1445+
ASSERT_UNREACHABLE();
1446+
}
1447+
ParseDeps(pak, depsData, err);
12861448
}
1287-
ParseDeps(pak, depsData, err);
12881449
}
12891450
}
12901451

@@ -1306,6 +1467,7 @@ void LoadPakExplicit(const PakInfo& pak, uint32_t expectedChecksum, std::error_c
13061467
void ClearPaks()
13071468
{
13081469
fsLogs.Verbose("^5Unloading all paks");
1470+
deletedFileSet.clear();
13091471
fileMap.clear();
13101472
for (LoadedPakInfo& x: loadedPaks) {
13111473
if (x.fd != -1)

0 commit comments

Comments
 (0)