diff --git a/changelog.md b/changelog.md index 0537b3d6a634b..abd2f8733d057 100644 --- a/changelog.md +++ b/changelog.md @@ -283,9 +283,12 @@ - Added `hasClosure` to `std/typetraits`. +- Added `std/tempfiles`. + - Added `genasts.genAst` that avoids the problems inherent with `quote do` and can be used as a replacement. + ## Language changes - `nimscript` now handles `except Exception as e`. diff --git a/lib/std/tempfiles.nim b/lib/std/tempfiles.nim new file mode 100644 index 0000000000000..1e1bbd403facd --- /dev/null +++ b/lib/std/tempfiles.nim @@ -0,0 +1,139 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2021 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module creates temporary files and directories. + +import os, random + + +const + maxRetry = 10000 + letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + nimTempPathLength {.intdefine.} = 8 + + +when defined(windows): + import winlean + + var O_RDWR {.importc: "_O_RDWR", header: "".}: cint + + proc c_fdopen( + filehandle: cint, + mode: cstring + ): File {.importc: "_fdopen",header: "".} + + proc open_osfhandle(osh: Handle, mode: cint): cint {. + importc: "_open_osfhandle", header: "".} + + proc close_osfandle(fd: cint): cint {. + importc: "_close", header: "".} +else: + import posix + + proc c_fdopen( + filehandle: cint, + mode: cstring + ): File {.importc: "fdopen",header: "".} + + +proc safeOpen(filename: string): File = + ## Open files exclusively. + when defined(windows): + let dwShareMode = FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE + let dwCreation = CREATE_NEW + let dwFlags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL + let handle = createFileW(newWideCString(filename), GENERIC_READ or GENERIC_WRITE, dwShareMode, + nil, dwCreation, dwFlags, Handle(0)) + + if handle == INVALID_HANDLE_VALUE: + raiseOSError(osLastError(), filename) + + let fileHandle = open_osfhandle(handle, O_RDWR) + if fileHandle == -1: + discard closeHandle(handle) + raiseOSError(osLastError(), filename) + + result = c_fdopen(fileHandle, "w+") + if result == nil: + discard close_osfandle(fileHandle) + raiseOSError(osLastError(), filename) + else: + let flags = posix.O_RDWR or posix.O_CREAT or posix.O_EXCL + + let fileHandle = posix.open(filename, flags) + if fileHandle == -1: + raiseOSError(osLastError(), filename) + + result = c_fdopen(fileHandle, "w+") + if result == nil: + discard posix.close(fileHandle) # TODO handles failure when closing file + raiseOSError(osLastError(), filename) + +template randomPathName(length: Natural): string = + var res = newString(length) + var state = initRand() + for i in 0 ..< length: + res[i] = state.sample(letters) + res + +proc createTempFile*(prefix, suffix: string, dir = ""): tuple[fd: File, path: string] = + ## `createTempFile` creates a new temporary file in the directory `dir`. + ## + ## If `dir` is the empty string, the default directory for temporary files + ## (`getTempDir `_) will be used. + ## The temporary file name begins with `prefix` and ends with `suffix`. + ## `createTempFile` returns a file handle to an open file and the path of that file. + ## + ## If failing to create a temporary file, `IOError` will be raised. + ## + ## .. note:: It is the caller's responsibility to remove the file when no longer needed. + ## + var dir = dir + if dir.len == 0: + dir = getTempDir() + + createDir(dir) + + for i in 0 ..< maxRetry: + result.path = dir / (prefix & randomPathName(nimTempPathLength) & suffix) + try: + result.fd = safeOpen(result.path) + except OSError: + continue + return + + raise newException(IOError, "Failed to create a temporary file under directory " & dir) + +proc createTempDir*(prefix, suffix: string, dir = ""): string = + ## `createTempDir` creates a new temporary directory in the directory `dir`. + ## + ## If `dir` is the empty string, the default directory for temporary files + ## (`getTempDir `_) will be used. + ## The temporary directory name begins with `prefix` and ends with `suffix`. + ## `createTempDir` returns the path of that temporary firectory. + ## + ## If failing to create a temporary directory, `IOError` will be raised. + ## + ## .. note:: It is the caller's responsibility to remove the directory when no longer needed. + ## + var dir = dir + if dir.len == 0: + dir = getTempDir() + + createDir(dir) + + for i in 0 ..< maxRetry: + result = dir / (prefix & randomPathName(nimTempPathLength) & suffix) + try: + if not existsOrCreateDir(result): + return + except OSError: + continue + + raise newException(IOError, "Failed to create a temporary directory under directory " & dir) diff --git a/tests/stdlib/ttempfiles.nim b/tests/stdlib/ttempfiles.nim new file mode 100644 index 0000000000000..29ef2fb2b74c8 --- /dev/null +++ b/tests/stdlib/ttempfiles.nim @@ -0,0 +1,17 @@ +import std/[os, tempfiles] + + +doAssert createTempDir("nim", "tmp") != createTempDir("nim", "tmp") + +block: + var t1 = createTempFile("nim", ".tmp") + var t2 = createTempFile("nim", ".tmp") + doAssert t1.path != t2.path + + write(t1.fd, "1234") + doAssert readAll(t2.fd) == "" + + close(t1.fd) + close(t2.fd) + removeFile(t1.path) + removeFile(t2.path)