From 9c0b927dd321724016b05d506d6a1f008a46ac89 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 18 Jan 2021 14:44:17 -0800 Subject: [PATCH] Implement fstatat flag: AT_EMPTY_PATH Also, add new test for fstatat. I notived this issue while updating to the new musl version that uses this internally. --- src/library_syscall.js | 35 +++++---- src/struct_info.json | 3 +- tests/stat/test_fstatat.c | 146 ++++++++++++++++++++++++++++++++++++++ tests/stat/test_stat.c | 7 +- tests/test_core.py | 3 + tests/test_other.py | 8 +-- 6 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 tests/stat/test_fstatat.c diff --git a/src/library_syscall.js b/src/library_syscall.js index 530429bf9fe33..20b8de2e0ad3e 100644 --- a/src/library_syscall.js +++ b/src/library_syscall.js @@ -25,20 +25,26 @@ var SyscallsLibrary = { umask: 0x1FF, // S_IRWXU | S_IRWXG | S_IRWXO // shared utilities - calculateAt: function(dirfd, path) { - if (path[0] !== '/') { - // relative path - var dir; - if (dirfd === {{{ cDefine('AT_FDCWD') }}}) { - dir = FS.cwd(); - } else { - var dirstream = FS.getStream(dirfd); - if (!dirstream) throw new FS.ErrnoError({{{ cDefine('EBADF') }}}); - dir = dirstream.path; + calculateAt: function(dirfd, path, allowEmpty) { + if (path[0] === '/') { + return path; + } + // relative path + var dir; + if (dirfd === {{{ cDefine('AT_FDCWD') }}}) { + dir = FS.cwd(); + } else { + var dirstream = FS.getStream(dirfd); + if (!dirstream) throw new FS.ErrnoError({{{ cDefine('EBADF') }}}); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new FS.ErrnoError({{{ cDefine('ENOENT') }}});; } - path = PATH.join2(dir, path); + return dir; } - return path; + return PATH.join2(dir, path); }, doStat: function(func, path, buf) { @@ -1273,11 +1279,12 @@ var SyscallsLibrary = { __sys_fstatat64: function(dirfd, path, buf, flags) { path = SYSCALLS.getStr(path); var nofollow = flags & {{{ cDefine('AT_SYMLINK_NOFOLLOW') }}}; - flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') }}}); + var allowEmpty = flags & {{{ cDefine('AT_EMPTY_PATH') }}}; + flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') | cDefine('AT_EMPTY_PATH') }}}); #if ASSERTIONS assert(!flags, flags); #endif - path = SYSCALLS.calculateAt(dirfd, path); + path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); return SYSCALLS.doStat(nofollow ? FS.lstat : FS.stat, path, buf); }, __sys_unlinkat: function(dirfd, path, flags) { diff --git a/src/struct_info.json b/src/struct_info.json index 0a133b15cd660..c023af39de820 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -131,7 +131,8 @@ "O_WRONLY", "AT_FDCWD", "AT_SYMLINK_NOFOLLOW", - "AT_REMOVEDIR" + "AT_REMOVEDIR", + "AT_EMPTY_PATH" ], "structs": { "flock": [ diff --git a/tests/stat/test_fstatat.c b/tests/stat/test_fstatat.c new file mode 100644 index 0000000000000..0a5d5c1918fae --- /dev/null +++ b/tests/stat/test_fstatat.c @@ -0,0 +1,146 @@ +/* + * Copyright 2021 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 +#include +#include +#include +#include +#include +#include + + +void create_file(const char *path, const char *buffer, int mode) { + int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + assert(fd >= 0); + + int err = write(fd, buffer, sizeof(char) * strlen(buffer)); + assert(err == (sizeof(char) * strlen(buffer))); + + close(fd); +} + +void setup() { + mkdir("folder", 0777); + create_file("folder/file", "abcdef", 0777); + symlink("file", "folder/file-link"); +} + +void test() { + int err; + struct stat s; + + int fd = open(".", O_RDONLY); + assert(fd >= 0); + + // missing file + err = fstatat(fd, "does_not_exist", &s, 0); + assert(err == -1); + printf("errno: %d\n", errno); + assert(errno == ENOENT); + + // stat a folder + memset(&s, 0, sizeof(s)); + err = fstatat(fd, "folder", &s, 0); + assert(!err); + assert(s.st_dev); + assert(s.st_ino); + assert(S_ISDIR(s.st_mode)); + assert(s.st_nlink); + assert(s.st_rdev == 0); + assert(s.st_size); + assert(s.st_ctime); +#ifdef __EMSCRIPTEN__ + assert(s.st_blksize == 4096); + assert(s.st_blocks == 1); +#endif + + // stat a file + memset(&s, 0, sizeof(s)); + err = fstatat(fd, "folder/file", &s, 0); + assert(!err); + assert(s.st_dev); + assert(s.st_ino); + assert(S_ISREG(s.st_mode)); + assert(s.st_nlink); + assert(s.st_rdev == 0); + assert(s.st_size == 6); + assert(s.st_ctime); +#ifdef __EMSCRIPTEN__ + assert(s.st_blksize == 4096); + assert(s.st_blocks == 1); +#endif + + close(fd); + + fd = open("folder", O_RDONLY); + assert(fd >= 0); + + // missing file + err = fstatat(fd, "does_not_exist", &s, 0); + assert(err == -1); + assert(errno == ENOENT); + + // stat a file + memset(&s, 0, sizeof(s)); + err = fstatat(fd, "file", &s, 0); + assert(!err); + + // empty path - not allowed + err = fstatat(fd, "", &s, 0); + assert(err == -1); + assert(errno == ENOENT); + + // empty path - with AT_EMPTY_PATH (stat's the fd itself, a directory in this case) + memset(&s, 0, sizeof(s)); + err = fstatat(fd, "", &s, AT_EMPTY_PATH); + assert(!err); + assert(s.st_dev); + assert(s.st_ino); + assert(S_ISDIR(s.st_mode)); + assert(s.st_nlink); + assert(s.st_rdev == 0); + assert(s.st_size); + assert(s.st_ctime); +#ifdef __EMSCRIPTEN__ + assert(s.st_blksize == 4096); + assert(s.st_blocks == 1); +#endif + + close(fd); + + fd = open("folder/file", O_RDONLY); + assert(fd >= 0); + + // empty path - with AT_EMPTY_PATH (stat's the fd itself, a file) + memset(&s, 0, sizeof(s)); + err = fstatat(fd, "", &s, AT_EMPTY_PATH); + assert(!err); + assert(s.st_dev); + assert(s.st_ino); + assert(S_ISREG(s.st_mode)); + assert(s.st_nlink); + assert(s.st_rdev == 0); + assert(s.st_size == 6); + assert(s.st_ctime); +#ifdef __EMSCRIPTEN__ + assert(s.st_blksize == 4096); + assert(s.st_blocks == 1); +#endif + + close(fd); + + puts("success"); +} + +int main() { + setup(); + test(); + return 0; +} diff --git a/tests/stat/test_stat.c b/tests/stat/test_stat.c index 0bda38840721c..01f11a1062f25 100644 --- a/tests/stat/test_stat.c +++ b/tests/stat/test_stat.c @@ -32,7 +32,7 @@ void create_file(const char *path, const char *buffer, int mode) { void setup() { struct utimbuf t = {1200000000, 1200000000}; - + mkdir("folder", 0777); create_file("folder/file", "abcdef", 0777); symlink("file", "folder/file-link"); @@ -53,6 +53,11 @@ void test() { struct stat s; struct utimbuf t = {1200000000, 1200000000}; + // non-existent + err = stat("does_not_exist", &s); + assert(err == -1); + assert(errno == ENOENT); + // stat a folder memset(&s, 0, sizeof(s)); err = stat("folder", &s); diff --git a/tests/test_core.py b/tests/test_core.py index 2eca4a3340086..8f0b4aeee8cb6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4949,6 +4949,9 @@ def test_stat(self): self.do_runf(path_from_root('tests', 'stat', 'test_stat.c'), 'success') self.verify_in_strict_mode('test_stat.js') + def test_fstatat(self): + self.do_runf(path_from_root('tests', 'stat', 'test_fstatat.c'), 'success') + def test_stat_chmod(self): self.do_runf(path_from_root('tests', 'stat', 'test_chmod.c'), 'success') diff --git a/tests/test_other.py b/tests/test_other.py index 7d803ece6cf32..217c5c6934286 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -3762,15 +3762,15 @@ def test_stat_silly(self): const char *path = argv[i]; struct stat path_stat; if (stat(path, &path_stat) != 0) { - printf("Failed to stat path: %s; errno=%d\n", path, errno); + printf("Failed to stat path: '%s'; errno=%d\n", path, errno); } else { - printf("ok on %s\n", path); + printf("stat success on '%s'\n", path); } } } ''') - self.do_runf('src.cpp', r'''Failed to stat path: /a; errno=44 -Failed to stat path: ; errno=44 + self.do_runf('src.cpp', r'''Failed to stat path: '/a'; errno=44 +Failed to stat path: ''; errno=44 ''', args=['/a', '']) def test_symlink_silly(self):