diff --git a/AUTHORS b/AUTHORS index 3baa0068ef86b..ffb728e0258ab 100644 --- a/AUTHORS +++ b/AUTHORS @@ -602,3 +602,4 @@ a license to everyone to use it as detailed in LICENSE.) * Christian Lloyd (copyright owned by Teladoc Health, Inc.) * Sean Morris * Mitchell Wills (copyright owned by Google, Inc.) +* Alex Walsh (alexwalsh6x7@gmail.com> diff --git a/src/lib/libfs.js b/src/lib/libfs.js index d148c84f5a2d2..4251333c36745 100644 --- a/src/lib/libfs.js +++ b/src/lib/libfs.js @@ -1616,9 +1616,36 @@ FS.staticInit();`; path = name ? PATH.join2(parent, name) : parent; } var mode = FS_getMode(canRead, canWrite); - var node = FS.create(path, mode); if (data) { data = FS_fileDataToTypedArray(data); + } +#if CASE_INSENSITIVE_FS + try { +#endif + var node = FS.create(path, mode); +#if CASE_INSENSITIVE_FS + } catch (e) { + if (e.errno === {{{ cDefs.EEXIST }}}) { + var oldNodeLookup = FS.lookupPath(path); + var oldNode = oldNodeLookup.node; + if (data.length === oldNode.contents.length) { + var isDup = true; + for (var i = 0; i < data.length; i++) { + if (data[i] !== oldNode.contents[i]) { + isDup = false; + break; + } + } + if (isDup) { + throw e; + } + } + FS.destroyNode(oldNode); + node = FS.create(path, mode); + } + } +#endif + if (data) { // make sure we can write to the file FS.chmod(node, mode | {{{ cDefs.S_IWUGO }}}); var stream = FS.open(node, {{{ cDefs.O_TRUNC | cDefs.O_CREAT | cDefs.O_WRONLY }}}); diff --git a/test/other/test_crash_icase.c b/test/other/test_crash_icase.c new file mode 100644 index 0000000000000..dd7eb69b8ee84 --- /dev/null +++ b/test/other/test_crash_icase.c @@ -0,0 +1,39 @@ +/* + * Copyright 2026 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include + +#include + +void loadScript() { + printf("load2"); + FILE* file = fopen("file1.txt", "r"); + + if (!file) { + assert(false); + } + + while (!feof(file)) { + char c = fgetc(file); + if (c != EOF) { + putchar(c); + } + } + fclose(file); + exit(0); +} + +void scriptLoadFail() { + printf("failed to load data_files.js\n"); + assert(false); +} + +int main() { + emscripten_async_load_script("data_files.js", loadScript, scriptLoadFail); + return 99; +} diff --git a/test/other/test_overwrite_icase.js b/test/other/test_overwrite_icase.js new file mode 100644 index 0000000000000..13614faeba0a2 --- /dev/null +++ b/test/other/test_overwrite_icase.js @@ -0,0 +1,13 @@ +FS.createDataFile('/', "file.txt", "foo"); +FS.createDataFile('/', "fILe.txt", "foo2"); +var fileContents = FS.readFile("/file.txt"); +out('file.txt: ' + fileContents); +var ret = FS.analyzePath('/file.txt'); +out('file.txt collison: ' + ret.object.name_next); +var errCode = 0; +try { + FS.createDataFile('/', "FIlE.txt", "foo2"); +} catch (e) { + errCode = e.errno; +} +out('errorCode: ' + errCode); diff --git a/test/test_other.py b/test/test_other.py index 498f8b40bf1ab..a94002856e8d1 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13813,9 +13813,24 @@ def test_recursive_cache_lock(self): self.assertContained('AssertionError: attempt to lock the cache while a parent process is holding the lock', err) @also_with_wasmfs + @crossplatform def test_fs_icase(self): # c++20 for ends_with(). self.do_other_test('test_fs_icase.cpp', cflags=['-sCASE_INSENSITIVE_FS', '-std=c++20']) + create_file('file1.txt', 'one') + create_file('fILe1.txt', 'two') + # `--from-emcc` needed here otherwise the output defines `var Module =` which will shadow the + # global `Module`. + self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'file1.txt', 'fILe1.txt', '--from-emcc', '--js-output=data_files.js']) + self.do_runf('other/test_crash_icase.c', cflags=['-sFORCE_FILESYSTEM', '-sCASE_INSENSITIVE_FS']) + + @crossplatform + def test_overwrite_icase(self): + self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$FS']) + self.set_setting('INCLUDE_FULL_LIBRARY', 1) + self.set_setting('CASE_INSENSITIVE_FS', 1) + self.add_pre_run(read_file(test_file('other/test_overwrite_icase.js'))) + self.do_runf('hello_world.c', 'file.txt: 102,111,111,50\nfile.txt collison: undefined\nerrorCode: 20') @crossplatform @with_all_fs diff --git a/tools/file_packager.py b/tools/file_packager.py index daaceaec352b4..2af63ecf15b7a 100755 --- a/tools/file_packager.py +++ b/tools/file_packager.py @@ -683,8 +683,12 @@ def generate_preload_js(data_target, data_files, metadata): } catch (e) { err(`Preloading file ${name} failed`, e); }\n''' - create_data = '''// canOwn this data in the filesystem, it is a slice into the heap that will never change - Module['FS_createDataFile'](name, null, data, true, true, true); + create_data = '''try { + // canOwn this data in the filesystem, it is a slice into the heap that will never change + Module['FS_createDataFile'](name, null, data, true, true, true); + } catch(e) { + err(`Preloading file ${name} failed`, e); + } Module['removeRunDependency'](`fp ${name}`);''' finish_handler = create_preloaded if options.use_preload_plugins else create_data