# # # Nimrod's Runtime Library # (c) Copyright 2010 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module contains basic operating system facilities like ## retrieving environment variables, reading command line arguments, ## working with directories, running shell commands, etc. {.deadCodeElim: on.} {.push debugger: off.} import strutils, times when defined(windows): import winlean elif defined(posix): import posix else: {.error: "OS module not ported to your operating system!".} include "system/ansi_c" const doslike = defined(windows) or defined(OS2) or defined(DOS) # DOS-like filesystem when defined(Nimdoc): # only for proper documentation: const CurDir* = '.' ## The constant string used by the operating system to refer to the ## current directory. ## ## For example: '.' for POSIX or ':' for the classic Macintosh. ParDir* = ".." ## The constant string used by the operating system to refer to the parent ## directory. ## ## For example: ".." for POSIX or "::" for the classic Macintosh. DirSep* = '/' ## The character used by the operating system to separate pathname ## components, for example, '/' for POSIX or ':' for the classic ## Macintosh. AltSep* = '/' ## An alternative character used by the operating system to separate ## pathname components, or the same as `DirSep` if only one separator ## character exists. This is set to '/' on Windows systems where `DirSep` ## is a backslash. PathSep* = ':' ## The character conventionally used by the operating system to separate ## search patch components (as in PATH), such as ':' for POSIX or ';' for ## Windows. FileSystemCaseSensitive* = True ## True if the file system is case sensitive, false otherwise. Used by ## `cmpPaths` to compare filenames properly. ExeExt* = "" ## The file extension of native executables. For example: ## "" for POSIX, "exe" on Windows. ScriptExt* = "" ## The file extension of a script file. For example: "" for POSIX, ## "bat" on Windows. elif defined(macos): const curdir* = ':' pardir* = "::" dirsep* = ':' altsep* = dirsep pathsep* = ',' FileSystemCaseSensitive* = false ExeExt* = "" ScriptExt* = "" # MacOS paths # =========== # MacOS directory separator is a colon ":" which is the only character not # allowed in filenames. # # A path containing no colon or which begins with a colon is a partial path. # E.g. ":kalle:petter" ":kalle" "kalle" # # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" # When generating paths, one is safe if one ensures that all partial paths # begin with a colon, and all full paths end with a colon. # In full paths the first name (e g HD above) is the name of a mounted # volume. # These names are not unique, because, for instance, two diskettes with the # same names could be inserted. This means that paths on MacOS are not # waterproof. In case of equal names the first volume found will do. # Two colons "::" are the relative path to the parent. Three is to the # grandparent etc. elif doslike: const curdir* = '.' pardir* = ".." dirsep* = '\\' # seperator within paths altsep* = '/' pathSep* = ';' # seperator between paths FileSystemCaseSensitive* = false ExeExt* = "exe" ScriptExt* = "bat" elif defined(PalmOS) or defined(MorphOS): const dirsep* = '/' altsep* = dirsep PathSep* = ';' pardir* = ".." FileSystemCaseSensitive* = false ExeExt* = "" ScriptExt* = "" elif defined(RISCOS): const dirsep* = '.' altsep* = '.' pardir* = ".." # is this correct? pathSep* = ',' FileSystemCaseSensitive* = true ExeExt* = "" ScriptExt* = "" else: # UNIX-like operating system const curdir* = '.' pardir* = ".." dirsep* = '/' altsep* = dirsep pathSep* = ':' FileSystemCaseSensitive* = true ExeExt* = "" ScriptExt* = "" const ExtSep* = '.' ## The character which separates the base filename from the extension; ## for example, the '.' in ``os.nim``. # procs dealing with command line arguments: proc paramCount*(): int ## Returns the number of command line arguments given to the ## application. proc paramStr*(i: int): string ## Returns the `i`-th command line arguments given to the ## application. ## ## `i` should be in the range `1..paramCount()`, else ## the `EOutOfIndex` exception is raised. proc OSError*(msg: string = "") {.noinline.} = ## raises an EOS 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. if len(msg) == 0: when defined(Windows): var err = GetLastError() if err != 0'i32: # sigh, why is this is so difficult? var msgbuf: cstring if FormatMessageA(0x000
#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# this unit handles Nim sets; it implements bit sets
# the code here should be reused in the Nim standard library

