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

View file

@ -3,6 +3,8 @@
{.warning[UnusedImport]: off.}
from stdtest/specialpaths import buildDir
import std/[
# Core:
bitops, typetraits, lenientops, macros, volatile,
@ -87,3 +89,28 @@ block:
try: doAssert false
except Exception as e:
discard
block: # cpDir, cpFile, dirExists, fileExists, mkDir, mvDir, mvFile, rmDir, rmFile
const dname = buildDir/"D20210121T175016"
const subDir = dname/"sub"
const subDir2 = dname/"sub2"
const fpath = subDir/"f"
const fpath2 = subDir/"f2"
const fpath3 = subDir2/"f"
mkDir(subDir)
writeFile(fpath, "some text")
cpFile(fpath, fpath2)
doAssert fileExists(fpath2)
rmFile(fpath2)
cpDir(subDir, subDir2)
doAssert fileExists(fpath3)
rmDir(subDir2)
mvFile(fpath, fpath2)
doAssert not fileExists(fpath)
doAssert fileExists(fpath2)
mvFile(fpath2, fpath)
mvDir(subDir, subDir2)
doAssert not dirExists(subDir)
doAssert dirExists(subDir2)
mvDir(subDir2, subDir)
rmDir(dname)