Skip to content

Commit c399794

Browse files
authored
Merge pull request emscripten-core#29 from rstz/fix-parent_contents_bug
Fix rename
2 parents 7808174 + f5bbd68 commit c399794

File tree

5 files changed

+254
-88
lines changed

5 files changed

+254
-88
lines changed

pthreadfs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ See `pthreadfs/examples/emscripten-tests/fsafs.cpp` for exemplary usage.
7676
- Accessing the file system before `main()` requires linker option `PTHREAD_POOL_SIZE=<expression>` to be active. Doing so may lead to some blocking of the main thread, which is risky. Check out `examples/early_syscall.cpp` for an example.
7777
- Compiling with the Closure Compiler is not supported.
7878
- Compiling with optimization `-O3` is not yet supported and may lead to a faulty build.
79+
- Directories cannot be renamed when using OPFS Access Handles. This is currently a limitation of the underlying API.
7980

8081
## Examples
8182

+143-88
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,143 @@
1-
#include <iostream>
2-
#include <fstream>
3-
#include <cstdio>
4-
#include <emscripten.h>
5-
6-
7-
int main()
8-
{
9-
std::cout << "Start Rename test\n";
10-
11-
// MEMFS file that is closed when renaming.
12-
13-
std::ofstream closed_memfs_file;
14-
closed_memfs_file.open ("old_closed_memfs_file");
15-
closed_memfs_file << "Contents of closed_memfs_file.";
16-
closed_memfs_file.close();
17-
18-
if (std::rename("old_closed_memfs_file", "new_closed_memfs_file")) {
19-
std::cout << "Error renaming closed_memfs_file\n";
20-
return 1;
21-
}
22-
std::cout << "Rename closed_memfs_file successfully\n";
23-
24-
if(std::remove("new_closed_memfs_file")) {
25-
std::cout << "Removing closed_memfs_file failed\n";
26-
return 1;
27-
}
28-
std::cout << "Removed closed_memfs_file\n";
29-
30-
// MEMFS file that is open when renaming.
31-
32-
std::ofstream open_memfs_file;
33-
open_memfs_file.open ("old_open_memfs_file");
34-
open_memfs_file << "Contents of open_memfs_file.";
35-
36-
if (std::rename("old_open_memfs_file", "new_open_memfs_file")) {
37-
std::cout << "Error renaming open_memfs_file\n";
38-
return 1;
39-
}
40-
std::cout << "Rename open_memfs_file successfully\n";
41-
open_memfs_file.close();
42-
43-
if(std::remove("new_open_memfs_file")) {
44-
std::cout << "Removing open_memfs_file failed\n";
45-
return 1;
46-
}
47-
std::cout << "Removed open_memfs_file\n";
48-
49-
// PThreadFS file that is closed when renaming.
50-
std::ofstream closed_pthreadfs_file;
51-
closed_pthreadfs_file.open ("persistent/old_closed_pthreadfs_file");
52-
closed_pthreadfs_file << "Contents of closed_pthreadfs_file.";
53-
closed_pthreadfs_file.close();
54-
55-
if (std::rename("persistent/old_closed_pthreadfs_file", "persistent/new_closed_pthreadfs_file")) {
56-
std::cout << "Error renaming closed_pthreadfs_file\n";
57-
return 1;
58-
}
59-
std::cout << "Rename closed_pthreadfs_file successfully\n";
60-
closed_pthreadfs_file.close();
61-
62-
if(std::remove("persistent/new_closed_pthreadfs_file")) {
63-
std::cout << "Removing closed_pthreadfs_file failed\n";
64-
return 1;
65-
}
66-
std::cout << "Removed closed_pthreadfs_file\n";
67-
68-
// PThreadFS file that is open when renaming.
69-
std::ofstream open_pthreadfs_file;
70-
open_pthreadfs_file.open ("persistent/old_open_pthreadfs_file");
71-
open_pthreadfs_file << "Contents of open_pthreadfs_file.";
72-
73-
if (std::rename("persistent/old_open_pthreadfs_file", "persistent/new_open_pthreadfs_file")) {
74-
std::cout << "Error renaming open_pthreadfs_file\n";
75-
return 1;
76-
}
77-
std::cout << "Rename open_pthreadfs_file successfully\n";
78-
open_pthreadfs_file.close();
79-
80-
if(std::remove("persistent/new_open_pthreadfs_file")) {
81-
std::cout << "Removing open_pthreadfs_file failed.\n";
82-
std::cout << "This is expected when using the OPFS backend.\n";
83-
return 1;
84-
}
85-
std::cout << "Removed open_pthreadfs_file\n";
86-
87-
std::cout << "Success\n";
88-
}
1+
/*
2+
* Copyright 2013 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <assert.h>
9+
#include <errno.h>
10+
#include <fcntl.h>
11+
#include <signal.h>
12+
#include <stdio.h>
13+
#include <stdlib.h>
14+
#include <string.h>
15+
#include <unistd.h>
16+
#include <sys/stat.h>
17+
18+
// PThreadFS currently does not allow renaming or moving directories.
19+
#define PTHREADFS_NO_DIR_RENAME
20+
21+
void create_file(const char *path, const char *buffer, int mode) {
22+
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
23+
assert(fd >= 0);
24+
25+
int err = write(fd, buffer, sizeof(char) * strlen(buffer));
26+
assert(err == (sizeof(char) * strlen(buffer)));
27+
28+
close(fd);
29+
}
30+
31+
void setup() {
32+
create_file("persistent/file", "abcdef", 0777);
33+
mkdir("persistent/dir", 0777);
34+
create_file("persistent/dir/file", "abcdef", 0777);
35+
mkdir("persistent/dir/subdir", 0777);
36+
mkdir("persistent/dir-readonly", 0555);
37+
mkdir("persistent/dir-nonempty", 0777);
38+
mkdir("persistent/dir/subdir3", 0777);
39+
mkdir("persistent/dir/subdir3/subdir3_1", 0777);
40+
mkdir("persistent/dir/subdir4/", 0777);
41+
create_file("persistent/dir-nonempty/file", "abcdef", 0777);
42+
}
43+
44+
void cleanup() {
45+
// we're hulk-smashing and removing original + renamed files to
46+
// make sure we get it all regardless of anything failing
47+
unlink("persistent/file");
48+
unlink("persistent/dir/file");
49+
unlink("persistent/dir/file1");
50+
unlink("persistent/dir/file2");
51+
rmdir("persistent/dir/subdir");
52+
rmdir("persistent/dir/subdir1");
53+
rmdir("persistent/dir/subdir2");
54+
rmdir("persistent/dir/subdir3/subdir3_1/subdir1 renamed");
55+
rmdir("persistent/dir/subdir3/subdir3_1");
56+
rmdir("persistent/dir/subdir3");
57+
rmdir("persistent/dir/subdir4/");
58+
rmdir("persistent/dir/subdir5/");
59+
rmdir("persistent/dir");
60+
rmdir("persistent/dir-readonly");
61+
unlink("persistent/dir-nonempty/file");
62+
rmdir("persistent/dir-nonempty");
63+
}
64+
65+
void test() {
66+
int err;
67+
68+
// can't rename something that doesn't exist
69+
err = rename("persistent/noexist", "persistent/dir");
70+
assert(err == -1);
71+
assert(errno == ENOENT);
72+
73+
// can't overwrite a folder with a file
74+
err = rename("persistent/file", "persistent/dir");
75+
assert(err == -1);
76+
assert(errno == EISDIR);
77+
78+
// can't overwrite a file with a folder
79+
err = rename("persistent/dir", "persistent/file");
80+
assert(err == -1);
81+
assert(errno == ENOTDIR);
82+
83+
#ifndef PTHREADFS_NO_DIR_RENAME
84+
// can't overwrite a non-empty folder
85+
err = rename("persistent/dir", "persistent/dir-nonempty");
86+
assert(err == -1);
87+
assert(errno == ENOTEMPTY);
88+
89+
// can't create anything in a read-only directory
90+
err = rename("persistent/dir", "persistent/dir-readonly/dir");
91+
assert(err == -1);
92+
assert(errno == EACCES);
93+
94+
// source should not be ancestor of target
95+
err = rename("persistent/dir", "persistent/dir/somename");
96+
assert(err == -1);
97+
assert(errno == EINVAL);
98+
99+
// target should not be an ancestor of source
100+
err = rename("persistent/dir/subdir", "persistent/dir");
101+
assert(err == -1);
102+
assert(errno == ENOTEMPTY);
103+
#endif // PTHREADFS_NO_DIR_RENAME
104+
105+
// do some valid renaming
106+
err = rename("persistent/dir/file", "persistent/dir/file1");
107+
assert(!err);
108+
err = rename("persistent/dir/file1", "persistent/dir/file2");
109+
assert(!err);
110+
err = access("persistent/dir/file2", F_OK);
111+
assert(!err);
112+
113+
#ifndef PTHREADFS_NO_DIR_RENAME
114+
err = rename("persistent/dir/subdir", "persistent/dir/subdir1");
115+
assert(!err);
116+
err = rename("persistent/dir/subdir1", "persistent/dir/subdir2");
117+
assert(!err);
118+
err = access("persistent/dir/subdir2", F_OK);
119+
assert(!err);
120+
121+
err = rename("persistent/dir/subdir2", "persistent/dir/subdir3/subdir3_1/subdir1 renamed");
122+
assert(!err);
123+
err = access("persistent/dir/subdir3/subdir3_1/subdir1 renamed", F_OK);
124+
assert(!err);
125+
126+
// test that non-existant parent during rename generates the correct error code
127+
err = rename("persistent/dir/hicsuntdracones/empty", "persistent/dir/hicsuntdracones/renamed");
128+
assert(err == -1);
129+
assert(errno == ENOENT);
130+
131+
err = rename("persistent/dir/subdir4/", "persistent/dir/subdir5/");
132+
assert(!err);
133+
#endif // PTHREADFS_NO_DIR_RENAME
134+
135+
puts("success");
136+
}
137+
138+
int main() {
139+
setup();
140+
test();
141+
cleanup();
142+
return EXIT_SUCCESS;
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include <iostream>
2+
#include <fstream>
3+
#include <cstdio>
4+
#include <emscripten.h>
5+
6+
7+
int main()
8+
{
9+
std::cout << "Start Rename test\n";
10+
11+
// MEMFS file that is closed when renaming.
12+
13+
std::ofstream closed_memfs_file;
14+
closed_memfs_file.open ("old_closed_memfs_file");
15+
closed_memfs_file << "Contents of closed_memfs_file.";
16+
closed_memfs_file.close();
17+
18+
if (std::rename("old_closed_memfs_file", "new_closed_memfs_file")) {
19+
std::cout << "Error renaming closed_memfs_file\n";
20+
return 1;
21+
}
22+
std::cout << "Rename closed_memfs_file successfully\n";
23+
24+
if(std::remove("new_closed_memfs_file")) {
25+
std::cout << "Removing closed_memfs_file failed\n";
26+
return 1;
27+
}
28+
std::cout << "Removed closed_memfs_file\n";
29+
30+
// MEMFS file that is open when renaming.
31+
32+
std::ofstream open_memfs_file;
33+
open_memfs_file.open ("old_open_memfs_file");
34+
open_memfs_file << "Contents of open_memfs_file.";
35+
36+
if (std::rename("old_open_memfs_file", "new_open_memfs_file")) {
37+
std::cout << "Error renaming open_memfs_file\n";
38+
return 1;
39+
}
40+
std::cout << "Rename open_memfs_file successfully\n";
41+
open_memfs_file.close();
42+
43+
if(std::remove("new_open_memfs_file")) {
44+
std::cout << "Removing open_memfs_file failed\n";
45+
return 1;
46+
}
47+
std::cout << "Removed open_memfs_file\n";
48+
49+
// PThreadFS file that is closed when renaming.
50+
std::ofstream closed_pthreadfs_file;
51+
closed_pthreadfs_file.open ("persistent/old_closed_pthreadfs_file");
52+
closed_pthreadfs_file << "Contents of closed_pthreadfs_file.";
53+
closed_pthreadfs_file.close();
54+
55+
if (std::rename("persistent/old_closed_pthreadfs_file", "persistent/new_closed_pthreadfs_file")) {
56+
std::cout << "Error renaming closed_pthreadfs_file\n";
57+
return 1;
58+
}
59+
std::cout << "Rename closed_pthreadfs_file successfully\n";
60+
closed_pthreadfs_file.close();
61+
62+
if(std::remove("persistent/new_closed_pthreadfs_file")) {
63+
std::cout << "Removing closed_pthreadfs_file failed\n";
64+
return 1;
65+
}
66+
std::cout << "Removed closed_pthreadfs_file\n";
67+
68+
// PThreadFS file that is open when renaming.
69+
std::ofstream open_pthreadfs_file;
70+
open_pthreadfs_file.open ("persistent/old_open_pthreadfs_file");
71+
open_pthreadfs_file << "Contents of open_pthreadfs_file.";
72+
73+
if (std::rename("persistent/old_open_pthreadfs_file", "persistent/new_open_pthreadfs_file")) {
74+
std::cout << "Error renaming open_pthreadfs_file\n";
75+
return 1;
76+
}
77+
std::cout << "Rename open_pthreadfs_file successfully\n";
78+
open_pthreadfs_file.close();
79+
80+
if(std::remove("persistent/new_open_pthreadfs_file")) {
81+
std::cout << "Removing open_pthreadfs_file failed.\n";
82+
std::cout << "This is expected when using the OPFS backend.\n";
83+
return 1;
84+
}
85+
std::cout << "Removed open_pthreadfs_file\n";
86+
87+
std::cout << "Success\n";
88+
}

pthreadfs/library_pthreadfs.js

+11
Original file line numberDiff line numberDiff line change
@@ -3187,6 +3187,10 @@ mergeInto(LibraryManager.library, {
31873187

31883188
rename: async function (oldNode, newParentNode, newName) {
31893189
FSAFS.debug('rename', arguments);
3190+
if (PThreadFS.isDir(oldNode.mode)) {
3191+
console.log('Rename error: File System Access does not support renaming directories');
3192+
throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}});
3193+
}
31903194
try {
31913195
await oldNode.localReference.move(newParentNode.localReference, newName);
31923196
}
@@ -3206,6 +3210,13 @@ mergeInto(LibraryManager.library, {
32063210
}
32073211
throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}});
32083212
}
3213+
// Update the internal directory cache.
3214+
delete oldNode.parent.contents[oldNode.name];
3215+
oldNode.parent.timestamp = Date.now()
3216+
oldNode.name = newName;
3217+
newParentNode.contents[newName] = oldNode;
3218+
newParentNode.timestamp = oldNode.parent.timestamp;
3219+
oldNode.parent = newParentNode;
32093220
},
32103221

32113222
unlink: async function(parent, name) {

pthreadfs/src/js/library_fsafs.js

+11
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ mergeInto(LibraryManager.library, {
189189

190190
rename: async function (oldNode, newParentNode, newName) {
191191
FSAFS.debug('rename', arguments);
192+
if (PThreadFS.isDir(oldNode.mode)) {
193+
console.log('Rename error: File System Access does not support renaming directories');
194+
throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}});
195+
}
192196
try {
193197
await oldNode.localReference.move(newParentNode.localReference, newName);
194198
}
@@ -208,6 +212,13 @@ mergeInto(LibraryManager.library, {
208212
}
209213
throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}});
210214
}
215+
// Update the internal directory cache.
216+
delete oldNode.parent.contents[oldNode.name];
217+
oldNode.parent.timestamp = Date.now()
218+
oldNode.name = newName;
219+
newParentNode.contents[newName] = oldNode;
220+
newParentNode.timestamp = oldNode.parent.timestamp;
221+
oldNode.parent = newParentNode;
211222
},
212223

213224
unlink: async function(parent, name) {

0 commit comments

Comments
 (0)