Skip to content

Commit 5bd422e

Browse files
committed
When opening broken symlink with O_CREAT, create file at target
Instead of raising an EEXIST error
1 parent b3edd4c commit 5bd422e

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

src/library_fs.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,14 @@ FS.staticInit();
199199
break;
200200
}
201201

202-
current = FS.lookupNode(current, parts[i]);
203202
current_path = PATH.join2(current_path, parts[i]);
203+
try {
204+
current = FS.lookupNode(current, parts[i]);
205+
} catch (e) {
206+
if ((e?.errno === {{{ cDefs.ENOENT }}}) && islast && opts.handleBrokenLink) {
207+
return { path: current_path };
208+
}
209+
}
204210

205211
// jump to the mount's root node if this is a mountpoint
206212
if (FS.isMountpoint(current)) {
@@ -213,11 +219,17 @@ FS.staticInit();
213219
// setting opts.follow = true will override this behavior.
214220
if (!islast || opts.follow) {
215221
var count = 0;
216-
while (FS.isLink(current.mode)) {
217-
var link = FS.readlink(current_path);
222+
while (current && FS.isLink(current.mode)) {
223+
if (!current.node_ops.readlink) {
224+
throw new FS.ErrnoError({{{ cDefs.ENOSYS }}});
225+
}
226+
var link = current.node_ops.readlink(current);
218227
current_path = PATH_FS.resolve(PATH.dirname(current_path), link);
219228

220-
var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count + 1 });
229+
var lookup = FS.lookupPath(current_path, {
230+
recurse_count: opts.recurse_count + 1,
231+
handleBrokenLink: islast && opts.handleBrokenLink
232+
});
221233
current = lookup.node;
222234

223235
if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX).
@@ -1027,9 +1039,11 @@ FS.staticInit();
10271039
path = PATH.normalize(path);
10281040
try {
10291041
var lookup = FS.lookupPath(path, {
1030-
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}})
1042+
follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}),
1043+
handleBrokenLink: true
10311044
});
10321045
node = lookup.node;
1046+
path = lookup.path;
10331047
} catch (e) {
10341048
// ignore
10351049
}

test/test_core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6059,6 +6059,9 @@ def test_unistd_links(self, args, nodefs):
60596059

60606060
self.do_run_in_out_file_test('unistd/links.c', emcc_args=args)
60616061

6062+
def test_unistd_write_broken_link(self):
6063+
self.do_run_in_out_file_test('unistd/write_broken_link.c')
6064+
60626065
@no_windows('Skipping NODEFS test, since it would require administrative privileges.')
60636066
@requires_node
60646067
def test_unistd_symlink_on_nodefs(self):

test/unistd/write_broken_link.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <stdlib.h>
2+
#include <errno.h>
3+
#include <fcntl.h>
4+
#include <stdio.h>
5+
#include <string.h>
6+
#include <unistd.h>
7+
8+
9+
char* join_path(const char* path1, const char* path2) {
10+
int len1 = strlen(path1);
11+
int len2 = strlen(path2);
12+
char* result = malloc(len1 + len2 + 2);
13+
memcpy(result, path1, len1);
14+
result[len1] = '/';
15+
memcpy(result + len1 + 1, path2, len2);
16+
return result;
17+
}
18+
19+
int main() {
20+
char template[] = "/tmp/tmpdir.XXXXXX";
21+
char *tmpdir = mkdtemp(template);
22+
char* p1 = join_path(tmpdir, "test");
23+
char* p2 = join_path(tmpdir, "test2");
24+
25+
int res = symlink(p2, p1);
26+
printf("link result: %d\n", res);
27+
int src_fd = open(p1, O_CREAT | O_WRONLY, 0777);
28+
printf("source_fd: %d, errno: %d %s\n", src_fd, errno, strerror(errno));
29+
write(src_fd, "abc", 3);
30+
close(src_fd);
31+
{
32+
int target_fd = open(p2, O_RDONLY);
33+
printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno));
34+
char buf[10];
35+
read(target_fd, buf, 10);
36+
printf("buf: '%s'\n", buf);
37+
close(target_fd);
38+
}
39+
{
40+
int target_fd = open(p1, O_RDONLY);
41+
printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno));
42+
char buf[10];
43+
read(target_fd, buf, 10);
44+
printf("buf: '%s'\n", buf);
45+
close(target_fd);
46+
}
47+
free(p1);
48+
free(p2);
49+
}

test/unistd/write_broken_link.out

Lines changed: 6 additions & 0 deletions
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'

0 commit comments

Comments
 (0)