• Jump To … +
    command.coffee configuration.coffee daemon.coffee dns_server.coffee index.coffee installer.coffee logger.coffee utils.coffee
  • utils.coffee

  • ¶

    The util module houses a number of utility functions used throughout Pow.

    fs         = require "fs"
    path       = require "path"
    async      = require "async"
    {execFile} = require "child_process"
    {Stream}   = require "stream"
  • ¶

    Asynchronously and recursively create a directory if it does not already exist. Then invoke the given callback.

    exports.mkdirp = (dirname, callback) ->
      fs.lstat (p = path.normalize dirname), (err, stats) ->
        if err
          paths = [p].concat(p = path.dirname p until p in ["/", "."])
          async.forEachSeries paths.reverse(), (p, next) ->
            fs.exists p, (exists) ->
              if exists then next()
              else fs.mkdir p, 0o755, (err) ->
                if err then callback err
                else next()
          , callback
        else if stats.isDirectory()
          callback()
        else
          callback "file exists"
  • ¶

    A wrapper around chown(8) for taking ownership of a given path with the specified owner string (such as "root:wheel"). Invokes callback with the error string, if any, and a boolean value indicating whether or not the operation succeeded.

    exports.chown = (path, owner, callback) ->
        error = ""
        exec ["chown", owner, path], (err, stdout, stderr) ->
            if err then callback err, stderr
            else callback null
  • ¶

    Spawn a Bash shell with the given env and source the named script. Then collect its resulting environment variables and pass them to callback as the second argument. If the script returns a non-zero exit code, call callback with the error as its first argument, and annotate the error with the captured stdout and stderr.

    exports.sourceScriptEnv = (script, env, options, callback) ->
      if options.call
        callback = options
        options = {}
      else
        options ?= {}
  • ¶

    Build up the command to execute, starting with the before option, if any. Then source the given script, swallowing any output written to stderr. Finally, dump the current environment to a temporary file.

      cwd = path.dirname script
      filename = makeTemporaryFilename()
      command = """
        #{options.before ? "true"} &&
        source #{quote script} > /dev/null &&
        env > #{quote filename}
      """
  • ¶

    Run our command through Bash in the directory of the script. If an error occurs, rewrite the error to a more descriptive message. Otherwise, read and parse the environment from the temporary file and pass it along to the callback.

      exec ["bash", "-c", command], {cwd, env}, (err, stdout, stderr) ->
        if err
          err.message = "'#{script}' failed to load:\n#{command}"
          err.stdout = stdout
          err.stderr = stderr
          callback err
        else readAndUnlink filename, (err, result) ->
          if err then callback err
          else callback null, parseEnv result
  • ¶

    Get the user’s login environment by spawning a login shell and collecting its environment variables via the env command. (In case the user’s shell profile script prints output to stdout or stderr, we must redirect env output to a temporary file and read that.)

    The returned environment will include a default LANG variable if one is not set by the user’s shell. This default value of LANG is determined by joining the user’s current locale with the value of the defaultEncoding parameter, or UTF-8 if it is not set.

    exports.getUserEnv = (callback, defaultEncoding = "UTF-8") ->
      filename = makeTemporaryFilename()
      loginExec "exec env > #{quote filename}", (err) ->
        if err then callback err
        else readAndUnlink filename, (err, result) ->
          if err then callback err
          else getUserLocale (locale) ->
            env = parseEnv result
            env.LANG ?= "#{locale}.#{defaultEncoding}"
            callback null, env
  • ¶

    Execute a command without spawning a subshell. The command argument is an array of program name and arguments.

    exec = (command, options, callback) ->
      unless callback?
        callback = options
        options = {}
      execFile "/usr/bin/env", command, options, callback
  • ¶

    Single-quote a string for command line execution.

    quote = (string) -> "'" + string.replace(/\'/g, "'\\''") + "'"
  • ¶

    Generate and return a unique temporary filename based on the current process’s PID, the number of milliseconds elapsed since the UNIX epoch, and a random integer.

    makeTemporaryFilename = ->
      tmpdir    = process.env.TMPDIR ? "/tmp"
      timestamp = new Date().getTime()
      random    = parseInt Math.random() * Math.pow(2, 16)
      filename  = "pow.#{process.pid}.#{timestamp}.#{random}"
      path.join tmpdir, filename
  • ¶

    Read the contents of a file, unlink the file, then invoke the callback with the contents of the file.

    readAndUnlink = (filename, callback) ->
      fs.readFile filename, "utf8", (err, contents) ->
        if err then callback err
        else fs.unlink filename, (err) ->
          if err then callback err
          else callback null, contents
  • ¶

    Execute the given command through a login shell and pass the contents of its stdout and stderr streams to the callback. In order to spawn a login shell, first spawn the user’s shell with the -l option. If that fails, retry without -l; some shells, like tcsh, cannot be started as non-interactive login shells. If that fails, bubble the error up to the callback.

    loginExec = (command, callback) ->
      getUserShell (shell) ->
        login = ["login", "-qf", process.env.LOGNAME, shell]
        exec [login..., "-i", "-c", command], (err, stdout, stderr) ->
          if err
            exec [login..., "-c", command], callback
          else
            callback null, stdout, stderr
  • ¶

    Invoke dscl(1) to find out what shell the user prefers. We cannot rely on process.env.SHELL because it always seems to be /bin/bash when spawned from launchctl, regardless of what the user has set.

    getUserShell = (callback) ->
      command = ["dscl", ".", "-read", "/Users/#{process.env.LOGNAME}", "UserShell"]
      exec command, (err, stdout, stderr) ->
        if err
          callback process.env.SHELL
        else
          if matches = stdout.trim().match /^UserShell: (.+)$/
            [match, shell] = matches
            callback shell
          else
            callback process.env.SHELL
  • ¶

    Read the user’s current locale preference from the OS X defaults database. Fall back to en_US if it can’t be determined.

    getUserLocale = (callback) ->
      exec ["defaults", "read", "-g", "AppleLocale"], (err, stdout, stderr) ->
        locale = stdout?.trim() ? ""
        locale = "en_US" unless locale.match /^\w+$/
        callback locale
  • ¶

    Parse the output of the env command into a JavaScript object.

    parseEnv = (stdout) ->
      env = {}
      for line in stdout.split "\n"
        if matches = line.match /([^=]+)=(.+)/
          [match, name, value] = matches
          env[name] = value
      env