-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stdlib/os: handle symlinks in copy/move functions #16709
stdlib/os: handle symlinks in copy/move functions #16709
Conversation
b11aedd
to
1b2eb5e
Compare
Well, we get:
What's the way to fix this? I'm not qualified enough yet to fix this... |
1b2eb5e
to
92621c8
Compare
92621c8
to
2199cf6
Compare
2199cf6
to
2476982
Compare
follow the error messages in logs; it shows it fails with then run that locally, it fails; then see why it fails: it's because nimsuggest/nimsuggest.nim.cfg contains then run the fix: add gcsafe annotations for decl+impl for createSymlink, expandSymlink
|
Check that those still work on NimScript because they currently do. I really appreciate the effort, if you are going to improve it remember that Nim can not differentiate a file from a folder. 🤷 |
How other languages handle symlinks in copyDir analogsPython
Docs: https://docs.python.org/3/library/shutil.html#shutil.copytree RustThere is no SO question: https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust C++
Docs: https://en.cppreference.com/w/cpp/filesystem/copy_options GoThere is no A most popular library for Go copies symlinks instead of following them. Sources: https://github.com/otiai10/copy/blob/1dfb4e4bf632edd08bc1a853160ad03ff18e68b5/options.go#L66 Julia
Docs: https://docs.julialang.org/en/v1/base/file/#Base.Filesystem.cp ConclusionsSome languages don't have My thoughtsI'm tempted to make it the same way as C++ does: follow symlinks by default and to pass the Reasoning:
So, what do you think? |
agreed here, and also it's a reasonable default for copyFile
I agree. But I'd prefer using a type CopyOptions* = object
followSymlinks*: bool
proc initCopyOptions(): CopyOptions = ... for same rationale as in jsonutils #15133 (comment) eg: let opt = initCopyOptions()
opt.follow
copyDir(src, dst, opt)
that's the only part i disagree with, and I disagree strongly. following symlinks should be opt in when it comes to copyDir which is recursive. Imagine you call:
copyDir "foo", "foo2" and then there are other problems too, eg handling internal symlinks etc (these are in fact common), or even cyclic links. |
@juancarlospaco, you wrote:
I don't think so, I've just tried it:
|
@rominf for nimscript, try instead cpFile, cpDir (https://nim-lang.github.io/Nim/nimscript.html) no need to add API's to std/nimscript though (the preferred way is for nims to just extend |
efc9a3c
to
c665b26
Compare
c665b26
to
14574c7
Compare
1e3441d
to
bfa077a
Compare
What?! No, we don't produce output on stderr in undocumented ways. |
@Araq Ok, I understand your point. How do you propose to solve this? |
Pretend for the time being that Windows has no symlinks and fix copyDir for Posix only. |
- 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: nim-lang/RFCs#319 Co-authored-by: Timothee Cour <[email protected]>
Co-authored-by: Timothee Cour <[email protected]>
Skip symlinks on Windows.
fe11588
to
c46126b
Compare
Skip symlinks on Windows.
c46126b
to
a9a6847
Compare
Skip symlinks on Windows.
a9a6847
to
c704902
Compare
Skip symlinks on Windows.
c704902
to
90db1cd
Compare
Skip symlinks on Windows.
90db1cd
to
c1301f7
Compare
Merging this now, will clean it up a bit later. |
- On non-Windows OSes, `copyDir` and `copyDirWithPermissions` copy symlinks as | ||
symlinks (instead of skipping them as it was before); on Windows symlinks are | ||
skipped. | ||
- On non-Windows OSes, `moveFile` and `moveDir` move symlinks as symlinks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rominf how about on windows, what is the behavior now? (even if it hasn't changed); when reading the changelog, one might wonder
|
||
proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noWeirdTarget.} = | ||
## Moves a directory from `source` to `dest`. | ||
## | ||
## Symlinks are not followed: if `source` contains symlinks, they themself are | ||
## moved, not their target. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the behavior for windows when source is a symlink? noop? remove source? something else?
except: | ||
if not ignorePermissionErrors: | ||
raise | ||
|
||
proc copyDirWithPermissions*(source, dest: string, | ||
ignorePermissionErrors = true) {.rtl, extern: "nos$1", | ||
tags: [WriteIOEffect, ReadIOEffect], benign, noWeirdTarget.} = | ||
ignorePermissionErrors = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in future work, copyDirWithPermissions
and copyDir
should be refactored with a common implementation copyDirImpl
to avoid the duplication (but see also timotheecour#570 which suggests using a single copyDir with options, which would make copyDirImpl
un-necessary)
## Copies a directory from `source` to `dest`. | ||
## | ||
## On non-Windows OSes, symlinks are copied as symlinks. On Windows, symlinks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- pre-existing but what's the behavior when dest already exists? (on each OS)
- also, we should document that dest.parentDir doesn't need to exist
mkDir(subDir) | ||
writeFile(fpath, "some text") | ||
cpFile(fpath, fpath2) | ||
doAssert fileExists(fpath2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add:
doAssert fpath2.readFile == "some text"
when symlinksAreHandled: | ||
doAssertRaises(OSError): | ||
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy) | ||
doAssertRaises(OSError): | ||
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy, | ||
options = {cfSymlinkFollow}) | ||
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy, | ||
options = {cfSymlinkIgnore}) | ||
doAssert not fileExists(brokenSymlinkCopy) | ||
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy, | ||
options = {cfSymlinkAsIs}) | ||
when symlinksAreHandled: | ||
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc | ||
removeFile(brokenSymlinkCopy) | ||
else: | ||
doAssert not fileExists(brokenSymlinkCopy) | ||
doAssertRaises(AssertionDefect): | ||
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy, | ||
options = {cfSymlinkAsIs, cfSymlinkFollow}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use a template to avoid this entire block being duplicated:
eg:
template test(algo) =
...
test copyFile
test copyFileWithPermissions
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy, | ||
options = {cfSymlinkAsIs, cfSymlinkFollow}) | ||
|
||
# Test copyFileToDir |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise, there should be a way to avoid duplication even for copyFileToDir
block
|
||
# Test moveFile | ||
moveFile(brokenSymlink, brokenSymlinkCopy) | ||
when not defined(windows): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not: when symlinksAreHandled:
?
|
||
# Test moveDir | ||
moveDir(subDir, subDir2) | ||
when not defined(windows): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, when symlinksAreHandled:
?
@rominf excellent change, and also the added tests are really good. I added some post-review comments for future PRs; IMO we should still support symlinks on windows, but deferring this to a future PR makes sense as this PR was already complex enough |
* 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: nim-lang/RFCs#319 Co-authored-by: Timothee Cour <[email protected]> * Address comments in nim-lang#16709 Co-authored-by: Timothee Cour <[email protected]> * Address comments in nim-lang#16709 (second iteration) Skip symlinks on Windows. Co-authored-by: Timothee Cour <[email protected]>
options
argument tocopyFile
,copyFileToDir
, andcopyFileWithPermissions
. By default, symlinks are followed (copy filessymlinks point to). On Windows,
options == {cfSymlinkAsIs}
do not affect.copyDir
andcopyDirWithPermissions
copy symlinks as they are (instead ofskipping them as it was before). On Windows, symlinks are followed.
moveFile
andmoveDir
move symlinks as they are (instead of skipping themsometimes as it was before).
followSymlinks
argument tosetFilePermissions
.See also: nim-lang/RFCs#319