type
  TBitSet* = seq[int8]        # we use byte here to avoid issues with
                              # cross-compiling; uint would be more efficient
                              # however

const
  ElemSize* = sizeof(int8) * 8

proc bitSetInit*(b: var TBitSet, length: int)
proc bitSetUnion*(x: var TBitSet, y: TBitSet)
proc bitSetDiff*(x: var TBitSet, y: TBitSet)
proc bitSetSymDiff*(x: var TBitSet, y: TBitSet)
proc bitSetIntersect*(x: var TBitSet, y: TBitSet)
proc bitSetIncl*(x: var TBitSet, elem: BiggestInt)
proc bitSetExcl*(x: var TBitSet, elem: BiggestInt)
proc bitSetIn*(x: TBitSet, e: BiggestInt): bool
proc bitSetEquals*(x, y: TBitSet): bool
proc bitSetContains*(x, y: TBitSet): bool
# implementation

proc bitSetIn(x: TBitSet, e: BiggestInt): bool =
  result = (x[int(e div ElemSize)] and toU8(int(1 shl (e mod ElemSize)))) !=
      toU8(0)

proc bitSetIncl(x: var TBitSet, elem: BiggestInt) =
  assert(elem >= 0)
  x[int(elem div ElemSize)] = x[int(elem div ElemSize)] or
      toU8(int(1 shl (elem mod ElemSize)))

proc bitSetExcl(x: var TBitSet, elem: BiggestInt) =
  x[int(elem div ElemSize)] = x[int(elem div ElemSize)] and
      not toU8(int(1 shl (elem mod ElemSize)))

proc bitSetInit(b: var TBitSet, length: int) =
  newSeq(b, length)

proc bitSetUnion(x: var TBitSet, y: TBitSet) =
  for i in countup(0, high(x)): x[i] = x[i] or y[i]

proc bitSetDiff(x: var TBitSet, y: TBitSet) =
  for i in countup(0, high(x)): x[i] = x[i] and not y[i]

proc bitSetSymDiff(x: var TBitSet, y: TBitSet) =
  for i in countup(0, high(x)): x[i] = x[i] xor y[i]

proc bitSetIntersect(x: var TBitSet, y: TBitSet) =
  for i in countup(0, high(x)): x[i] = x[i] and y[i]

proc bitSetEquals(x, y: TBitSet): bool =
  for i in countup(0, high(x)):
    if x[i] != y[i]:
      return false
  result = true

proc bitSetContains(x, y: TBitSet): bool =
  for i in countup(0, high(x)):
    if (x[i] and not y[i]) != int8(0):
      return false
  result = true
