123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- #
- #
- # 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.
- ##
- ## Experimental API, subject to change.
- #[
- See also:
- * `GetTempFileName` (on windows), refs https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea
- * `mkstemp` (posix), refs https://man7.org/linux/man-pages/man3/mkstemp.3.html
- ]#
- import std / [os, random]
- when defined(nimPreviewSlimSystem):
- import std/syncio
- const
- maxRetry = 10000
- letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- nimTempPathLength {.intdefine.} = 8
- when defined(windows):
- import std/winlean
- when defined(nimPreviewSlimSystem):
- import std/widestrs
- var O_RDWR {.importc: "_O_RDWR", header: "<fcntl.h>".}: cint
- proc c_fdopen(
- filehandle: cint,
- mode: cstring
- ): File {.importc: "_fdopen",header: "<stdio.h>".}
- proc open_osfhandle(osh: Handle, mode: cint): cint {.
- importc: "_open_osfhandle", header: "<io.h>".}
- proc close_osfandle(fd: cint): cint {.
- importc: "_close", header: "<io.h>".}
- else:
- import std/posix
- proc c_fdopen(
- filehandle: cint,
- mode: cstring
- ): File {.importc: "fdopen",header: "<stdio.h>".}
- proc safeOpen(filename: string): File =
- ## Open files exclusively; returns `nil` if the file already exists.
- # xxx this should be clarified; it doesn't in particular prevent other processes
- # from opening the file, at least currently.
- 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:
- if getLastError() == ERROR_FILE_EXISTS:
- return nil
- else:
- 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:
- # xxx we need a `proc toMode(a: FilePermission): Mode`, possibly by
- # exposing fusion/filepermissions.fromFilePermissions to stdlib; then we need
- # to expose a `perm` param so users can customize this (e.g. the temp file may
- # need execute permissions), and figure out how to make the API cross platform.
- let mode = Mode(S_IRUSR or S_IWUSR)
- let flags = posix.O_RDWR or posix.O_CREAT or posix.O_EXCL
- let fileHandle = posix.open(filename, flags, mode)
- if fileHandle == -1:
- if errno == EEXIST:
- # xxx `getLastError()` should be defined on posix too and resolve to `errno`?
- return nil
- else:
- raiseOSError(osLastError(), filename)
- result = c_fdopen(fileHandle, "w+")
- if result == nil:
- discard posix.close(fileHandle) # TODO handles failure when closing file
- raiseOSError(osLastError(), filename)
- type
- NimTempPathState = object
- state: Rand
- isInit: bool
- var nimTempPathState {.threadvar.}: NimTempPathState
- template randomPathName(length: Natural): string =
- var res = newString(length)
- if not nimTempPathState.isInit:
- nimTempPathState.isInit = true
- nimTempPathState.state = initRand()
- for i in 0 ..< length:
- res[i] = nimTempPathState.state.sample(letters)
- res
- proc getTempDirImpl(dir: string): string {.inline.} =
- result = dir
- if result.len == 0:
- result = getTempDir()
- proc genTempPath*(prefix, suffix: string, dir = ""): string =
- ## Generates a path name in `dir`.
- ##
- ## The path begins with `prefix` and ends with `suffix`.
- ##
- ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_).
- let dir = getTempDirImpl(dir)
- result = dir / (prefix & randomPathName(nimTempPathLength) & suffix)
- proc createTempFile*(prefix, suffix: string, dir = ""): tuple[cfile: File, path: string] =
- ## Creates a new temporary file in the directory `dir`.
- ##
- ## This generates a path name using `genTempPath(prefix, suffix, dir)` and
- ## returns a file handle to an open file and the path of that file, possibly after
- ## retrying to ensure it doesn't already exist.
- ##
- ## If failing to create a temporary file, `OSError` will be raised.
- ##
- ## .. note:: It is the caller's responsibility to close `result.cfile` and
- ## remove `result.file` when no longer needed.
- ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_).
- runnableExamples:
- import std/os
- doAssertRaises(OSError): discard createTempFile("", "", "nonexistent")
- let (cfile, path) = createTempFile("tmpprefix_", "_end.tmp")
- # path looks like: getTempDir() / "tmpprefix_FDCIRZA0_end.tmp"
- cfile.write "foo"
- cfile.setFilePos 0
- assert readAll(cfile) == "foo"
- close cfile
- assert readFile(path) == "foo"
- removeFile(path)
- # xxx why does above work without `cfile.flushFile` ?
- let dir = getTempDirImpl(dir)
- for i in 0 ..< maxRetry:
- result.path = genTempPath(prefix, suffix, dir)
- result.cfile = safeOpen(result.path)
- if result.cfile != nil:
- return
- raise newException(OSError, "Failed to create a temporary file under directory " & dir)
- proc createTempDir*(prefix, suffix: string, dir = ""): string =
- ## Creates a new temporary directory in the directory `dir`.
- ##
- ## This generates a dir name using `genTempPath(prefix, suffix, dir)`, creates
- ## the directory and returns it, possibly after retrying to ensure it doesn't
- ## already exist.
- ##
- ## If failing to create a temporary directory, `OSError` will be raised.
- ##
- ## .. note:: It is the caller's responsibility to remove the directory when no longer needed.
- ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_).
- runnableExamples:
- import std/os
- doAssertRaises(OSError): discard createTempDir("", "", "nonexistent")
- let dir = createTempDir("tmpprefix_", "_end")
- # dir looks like: getTempDir() / "tmpprefix_YEl9VuVj_end"
- assert dirExists(dir)
- removeDir(dir)
- let dir = getTempDirImpl(dir)
- for i in 0 ..< maxRetry:
- result = genTempPath(prefix, suffix, dir)
- if not existsOrCreateDir(result):
- return
- raise newException(OSError, "Failed to create a temporary directory under directory " & dir)
|