Skip to content

Commit c60dcfe

Browse files
authored
When opening broken symlink with O_CREAT, create file at target (#23002)
If we open a broken symlink with `O_CREAT` we should create a new file as the target of the symlink. Fixes: #23001.
1 parent a6ecc86 commit c60dcfe

File tree

5 files changed

+61
-13
lines changed

5 files changed

+61
-13
lines changed

src/library_fs.js

+20-9
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,18 @@ FS.staticInit();
193193
break;
194194
}
195195

196-
current = FS.lookupNode(current, parts[i]);
197196
current_path = PATH.join2(current_path, parts[i]);
197+
try {
198+
current = FS.lookupNode(current, parts[i]);
199+
} catch (e) {
200+
// if noent_okay is true, suppress a ENOENT in the last component
201+
// and return an object with an undefined node. This is needed for
202+
// resolving symlinks in the path when creating a file.
203+
if ((e?.errno === {{{ cDefs.ENOENT }}}) && islast && opts.noent_okay) {
204+
return { path: current_path };
205+
}
206+
throw e;
207+
}
198208

199209
// jump to the mount's root node if this is a mountpoint
200210
if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) {
@@ -1036,14 +1046,15 @@ FS.staticInit();
10361046
node = path;
10371047
} else {
10381048
path = PATH.normalize(path);
1039-
try {
1040-
var lookup = FS.lookupPath(path, {
1041-
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}})
1042-
});
1043-
node = lookup.node;
1044-
} catch (e) {
1045-
// ignore
1046-
}
1049+
// noent_okay makes it so that if the final component of the path
1050+
// doesn't exist, lookupPath returns `node: undefined`. `path` will be
1051+
// updated to point to the target of all symlinks.
1052+
var lookup = FS.lookupPath(path, {
1053+
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}),
1054+
noent_okay: true
1055+
});
1056+
node = lookup.node;
1057+
path = lookup.path;
10471058
}
10481059
// perhaps we need to create the node
10491060
var created = false;

test/test_core.py

+4
Original file line numberDiff line numberDiff line change
@@ -6105,6 +6105,10 @@ def test_unistd_links(self, args, nodefs):
61056105

61066106
self.do_run_in_out_file_test('unistd/links.c', emcc_args=args)
61076107

6108+
@also_with_noderawfs
6109+
def test_unistd_write_broken_link(self):
6110+
self.do_run_in_out_file_test('unistd/test_unistd_write_broken_link.c')
6111+
61086112
@no_windows('Skipping NODEFS test, since it would require administrative privileges.')
61096113
@requires_node
61106114
def test_unistd_symlink_on_nodefs(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <unistd.h>
6+
7+
8+
int main() {
9+
int res = symlink("link_target", "link_source");
10+
printf("link result: %d\n", res);
11+
int src_fd = open("link_source", O_CREAT | O_WRONLY, 0777);
12+
printf("source_fd: %d, errno: %d %s\n", src_fd, errno, strerror(errno));
13+
write(src_fd, "abc", 3);
14+
close(src_fd);
15+
{
16+
int target_fd = open("link_target", O_RDONLY);
17+
printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno));
18+
char buf[10];
19+
read(target_fd, buf, 10);
20+
printf("buf: '%s'\n", buf);
21+
close(target_fd);
22+
}
23+
{
24+
int target_fd = open("link_source", O_RDONLY);
25+
printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno));
26+
char buf[10];
27+
read(target_fd, buf, 10);
28+
printf("buf: '%s'\n", buf);
29+
close(target_fd);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
link result: 0
2+
source_fd: 3, errno: 0 No error information
3+
target_fd: 3, errno: 0 No error information
4+
buf: 'abc'
5+
target_fd: 3, errno: 0 No error information
6+
buf: 'abc'

test/wasmfs/wasmfs_open.c

-4
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ int main() {
8989
int fd5 = open("/dev/stdout/foo", O_RDWR);
9090
printf("Errno: %s\n", strerror(errno));
9191
// Both errors are valid, but in WasmFS, ENOTDIR is returned first.
92-
#ifdef WASMFS
9392
assert(errno == ENOTDIR);
94-
#else
95-
assert(errno == ENOENT);
96-
#endif
9793

9894
errno = 0;
9995
// Attempt to open and write to the root directory.

0 commit comments

Comments
 (0)