summary refs log tree commit diff stats
path: root/lib/pure/os.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/os.nim')
-rw-r--r--lib/pure/os.nim123
1 files changed, 77 insertions, 46 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index b5eead91d..f53abe81d 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -1,7 +1,7 @@
 #
 #
 #            Nim's Runtime Library
-#        (c) Copyright 2014 Andreas Rumpf
+#        (c) Copyright 2015 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -185,7 +185,7 @@ const
 proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
   ## Retrieves the operating system's error flag, ``errno``.
   ## On Windows ``GetLastError`` is checked before ``errno``.
-  ## Returns "" if no error occured.
+  ## Returns "" if no error occurred.
   ##
   ## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc.
 
@@ -211,13 +211,13 @@ proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
 {.push warning[deprecated]: off.}
 proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
                                        deprecated.} =
-  ## raises an EOS exception with the given message ``msg``.
+  ## raises an OSError exception with the given message ``msg``.
   ## If ``msg == ""``, the operating system's error flag
   ## (``errno``) is converted to a readable error message. On Windows
   ## ``GetLastError`` is checked before ``errno``.
   ## If no error flag is set, the message ``unknown OS error`` is used.
   ##
-  ## **Deprecated since version 0.9.4**: use the other ``OSError`` proc.
+  ## **Deprecated since version 0.9.4**: use the other ``raiseOSError`` proc.
   if len(msg) == 0:
     var m = osErrorMsg()
     raise newException(OSError, if m.len > 0: m else: "unknown OS error")
@@ -234,7 +234,7 @@ proc `$`*(err: OSErrorCode): string {.borrow.}
 proc osErrorMsg*(errorCode: OSErrorCode): string =
   ## Converts an OS error code into a human readable string.
   ##
-  ## The error code can be retrieved using the ``OSLastError`` proc.
+  ## The error code can be retrieved using the ``osLastError`` proc.
   ##
   ## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be
   ## returned.
@@ -262,10 +262,10 @@ proc osErrorMsg*(errorCode: OSErrorCode): string =
       result = $os.strerror(errorCode.int32)
 
 proc raiseOSError*(errorCode: OSErrorCode) =
-  ## Raises an ``EOS`` exception. The ``errorCode`` will determine the
-  ## message, ``OSErrorMsg`` will be used to get this message.
+  ## Raises an ``OSError`` exception. The ``errorCode`` will determine the
+  ## message, ``osErrorMsg`` will be used to get this message.
   ##
-  ## The error code can be retrieved using the ``OSLastError`` proc.
+  ## The error code can be retrieved using the ``osLastError`` proc.
   ##
   ## If the error code is ``0`` or an error message could not be retrieved,
   ## the message ``unknown OS error`` will be used.
@@ -502,7 +502,7 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
       raiseOSError(osLastError())
 
 proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
-  ## Sets the `current working directory`:idx:; `EOS` is raised if
+  ## Sets the `current working directory`:idx:; `OSError` is raised if
   ## `newDir` cannot been set.
   when defined(Windows):
     when useWinUnicode:
@@ -575,7 +575,7 @@ proc `/` * (head, tail: string): string {.noSideEffect.} =
 proc splitPath*(path: string): tuple[head, tail: string] {.
   noSideEffect, rtl, extern: "nos$1".} =
   ## Splits a directory into (head, tail), so that
-  ## ``joinPath(head, tail) == path``.
+  ## ``head / tail == path`` (except for edge cases like "/usr").
   ##
   ## Examples:
   ##
