From 12143d90c828bb4142751a6511ce790aac8aca51 Mon Sep 17 00:00:00 2001 From: Regis Caillaud <35006197+Clonkk@users.noreply.github.com> Date: Tue, 27 Oct 2020 08:52:40 +0100 Subject: [PATCH] Add C function dup and dup2 posix to system/io (#15675) * * Add handle to dup and dup2 posix as duplicate and duplicateTo in std/ioutils. * Added small test & changelog entry * Fixed import in tioutils removed when isMainModule * * Nest test inside block. Rename proc var -> let in captureStdout * Renamed tmpfile to iotuils.txt * Added block: # duplicate, duplicateTo * Improved docstring * Clean non-idiomatic code * Added runnable examples * rm 2 trailing space in expected output * Made syntax prettier * Runnable example: file in getTempDir() * Tmp -> Temp * Fixed runnableExamples on windows --- changelog.md | 3 +- lib/std/ioutils.nim | 85 +++++++++++++++++++++++++++++++++++++++ tests/stdlib/tioutils.nim | 49 ++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 lib/std/ioutils.nim create mode 100644 tests/stdlib/tioutils.nim diff --git a/changelog.md b/changelog.md index 8bd556e33..7f1034376 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,8 @@ - `prelude` now works with the JavaScript target. +- Added `ioutils` module containing `duplicate` and `duplicateTo` to duplicate `FileHandle` using C function `dup` and `dup2`. + ## Language changes @@ -18,4 +20,3 @@ ## Tool changes - diff --git a/lib/std/ioutils.nim b/lib/std/ioutils.nim new file mode 100644 index 000000000..bef61c54a --- /dev/null +++ b/lib/std/ioutils.nim @@ -0,0 +1,85 @@ +when defined(windows): + proc c_dup(oldfd: FileHandle): FileHandle {. + importc: "_dup", header: "".} + proc c_dup2(oldfd: FileHandle, newfd: FileHandle): cint {. + importc: "_dup2", header: "".} +else: + proc c_dup(oldfd: FileHandle): FileHandle{. + importc: "dup", header: "".} + proc c_dup2(oldfd: FileHandle, newfd: FileHandle): cint {. + importc: "dup2", header: "".} + +# when false: +# const SupportIoctlInheritCtl = (defined(linux) or defined(bsd)) and +# not defined(nimscript) +# when SupportIoctlInheritCtl: +# var +# FIOCLEX {.importc, header: "".}: cint +# FIONCLEX {.importc, header: "".}: cint + +## Also defined in std/posix and system/io +proc strerror(errnum: cint): cstring {.importc, header: "".} +when not defined(nimscript): + var errno {.importc, header: "".}: cint ## error variable + +template checkError(ret: cint) = + if ret == -1: + when not defined(nimscript): + raise newException(IOError, $strerror(errno)) + else: + doAssert(false) + +proc duplicate*(oldfd: FileHandle): FileHandle = + ##[ + Return a copy of the file handle `oldfd`. + After a successful return, both `FileHandle` may be used interchangeably. + They refer to the same open file description and share file offset and status flags. + Calls POSIX function `dup` on Posix platform and `_dup` on Windows + ]## + runnableExamples: + # stdoutDuplicate is a copy of stdout FileHandle that points to STDOUT + let stdoutDuplicate = duplicate(stdout.getFileHandle()) + # Writing to stdoutDuplicate will write to stdout + doAssert(stdoutDuplicate != stdout.getFileHandle()) + # On windows, opening a file from a FileHandle does not work + when not defined(windows): + var f : File + let res = open(f, stdoutDuplicate, mode=fmWrite) + let msg = "This is a test message that will be displayed ! \n" + f.write(msg) + # Output "Test" + f.close() + + result = c_dup(oldfd) + checkError(result) + +proc duplicateTo*(oldfd: FileHandle, newfd: FileHandle) = + ##[ + Perform the same task a `duplicate` but instead of using the lowest unused file descriptor + it uses the FileHandle` specified by `newfd`. + Calls POSIX function `dup2` on Posix platform and `_dup2` on Windows. + ]## + runnableExamples: + import os + # Redirect stdout to a file temporarily + let tmpFileName = getTempDir() / "hidden_output.txt" + let stdoutFileno = stdout.getFileHandle() + let stdoutDupFd = duplicate(stdoutFileno) + + # Create a new file + let tmpFile: File = open(tmpFileName, fmAppend) + let tmpFileFd: FileHandle = tmpFile.getFileHandle() + + # stdoutFileno now writes to tmpFile + duplicateTo(tmpFileFd, stdoutFileno) + echo "This is not displayed, but written to tmpFile instead !" + + # Close file & restore stdout + tmpFile.close() + duplicateTo(stdoutDupFd, stdoutFileno) + + # stdout is now restored ! + echo "This is displayed" + + let retValue = c_dup2(oldfd, newfd) + checkError(retValue) diff --git a/tests/stdlib/tioutils.nim b/tests/stdlib/tioutils.nim new file mode 100644 index 000000000..6854a7c1c --- /dev/null +++ b/tests/stdlib/tioutils.nim @@ -0,0 +1,49 @@ +discard """ + output: ''' +hello1 +hello1 +''' +""" + +import std/ioutils +import os +from stdtest/specialpaths import buildDir + +block: # duplicate, duplicateTo + let tmpFileName = buildDir / "tioutils.txt" + template captureStdout(body) : untyped = + let stdoutFileno = stdout.getFileHandle() + # Duplicate stoudFileno + let stdout_dupfd = duplicate(stdoutFileno) + # Create a new file + # You can use append strategy if you'd like + let tmpFile: File = open(tmpFileName, fmWrite) + # Get the FileHandle (the file descriptor) of your file + let tmpFileFd: FileHandle = tmpFile.getFileHandle() + # dup2 tmpFileFd to stdoutFileno -> writing to stdoutFileno now writes to tmpFile + duplicateTo(tmpFileFd, stdoutFileno) + body + # Force flush + tmpFile.flushFile() + # Close tmp + tmpFile.close() + # Read tmp + let ret = readFile(tmpFileName) + # Restore stdout + duplicateTo(stdout_dupfd, stdoutFileno) + ret + + proc duplicateStdout() = + var msg = "hello" + echo msg & "1" + + let s = captureStdout: + echo msg & "2" + + doAssert s == "hello2\n" + + discard tryRemoveFile(tmpFileName) + duplicateStdout() + # Check it works twice + duplicateStdout() + doAssert tryRemoveFile(tmpFileName)