This PR implements `expandSymlink` on Windows with POSIX readlink
semantics: it expands exactly one hop and returns the stored link target
without resolving the full chain.
The main design question was whether Windows symlink expansion should be
built on path-finalization APIs such as `GetFinalPathNameByHandleW`, or
on direct reparse-point inspection. Current `expandSymlink` is a
single-hop "what target is stored in this link object?" operation and
most of other ways to resolve symlinks on Windows actually try to answer
the "final true file location" question in various slightly-incompatible
ways.
The full final-path resolution on Windows is substantially more complex
than readlink and is planned as a follow-up.
## Implementation choice
Implements Windows `expandSymlink` by:
- opening the path with `FILE_FLAG_OPEN_REPARSE_POINT`
- calling `DeviceIoControl(FSCTL_GET_REPARSE_POINT)`
- parsing the reparse payload for `IO_REPARSE_TAG_SYMLINK` and
`IO_REPARSE_TAG_MOUNT_POINT`
- decoding the UTF-16 slice referenced by the payload
- returning the stored target
This is the right primitive for the API:
- does not depend on whole-path finalization
- works for both symlinks and junctions
- matches the existing Linux behaviour
`widestrs` changes allow using WideCString views without temporary
allocations.
Windows prohibits symlink creation without admin rights, so,
unfortunately, the tests are conditionally skipped by default. Manually
running `testament` in an admin console is required.
## Behaviour:
- One hop only
- Relative symlink targets are returned unchanged
- Absolute Windows targets are converted from stored NT-style prefixes
to usable Win32 forms when applicable
- Non-links, malformed payloads, and unsupported reparse tags raise
`OSError`
## Future work
Path canonicalization, i.e. "final true file location". Which is, BTW,
different from `absolutePath`, which works on paths only and doesn't hit
the underlying FS. So this needs to be an API extension.
I'd like to follow-up with this when I sort through the docs, for now
you can resolve symlinks in a loop.
---------
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
* make more standard libraries work with `nimPreviewSlimSystem`
* typo
* part two
* Delete specutils.nim
* fixes more tests
* more fixes
* fixes tests
* fixes three more tests
* add formatfloat import
* fix
* last
* Fix cannot create Windows directory in root
Fixes#20306, a regression bug with `createDir` caused by
`23e0160af283bb0bb573a86145e6c1c792780d49`.
The issue is that, if the path consists only of a drive and a single
directory (e.g. "Y:\nimcache2" in the original issue), then no
directories will be created. This works fine if there are multiple
directories (e.g. "Y:\nimcache2\test").
In the case of "Y:\nimcache2", `omitNext` in `createDir` is `false` on
the last condition in `createDir`. This means that the "nimcache2"
directory will not be created, and no exception will be raised.
Fixed by refactoring to use `parentDirs` iterator instead of iterating
over the string characters. Motivation is reduced code complexity.
Will not test the specific "C:\test" `createDir` case, since there is no
standard Windows drive with write permissions in the root. Creating a
custom drive-mapping to Windows Temp is a non-option. That could mess
up some users running the test.
Added `parentDirs` tests since they are lacking on for POSIX paths.
* Fix `createDir("")` causing error
The change to `createDir` caused `createDir("")` to raise an error,
where it previously didn't. Fixed so `createDir("")` does not fail, and
added test case.
* Add improved Windows UNC path support in std/os
Original issue: `std/os.createDir` tries to create every component of
the given path as a directory. The problem is that `createDir`
interprets every backslash/slash as a path separator. For a UNC path
this is incorrect. E.g. one UNC form is `\\Server\Volume\Path`. It's an
error to create the `\\Server` directory, as well as creating
`\\Server\Volume`.
Add `ntpath.nim` module with `splitDrive` proc. This implements UNC path
parsing as implemented in the Python `ntpath.py` module. The following
UNC forms are supported:
* `\\Server\Volume\Path`
* `\\?\Volume\Path`
* `\\?\UNC\Server\Volume\Path`
Improves support for UNC paths in various procs in `std/os`:
---
* pathnorm.addNormalizePath
* Issue: This had incomplete support for UNC paths
* The UNC prefix (first 2 characters of a UNC path) was assumed to
be exactly `\\`, but it can be `//` and `\/`, etc. as well
* Also, the UNC prefix must be normalized to the `dirSep` argument
of `addNormalizePath`
* Resolution: Changed to account for different UNC prefixes, and
normalizing the prefixes according to `dirSep`
* Affected procs that get tests: `relativePath`, `joinPath`
* Issue: The server/volume part of UNC paths can be stripped when
normalizing `..` path components
* This error should be negligable, so ignoring this
* splitPath
* Now make sure the UNC drive is not split; return the UNC drive as
`head` if the UNC drive is the only component of the path
* Consequently fixes `extractFilename`, `lastPathPart`
* parentDir / `/../`
* Strip away drive before working on the path, prepending the drive
after all work is done - prevents stripping UNC components
* Return empty string if drive component is the only component; this
is the behavior for POSIX paths as well
* Alternative implementation: Just call something like
`pathnorm.normalizePath(path & "/..")` for the whole proc - maybe
too big of a change
* tailDir
* If drive is present in path, just split that from path and return
path
* parentDirs iterator
* Uses `parentDir` for going backwards
* When going forwards, first `splitDrive`, yield the drive field, and
then iterate over path field as normal
* splitFile
* Make sure path parsing stops at end of drive component
* createDir
* Fixed by skipping drive part before creating directories
* Alternative implementation: use `parentDirs` iterator instead of
iterating over characters
* Consequence is that it will try to create the root directory
* isRootDir
* Changed to treat UNC drive alone as root (e.g. "//?/c:" is root)
* This change prevents the empty string being yielded by the
`parentDirs` iterator with `fromRoot = false`
* Internal `sameRoot`
* The "root" refers to the drive, so `splitDrive` can be used here
This adds UNC path support to all procs that could use it in std/os. I
don't think any more work has to be done to support UNC paths. For the
future, I believe the path handling code can be refactored due to
duplicate code. There are multiple ways of manipulating paths, such as
manually searching string for path separator and also having a path
normalizer (pathnorm.nim). If all path manipulation used `pathnorm.nim`,
and path component splitting used `parentDirs` iterator, then a lot of
code could be removed.
Tests
---
Added test file for `pathnorm.nim` and `ntpath.nim`.
`pathnorm.normalizePath` has no tests, so I'm adding a few unit tests.
`ntpath.nim` contains tests copied from Python's test suite.
Added integration tests to `tos.nim` that tests UNC paths.
Removed incorrect `relativePath` runnableExamples from being tested on Windows:
---
`relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"`
This is incorrect on Windows because the `/` and `//` are not the same
root. `/` (or `\`) is expanded to the drive in the current working
directory (e.g. `C:\`). `//` (or `\\`), however, are the first two
characters of a UNC path. The following holds true for normal Windows
installations:
* `dirExists("/Users") != dirExists("//Users")`
* `dirExists("\\Users") != dirExists("\\\\Users")`
Fixes#19103
Questions:
---
* Should the `splitDrive` proc be in `os.nim` instead with copyright
notice above the proc?
* Is it fine to put most of the new tests into the `runnableExamples`
section of the procs in std/os?
* [skipci] Apply suggestions from code review
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
* [skip ci] Update lib/pure/os.nim
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
* Move runnableExamples tests in os.nim to tos.nim
* tests/topt_no_cursor: Change from using splitFile to splitDrive
`splitFile` can no longer be used in the test, because it generates
different ARC code on Windows and Linux. This replaces `splitFile` with
`splitDrive`, because it generates same ARC code on Windows and Linux,
and returns a tuple. I assume the test wants a proc that returns a
tuple.
* Drop copyright attribute to Python
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
* Remove unnecessary environment tracking
* try to fix windows
* fix delEnv
* make putEnv work on windows even with empty values; improve tests: add tests, add js, vm testing
* [skip ci] fix changelog
Co-authored-by: Caden Haustein <code@brightlysalty.33mail.com>
* Replace calls to C `putenv` with C `setenv` to remove possible memory leaks
* Add test of correct behaviour on invalid input
* Fix style in tests/stdlib/tos.nim
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Update tests/stdlib/tos.nim
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Update tests/stdlib/tos.nim
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Add comment with bug number to tests/stdlib/tos.nim
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Fix possible msvc arch issues
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Revert "followup #17398: `getTempDir`, `getConfigDir` now do not have trailing DirSep (#17402)"
This reverts commit 2356d0603f.
* Revert "fix #17393 getHomeDir and expandTilde should not include trailing `/` (#17398)"
This reverts commit bebf2ce24a.
* fix test
* stdlib/os: handle symlinks in copy/move functions
- Added optional `options` argument to `copyFile`, `copyFileToDir`, and
`copyFileWithPermissions`. By default, symlinks are followed (copy files
symlinks point to).
- `copyDir` and `copyDirWithPermissions` copy symlinks as symlinks (instead of
skipping them as it was before).
- `moveFile` and `moveDir` move symlinks as symlinks (instead of skipping them
sometimes as it was before).
- Added optional `followSymlinks` argument to `setFilePermissions`.
See also: https://github.com/nim-lang/RFCs/issues/319
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Address comments in #16709
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* Address comments in #16709 (second iteration)
Skip symlinks on Windows.
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
* * relativePath(foo) now works
* relativePath(rel, abs) and relativePath(abs, rel) now work (fixes#13222)
* relativePath, absolutePath, getCurrentDir now available in more targets (eg: vm, nodejs etc)
* fix bug: isAbsolutePath now works with -d:js; add tests
* workaround https://github.com/nim-lang/Nim/issues/13469
* remove `relativePath(path)` overload for now
* add back changelog after rebase
* On windows, os.relativePath returns path as is when roots are different
* Implement os.sameRoot without windows API
* Fix compile error when compiling lib/nimhcr.nim
* Fix compile error when compiling lib/nimhcr.nim on Windows
* s/exitStatus(...)/exitStatusLikeShell(...)/
* fix#10273 execShellCmd now returns nonzero when child exits with signal
* test case for #10249 and explanation for the bug
* fix test failure
* add tests/nim.cfg