diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index d63bb5be7bff4..e29b47cccda83 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -46,6 +46,9 @@ #ifdef _MSC_VER #include // For mkdir etc. #endif +#ifdef _WIN32 +#include +#endif typedef struct _mp_obj_vfs_posix_t { mp_obj_base_t base; @@ -55,21 +58,23 @@ typedef struct _mp_obj_vfs_posix_t { } mp_obj_vfs_posix_t; STATIC const char *vfs_posix_get_path_str(mp_obj_vfs_posix_t *self, mp_obj_t path) { - if (self->root_len == 0) { - return mp_obj_str_get_str(path); + const char *path_str = mp_obj_str_get_str(path); + if (self->root_len == 0 || path_str[0] != '/') { + return path_str; } else { - self->root.len = self->root_len; - vstr_add_str(&self->root, mp_obj_str_get_str(path)); + self->root.len = self->root_len - 1; + vstr_add_str(&self->root, path_str); return vstr_null_terminated_str(&self->root); } } STATIC mp_obj_t vfs_posix_get_path_obj(mp_obj_vfs_posix_t *self, mp_obj_t path) { - if (self->root_len == 0) { + const char *path_str = mp_obj_str_get_str(path); + if (self->root_len == 0 || path_str[0] != '/') { return path; } else { - self->root.len = self->root_len; - vstr_add_str(&self->root, mp_obj_str_get_str(path)); + self->root.len = self->root_len - 1; + vstr_add_str(&self->root, path_str); return mp_obj_new_str(self->root.buf, self->root.len); } } @@ -107,7 +112,28 @@ STATIC mp_obj_t vfs_posix_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_obj_vfs_posix_t *vfs = mp_obj_malloc(mp_obj_vfs_posix_t, type); vstr_init(&vfs->root, 0); if (n_args == 1) { - vstr_add_str(&vfs->root, mp_obj_str_get_str(args[0])); + const char *root = mp_obj_str_get_str(args[0]); + // if the root is relative, make it absolute, otherwise we'll get confused by chdir + #ifdef _WIN32 + char buf[MICROPY_ALLOC_PATH_MAX + 1]; + DWORD result = GetFullPathNameA(root, sizeof(buf), buf, NULL); + if (result > 0 && result < sizeof(buf)) { + vstr_add_str(&vfs->root, buf); + } else { + mp_raise_OSError(GetLastError()); + } + #else + if (root[0] != '\0' && root[0] != '/') { + char buf[MICROPY_ALLOC_PATH_MAX + 1]; + const char *cwd = getcwd(buf, sizeof(buf)); + if (cwd == NULL) { + mp_raise_OSError(errno); + } + vstr_add_str(&vfs->root, cwd); + vstr_add_char(&vfs->root, '/'); + } + vstr_add_str(&vfs->root, root); + #endif vstr_add_char(&vfs->root, '/'); } vfs->root_len = vfs->root.len; @@ -160,7 +186,14 @@ STATIC mp_obj_t vfs_posix_getcwd(mp_obj_t self_in) { if (ret == NULL) { mp_raise_OSError(errno); } - ret += self->root_len; + if (self->root_len > 0) { + ret += self->root_len - 1; + #ifdef _WIN32 + if (*ret == '\\') { + *(char *)ret = '/'; + } + #endif + } return mp_obj_new_str(ret, strlen(ret)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(vfs_posix_getcwd_obj, vfs_posix_getcwd); diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py index 05dc0f537e3ca..bc4c7c2014b6c 100644 --- a/tests/extmod/vfs_posix.py +++ b/tests/extmod/vfs_posix.py @@ -88,6 +88,9 @@ def write_files_without_closing(): # construct new VfsPosix with path argument vfs = os.VfsPosix(temp_dir) +# when VfsPosix is used the intended way via os.mount(), it can only be called +# with relative paths when the CWD is inside or at its root, so simulate that +os.chdir(temp_dir) print(list(i[0] for i in vfs.ilistdir("."))) # stat, statvfs (statvfs may not exist) @@ -99,6 +102,22 @@ def write_files_without_closing(): print(type(list(vfs.ilistdir("."))[0][0])) print(type(list(vfs.ilistdir(b"."))[0][0])) +# chdir should not affect absolute paths (regression test) +vfs.mkdir("/subdir") +vfs.mkdir("/subdir/micropy_test_dir") +with vfs.open("/subdir/micropy_test_dir/test2", "w") as f: + f.write("wrong") +vfs.chdir("/subdir") +with vfs.open("/test2", "r") as f: + print(f.read()) +os.chdir(curdir) +vfs.remove("/subdir/micropy_test_dir/test2") +vfs.rmdir("/subdir/micropy_test_dir") +vfs.rmdir("/subdir") + +# done with vfs, restore CWD +os.chdir(curdir) + # remove os.remove(temp_dir + "/test2") print(os.listdir(temp_dir)) diff --git a/tests/extmod/vfs_posix.py.exp b/tests/extmod/vfs_posix.py.exp index 99922e621d4c2..bd1ec7bad67b2 100644 --- a/tests/extmod/vfs_posix.py.exp +++ b/tests/extmod/vfs_posix.py.exp @@ -10,6 +10,7 @@ next_file_no <= base_file_no True +hello [] remove OSError False diff --git a/tests/extmod/vfs_posix_enoent.py b/tests/extmod/vfs_posix_enoent.py new file mode 100644 index 0000000000000..e6010d79d58f0 --- /dev/null +++ b/tests/extmod/vfs_posix_enoent.py @@ -0,0 +1,42 @@ +# Test for VfsPosix error conditions + +try: + import os + import sys + + os.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +if sys.platform == "win32": + # Windows doesn't let you delete the current directory, so this cannot be + # tested. + print("SKIP") + raise SystemExit + +# We need an empty directory for testing. +# Skip the test if it already exists. +temp_dir = "vfs_posix_enoent_test_dir" +try: + os.stat(temp_dir) + print("SKIP") + raise SystemExit +except OSError: + pass + +curdir = os.getcwd() +os.mkdir(temp_dir) +os.chdir(temp_dir) +os.rmdir(curdir + "/" + temp_dir) +try: + print("getcwd():", os.getcwd()) +except OSError as e: + # expecting ENOENT = 2 + print("getcwd():", repr(e)) + +try: + print("VfsPosix():", os.VfsPosix("something")) +except OSError as e: + # expecting ENOENT = 2 + print("VfsPosix():", repr(e)) diff --git a/tests/extmod/vfs_posix_enoent.py.exp b/tests/extmod/vfs_posix_enoent.py.exp new file mode 100644 index 0000000000000..f2d9a0d551068 --- /dev/null +++ b/tests/extmod/vfs_posix_enoent.py.exp @@ -0,0 +1,2 @@ +getcwd(): OSError(2,) +VfsPosix(): OSError(2,) diff --git a/tests/extmod/vfs_posix_ilistdir_del.py b/tests/extmod/vfs_posix_ilistdir_del.py index edb50dfd6229c..8c8e6978b6540 100644 --- a/tests/extmod/vfs_posix_ilistdir_del.py +++ b/tests/extmod/vfs_posix_ilistdir_del.py @@ -11,7 +11,13 @@ def test(testdir): + curdir = os.getcwd() vfs = os.VfsPosix(testdir) + # When VfsPosix is used the intended way via os.mount(), it can only be called + # with relative paths when the CWD is inside or at its root, so simulate that. + # (Although perhaps calling with a relative path was an oversight in this case + # and the respective line below was meant to read `vfs.rmdir("/" + dname)`.) + os.chdir(testdir) vfs.mkdir("/test_d1") vfs.mkdir("/test_d2") vfs.mkdir("/test_d3") @@ -48,6 +54,9 @@ def test(testdir): vfs.open("/test", "w").close() vfs.remove("/test") + # Done with vfs, restore CWD. + os.chdir(curdir) + # We need an empty directory for testing. # Skip the test if it already exists. diff --git a/tests/extmod/vfs_posix_ilistdir_filter.py b/tests/extmod/vfs_posix_ilistdir_filter.py index c32d1249710de..54012746057a8 100644 --- a/tests/extmod/vfs_posix_ilistdir_filter.py +++ b/tests/extmod/vfs_posix_ilistdir_filter.py @@ -10,7 +10,11 @@ def test(testdir): + curdir = os.getcwd() vfs = os.VfsPosix(testdir) + # When VfsPosix is used the intended way via os.mount(), it can only be called + # with relative paths when the CWD is inside or at its root, so simulate that. + os.chdir(testdir) dirs = [".a", "..a", "...a", "a.b", "a..b"] @@ -24,6 +28,9 @@ def test(testdir): print(dirs) + # Done with vfs, restore CWD. + os.chdir(curdir) + # We need an empty directory for testing. # Skip the test if it already exists. diff --git a/tests/extmod/vfs_posix_paths.py b/tests/extmod/vfs_posix_paths.py new file mode 100644 index 0000000000000..5806a34521a23 --- /dev/null +++ b/tests/extmod/vfs_posix_paths.py @@ -0,0 +1,92 @@ +# Test for VfsPosix with relative paths + +try: + import os + + os.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# We need a directory for testing that doesn't already exist. +# Skip the test if it does exist. +temp_dir = "vfs_posix_paths_test_dir" +try: + import os + + os.stat(temp_dir) + print("SKIP") + raise SystemExit +except OSError: + pass + +curdir = os.getcwd() +os.mkdir(temp_dir) + +# construct new VfsPosix with absolute path argument +temp_dir_abs = os.getcwd() + os.sep + temp_dir +vfs = os.VfsPosix(temp_dir_abs) +# when VfsPosix is used the intended way via os.mount(), it can only be called +# with relative paths when the CWD is inside or at its root, so simulate that +os.chdir(temp_dir_abs) +vfs.mkdir("subdir") +vfs.mkdir("subdir/one") +print('listdir("/"):', sorted(i[0] for i in vfs.ilistdir("/"))) +print('listdir("."):', sorted(i[0] for i in vfs.ilistdir("."))) +print('getcwd() in {"", "/"}:', vfs.getcwd() in {"", "/"}) +print('chdir("subdir"):', vfs.chdir("subdir")) +print("getcwd():", vfs.getcwd()) +print('mkdir("two"):', vfs.mkdir("two")) +f = vfs.open("file.py", "w") +f.write("print('hello')") +f.close() +print('listdir("/"):', sorted(i[0] for i in vfs.ilistdir("/"))) +print('listdir("/subdir"):', sorted(i[0] for i in vfs.ilistdir("/subdir"))) +print('listdir("."):', sorted(i[0] for i in vfs.ilistdir("."))) +try: + f = vfs.open("/subdir/file.py", "r") + print(f.read()) + f.close() +except Exception as e: + print(e) +import sys + +sys.path.insert(0, "") +try: + import file + + print(file) +except Exception as e: + print(e) +del sys.path[0] +vfs.remove("file.py") +vfs.rmdir("two") +vfs.rmdir("/subdir/one") +vfs.chdir("/") +vfs.rmdir("/subdir") + +# done with vfs, restore CWD +os.chdir(curdir) + +# some integration tests with a mounted VFS +os.mount(os.VfsPosix(temp_dir_abs), "/mnt") +os.mkdir("/mnt/dir") +print('chdir("/mnt/dir"):', os.chdir("/mnt/dir")) +print("getcwd():", os.getcwd()) +print('chdir("/mnt"):', os.chdir("/mnt")) +print("getcwd():", os.getcwd()) +print('chdir("/"):', os.chdir("/")) +print("getcwd():", os.getcwd()) +print('chdir("/mnt/dir"):', os.chdir("/mnt/dir")) +print("getcwd():", os.getcwd()) +print('chdir(".."):', os.chdir("..")) +print("getcwd():", os.getcwd()) +os.rmdir("/mnt/dir") +os.umount("/mnt") + +# restore CWD +os.chdir(curdir) + +# rmdir +os.rmdir(temp_dir) +print(temp_dir in os.listdir()) diff --git a/tests/extmod/vfs_posix_paths.py.exp b/tests/extmod/vfs_posix_paths.py.exp new file mode 100644 index 0000000000000..ecc13222aaaeb --- /dev/null +++ b/tests/extmod/vfs_posix_paths.py.exp @@ -0,0 +1,23 @@ +listdir("/"): ['subdir'] +listdir("."): ['subdir'] +getcwd() in {"", "/"}: True +chdir("subdir"): None +getcwd(): /subdir +mkdir("two"): None +listdir("/"): ['subdir'] +listdir("/subdir"): ['file.py', 'one', 'two'] +listdir("."): ['file.py', 'one', 'two'] +print('hello') +hello + +chdir("/mnt/dir"): None +getcwd(): /mnt/dir +chdir("/mnt"): None +getcwd(): /mnt +chdir("/"): None +getcwd(): / +chdir("/mnt/dir"): None +getcwd(): /mnt/dir +chdir(".."): None +getcwd(): /mnt +False