ss for others proc getFilePermissions*(filename: string): set[TFilePermission] = ## retrieves file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is checked, every other ## permission is available in any case. when defined(posix): var a: TStat if stat(filename, a) < 0'i32: OSError() result = {} if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead) if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite) if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec) if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead) if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite) if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec) if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead) if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite) if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec) else: var res = GetFileAttributesA(filename) if res == -1'i32: OSError() if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead} else: result = {fpUserExec..fpOthersRead} proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) = ## sets the file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is changed, depending on ## ``fpUserWrite``. when defined(posix): var p = 0'i32 if fpUserRead in permissions: p = p or S_IRUSR if fpUserWrite in permissions: p = p or S_IWUSR if fpUserExec in permissions: p = p or S_IXUSR if fpGroupRead in permissions: p = p or S_IRGRP if fpGroupWrite in permissions: p = p or S_IWGRP if fpGroupExec in permissions: p = p or S_IXGRP if fpOthersRead in permissions: p = p or S_IROTH if fpOthersWrite in permissions: p = p or S_IWOTH if fpOthersExec in permissions: p = p or S_IXOTH if chmod(filename, p) != 0: OSError() else: var res = GetFileAttributesA(filename) if res == -1'i32: OSError() if fpUserWrite in permissions: res = res and not FILE_ATTRIBUTE_READONLY else: res = res or FILE_ATTRIBUTE_READONLY if SetFileAttributesA(filename, res) == - 1'i32: OSError() proc inclFilePermissions*(filename: string, permissions: set[TFilePermission]) = ## a convenience procedure for: ## ## .. code-block:: nimrod ## setFilePermissions(filename, getFilePermissions(filename)+permissions) setFilePermissions(filename, getFilePermissions(filename)+permissions) proc exclFilePermissions*(filename: string, permissions: set[TFilePermission]) = ## a convenience procedure for: ## ## .. code-block:: nimrod ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) proc getHomeDir*(): string = ## Returns the home directory of the current user. when defined(windows): return getEnv("USERPROFILE") & "\\" else: return getEnv("HOME") & "/" proc getConfigDir*(): string = ## Returns the config directory of the current user for applications. when defined(windows): return getEnv("APPDATA") & "\\" else: return getEnv("HOME") & "/.config/" when defined(windows): # Since we support GUI applications with Nimrod, we sometimes generate # a WinMain entry proc. But a WinMain proc has no access to the parsed # command line arguments. The way to get them differs. Thus we parse them # ourselves. This has the additional benefit that the program's behaviour # is always the same -- independent of the used C compiler. var ownArgv: seq[string] proc paramStr(i: int): string = if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLineA()) return ownArgv[i] proc paramCount(): int = if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLineA()) result = ownArgv.len-1 else: var cmdCount {.importc: "cmdCount".}: cint cmdLine {.importc: "cmdLine".}: cstringArray proc paramStr(i: int): string = if i < cmdCount and i >= 0: return $cmdLine[i] raise newException(EInvalidIndex, "invalid index") proc paramCount(): int = return cmdCount-1 when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): proc getApplAux(procPath: string): string = result = newString(256) var len = readlink(procPath, result, 256) if len > 256: result = newString(len+1) len = readlink(procPath, result, len) setlen(result, len) when defined(macosx): # a really hacky solution: since we like to include 2 headers we have to # define two procs which in reality are the same proc getExecPath1(c: cstring, size: var int32) {. importc: "_NSGetExecutablePath", header: "".} proc getExecPath2(c: cstring, size: var int32): bool {. importc: "_NSGetExecutablePath", header: "".} proc getApplicationFilename*(): string = ## Returns the filename of the application's executable. # Linux: /proc//exe # Solaris: # /proc//object/a.out (filename only) # /proc//path/a.out (complete pathname) # *BSD (and maybe Darwin too): # /proc//file when defined(windows): result = newString(256) var len = getModuleFileNameA(0, result, 256) setlen(result, int(len)) elif defined(linux) or defined(aix): result = getApplAux("/proc/self/exe") elif defined(solaris): result = getApplAux("/proc/" & $getpid() & "/path/a.out") elif defined(bsd): result = getApplAux("/proc/" & $getpid() & "/file") elif defined(macosx): var size: int32 getExecPath1(nil, size) result = newString(int(size)) if getExecPath2(result, size): result = "" # error! else: # little heuristic that may work on other POSIX-like systems: result = getEnv("_") if len(result) == 0: result = ParamStr(0) # POSIX guaranties that this contains the executable # as it has been executed by the calling process if len(result) > 0 and result[0] != DirSep: # not an absolute path? # iterate over any path in the $PATH environment variable for p in split(getEnv("PATH"), {PathSep}): var x = joinPath(p, result) if ExistsFile(x): return x proc getApplicationDir*(): string = ## Returns the directory of the application's executable. result = splitFile(getApplicationFilename()).dir proc sleep*(milsecs: int) = ## sleeps `milsecs` milliseconds. when defined(windows): winlean.sleep(int32(milsecs)) else: var a, b: Ttimespec a.tv_sec = TTime(milsecs div 1000) a.tv_nsec = (milsecs mod 1000) * 1000 discard posix.nanosleep(a, b) proc getFileSize*(file: string): biggestInt = ## returns the file size of `file`. Can raise ``EOS``. when defined(windows): var a: TWin32FindData var resA = findfirstFileA(file, a) if resA == -1: OSError() result = rdFileSize(a) findclose(resA) else: var f: TFile if open(f, file): result = getFileSize(f) close(f) else: OSError() {.pop.}