stdlib/os: handle symlinks in copy/move functions (#16709)

* 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>
This commit is contained in:
Roman Inflianskas 2021-02-04 09:57:41 -08:00 • committed by GitHub
commit e9b360c5df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 321 additions and 106 deletions

View file

@ -26,6 +26,7 @@ Raises
# test os path creation, iteration, and deletion
import os, strutils, pathnorm
from stdtest/specialpaths import buildDir
block fileOperations:
let files = @["these.txt", "are.x", "testing.r", "files.q"]
@ -154,6 +155,112 @@ block fileOperations:
doAssert fileExists("../dest/a/file.txt")
removeDir("../dest")
# Symlink handling in `copyFile`, `copyFileWithPermissions`, `copyFileToDir`,
# `copyDir`, `copyDirWithPermissions`, `moveFile`, and `moveDir`.
block:
const symlinksAreHandled = not defined(windows)
const dname = buildDir/"D20210116T140629"
const subDir = dname/"sub"
const subDir2 = dname/"sub2"
const brokenSymlinkName = "D20210101T191320_BROKEN_SYMLINK"
const brokenSymlink = dname/brokenSymlinkName
const brokenSymlinkSrc = "D20210101T191320_nonexistant"
const brokenSymlinkCopy = brokenSymlink & "_COPY"
const brokenSymlinkInSubDir = subDir/brokenSymlinkName
const brokenSymlinkInSubDir2 = subDir2/brokenSymlinkName
createDir(subDir)
createSymlink(brokenSymlinkSrc, brokenSymlink)
# Test copyFile
when symlinksAreHandled:
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy)
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkFollow})
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkCopy)
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkAsIs})
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc
removeFile(brokenSymlinkCopy)
else:
doAssert not fileExists(brokenSymlinkCopy)
doAssertRaises(AssertionDefect):
copyFile(brokenSymlink, brokenSymlinkCopy,
{cfSymlinkAsIs, cfSymlinkFollow})
# Test copyFileWithPermissions
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})
# Test copyFileToDir
when symlinksAreHandled:
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir)
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir, {cfSymlinkFollow})
copyFileToDir(brokenSymlink, subDir, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkInSubDir)
copyFileToDir(brokenSymlink, subDir, {cfSymlinkAsIs})
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir) == brokenSymlinkSrc
removeFile(brokenSymlinkInSubDir)
else:
doAssert not fileExists(brokenSymlinkInSubDir)
createSymlink(brokenSymlinkSrc, brokenSymlinkInSubDir)
# Test copyDir
copyDir(subDir, subDir2)
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test copyDirWithPermissions
copyDirWithPermissions(subDir, subDir2)
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test moveFile
moveFile(brokenSymlink, brokenSymlinkCopy)
when not defined(windows):
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc
else:
doAssert symlinkExists(brokenSymlinkCopy)
removeFile(brokenSymlinkCopy)
# Test moveDir
moveDir(subDir, subDir2)
when not defined(windows):
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
else:
doAssert symlinkExists(brokenSymlinkInSubDir2)
removeDir(dname)
import times
block modificationTime:
# Test get/set modification times