@@ -599,7 +599,7 @@ proc splitPath*(path: string): tuple[head, tail: string] {.
 
 proc parentDirPos(path: string): int =
   var q = 1
-  if path[len(path)-1] in {DirSep, AltSep}: q = 2
+  if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
   for i in countdown(len(path)-q, 0):
     if path[i] in {DirSep, AltSep}: return i
   result = -1
@@ -715,7 +715,7 @@ proc extractFilename*(path: string): string {.
 
 proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
   tags: [ReadDirEffect].} =
-  ## Returns the full path of `filename`, raises EOS in case of an error.
+  ## Returns the full path of `filename`, raises OSError in case of an error.
   when defined(windows):
     const bufsize = 3072'i32
     when useWinUnicode:
@@ -884,7 +884,7 @@ proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
   close(b)
 
 type
-  TFilePermission* = enum  ## file access permission; modelled after UNIX
+  FilePermission* = enum   ## file access permission; modelled after UNIX
     fpUserExec,            ## execute access for the file owner
     fpUserWrite,           ## write access for the file owner
     fpUserRead,            ## read access for the file owner
@@ -895,7 +895,9 @@ type
     fpOthersWrite,         ## write access for others
     fpOthersRead           ## read access for others
 
-proc getFilePermissions*(filename: string): set[TFilePermission] {.
+{.deprecated: [TFilePermission: FilePermission].}
+
+proc getFilePermissions*(filename: string): set[FilePermission] {.
   rtl, extern: "nos$1", tags: [ReadDirEffect].} =
   ## retrieves file permissions for `filename`. `OSError` is raised in case of
   ## an error. On Windows, only the ``readonly`` flag is checked, every other
@@ -927,7 +929,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {.
     else:
       result = {fpUserExec..fpOthersRead}
   
-proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {.
+proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
   rtl, extern: "nos$1", tags: [WriteDirEffect].} =
   ## sets the file permissions for `filename`. `OSError` is raised in case of
   ## an error. On Windows, only the ``readonly`` flag is changed, depending on
@@ -967,7 +969,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
   tags: [ReadIOEffect, WriteIOEffect].} =
   ## Copies a file from `source` to `dest`.
   ##
-  ## If this fails, `EOS` is raised. On the Windows platform this proc will
+  ## If this fails, `OSError` is raised. On the Windows platform this proc will
   ## copy the source file's attributes into dest. On other platforms you need
   ## to use `getFilePermissions() <#getFilePermissions>`_ and
   ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use
@@ -1007,9 +1009,17 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
 
 proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
   tags: [ReadIOEffect, WriteIOEffect].} =
-  ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised.
-  if c_rename(source, dest) != 0'i32:
-    raise newException(OSError, $strerror(errno))
+  ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
+  when defined(Windows):
+    when useWinUnicode:
+      let s = newWideCString(source)
+      let d = newWideCString(dest)
+      if moveFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
+    else:
+      if moveFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
+  else:
+    if c_rename(source, dest) != 0'i32:
+      raise newException(OSError, $strerror(errno))
 
 when not declared(ENOENT) and not defined(Windows):
   when NoFakeVars:
@@ -1028,7 +1038,7 @@ when defined(Windows):
       setFileAttributesA(file, attrs)
 
 proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
-  ## Removes the `file`. If this fails, `EOS` is raised. This does not fail
+  ## Removes the `file`. If this fails, `OSError` is raised. This does not fail
   ## if the file never existed in the first place.
   ## On Windows, ignores the read-only attribute.
   when defined(Windows):
@@ -1072,8 +1082,12 @@ when defined(windows):
   # because we support Windows GUI applications, things get really
   # messy here...
   when useWinUnicode:
-    proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
-      importc: "wcschr", header: "<string.h>".}
+    when defined(cpp):
+      proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
+        importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "<string.h>".}
+    else:
+      proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
+        importc: "wcschr", header: "<string.h>".}
   else:
     proc strEnd(cstr: cstring, c = 0'i32): cstring {.
       importc: "strchr", header: "<string.h>".}
@@ -1085,7 +1099,7 @@ when defined(windows):
         var
           env = getEnvironmentStringsW()
           e = env
-        if e == nil: return # an error occured
+        if e == nil: return # an error occurred
         while true:
           var eend = strEnd(e)
           add(environment, $e)
@@ -1096,7 +1110,7 @@ when defined(windows):
         var
           env = getEnvironmentStringsA()
           e = env
-        if e == nil: return # an error occured
+        if e == nil: return # an error occurred
         while true:
           var eend = strEnd(e)
           add(environment, $e)
@@ -1107,8 +1121,8 @@ when defined(windows):
 
 else:
   const
-    useNSGetEnviron = defined(macosx) and
-      (defined(createNimRtl) or defined(useNimRtl))
+    useNSGetEnviron = defined(macosx)
+
   when useNSGetEnviron:
     # From the manual:
     # Shared libraries and bundles don't have direct access to environ,
@@ -1168,7 +1182,7 @@ proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
   ## If an error occurs, `EInvalidEnvVar` is raised.
 
   # Note: by storing the string in the environment sequence,
-  # we gurantee that we don't free the memory before the program
+  # we guarantee that we don't free the memory before the program
   # ends (this is needed for POSIX compliance). It is also needed so that
   # the process itself may access its modified environment variables!
   var indx = findEnvVar(key)
@@ -1231,13 +1245,15 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
     globfree(addr(f))
 
 type
-  TPathComponent* = enum  ## Enumeration specifying a path component.
+  PathComponent* = enum   ## Enumeration specifying a path component.
     pcFile,               ## path refers to a file
     pcLinkToFile,         ## path refers to a symbolic link to a file
     pcDir,                ## path refers to a directory
     pcLinkToDir           ## path refers to a symbolic link to a directory
 
-iterator walkDir*(dir: string): tuple[kind: TPathComponent, path: string] {.
+{.deprecated: [TPathComponent: PathComponent].}
+
+iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {.
   tags: [ReadDirEffect].} =
   ## walks over the directory `dir` and yields for each directory or file in
   ## `dir`. The component type and full path for each item is returned.
@@ -1283,8 +1299,16 @@ iterator walkDir*(dir: string): tuple[kind: TPathComponent, path: string] {.
         if y != "." and y != "..":
           var s: TStat
           y = dir / y
-          if lstat(y, s) < 0'i32: break
           var k = pcFile
+
+          when defined(linux) or defined(macosx) or defined(bsd):
+            if x.d_type != DT_UNKNOWN:
+              if x.d_type == DT_DIR: k = pcDir
+              if x.d_type == DT_LNK: k = succ(k)
+              yield (k, y)
+              continue
+
+          if lstat(y, s) < 0'i32: break
           if S_ISDIR(s.st_mode): k = pcDir
           if S_ISLNK(s.st_mode): k = succ(k)
           yield (k, y)
@@ -1331,11 +1355,11 @@ proc rawRemoveDir(dir: string) =
     if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
 
 proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
-  WriteDirEffect, ReadDirEffect].} =
+  WriteDirEffect, ReadDirEffect], benign.} =
   ## Removes the directory `dir` including all subdirectories and files
   ## in `dir` (recursively).
   ##
-  ## If this fails, `EOS` is raised. This does not fail if the directory never
+  ## If this fails, `OSError` is raised. This does not fail if the directory never
   ## existed in the first place.
   for kind, path in walkDir(dir):
     case kind
@@ -1362,11 +1386,11 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
   ## Creates the `directory`:idx: `dir`.
   ##
   ## The directory may contain several subdirectories that do not exist yet.
-  ## The full path is created. If this fails, `EOS` is raised. It does **not**
+  ## The full path is created. If this fails, `OSError` is raised. It does **not**
   ## fail if the path already exists because for most usages this does not
   ## indicate an error.
   var omitNext = false
-  when defined(doslike):
+  when doslike:
     omitNext = isAbsolute(dir)
   for i in 1.. dir.len-1:
     if dir[i] in {DirSep, AltSep}:
@@ -1377,10 +1401,10 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
   rawCreateDir(dir)
 
 proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
-  tags: [WriteIOEffect, ReadIOEffect].} =
+  tags: [WriteIOEffect, ReadIOEffect], benign.} =
   ## Copies a directory from `source` to `dest`.
   ##
-  ## If this fails, `EOS` is raised. On the Windows platform this proc will
+  ## If this fails, `OSError` is raised. On the Windows platform this proc will
   ## copy the attributes from `source` into `dest`. On other platforms created
   ## files and directories will inherit the default permissions of a newly
   ## created file/directory for the user. To preserve attributes recursively on
@@ -1438,7 +1462,7 @@ proc createHardlink*(src, dest: string) =
 proc parseCmdLine*(c: string): seq[string] {.
   noSideEffect, rtl, extern: "nos$1".} =
   ## Splits a command line into several components;
-  ## This proc is only occassionally useful, better use the `parseopt` module.
+  ## This proc is only occasionally useful, better use the `parseopt` module.
   ##
   ## On Windows, it uses the following parsing rules
   ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
@@ -1550,10 +1574,10 @@ proc copyFileWithPermissions*(source, dest: string,
 
 proc copyDirWithPermissions*(source, dest: string,
     ignorePermissionErrors = true) {.rtl, extern: "nos$1",
-    tags: [WriteIOEffect, ReadIOEffect].} =
+    tags: [WriteIOEffect, ReadIOEffect], benign.} =
   ## Copies a directory from `source` to `dest` preserving file permissions.
   ##
-  ## If this fails, `EOS` is raised. This is a wrapper proc around `copyDir()
+  ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
   ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_
   ## on non Windows platforms. On Windows this proc is just a wrapper for
   ## `copyDir() <#copyDir>`_ since that proc already copies attributes.
@@ -1580,7 +1604,7 @@ proc copyDirWithPermissions*(source, dest: string,
     else: discard
 
 proc inclFilePermissions*(filename: string,
-                          permissions: set[TFilePermission]) {.
+                          permissions: set[FilePermission]) {.
   rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
   ## a convenience procedure for:
   ##
@@ -1589,7 +1613,7 @@ proc inclFilePermissions*(filename: string,
   setFilePermissions(filename, getFilePermissions(filename)+permissions)
 
 proc exclFilePermissions*(filename: string,
-                          permissions: set[TFilePermission]) {.
+                          permissions: set[FilePermission]) {.
   rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} =
   ## a convenience procedure for:
   ##
@@ -1825,7 +1849,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
 
 proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
   tags: [ReadIOEffect].} =
-  ## returns the file size of `file`. Can raise ``EOS``.
+  ## returns the file size of `file`. Can raise ``OSError``.
   when defined(windows):
     var a: TWIN32_FIND_DATA
     var resA = findFirstFile(file, a)
@@ -1839,16 +1863,21 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
       close(f)
     else: raiseOSError(osLastError())
 
+proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].}
+
 proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} =
   ## Searches for `exe` in the current working directory and then
   ## in directories listed in the ``PATH`` environment variable.
   ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe`
-  ## is added an ``.exe`` file extension if it has no extension.
+  ## is added the `ExeExt <#ExeExt>`_ file extension if it has none.
   result = addFileExt(exe, os.ExeExt)
   if existsFile(result): return
   var path = string(os.getEnv("PATH"))
   for candidate in split(path, PathSep):
-    var x = candidate / result
+    when defined(windows):
+      var x = candidate / result
+    else:
+      var x = expandTilde(candidate) / result
     if existsFile(x): return x
   result = ""
 
@@ -1885,9 +1914,9 @@ type
   FileInfo* = object
     ## Contains information associated with a file object.
     id*: tuple[device: DeviceId, file: FileId] # Device and file id.
-    kind*: TPathComponent # Kind of file object - directory, symlink, etc.
+    kind*: PathComponent # Kind of file object - directory, symlink, etc.
     size*: BiggestInt # Size of file.
-    permissions*: set[TFilePermission] # File permissions
+    permissions*: set[FilePermission] # File permissions
     linkCount*: BiggestInt # Number of hard links the file object has.
     lastAccessTime*: Time # Time file was last accessed.
     lastWriteTime*: Time # Time file was last modified/written to.
@@ -1973,6 +2002,8 @@ proc getFileInfo*(handle: FileHandle): FileInfo =
     rawToFormalFileInfo(rawInfo, result)
 
 proc getFileInfo*(file: File): FileInfo =
+  if file.isNil:
+    raise newException(IOError, "File is nil")
   result = getFileInfo(file.getFileHandle())
 
 proc getFileInfo*(path: string, followSymlink = true): FileInfo =