masq
Version:
A simple local dns server extracted from Pow
433 lines (319 loc) • 20.7 kB
HTML
<html>
<head>
<title>utils.coffee</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div id="container">
<div id="background"></div>
<ul id="jump_to">
<li>
<a class="large" href="javascript:void(0);">Jump To …</a>
<a class="small" href="javascript:void(0);">+</a>
<div id="jump_wrapper">
<div id="jump_page_wrapper">
<div id="jump_page">
<a class="source" href="command.html">
command.coffee
</a>
<a class="source" href="configuration.html">
configuration.coffee
</a>
<a class="source" href="daemon.html">
daemon.coffee
</a>
<a class="source" href="dns_server.html">
dns_server.coffee
</a>
<a class="source" href="index.html">
index.coffee
</a>
<a class="source" href="installer.html">
installer.coffee
</a>
<a class="source" href="logger.html">
logger.coffee
</a>
<a class="source" href="utils.html">
utils.coffee
</a>
</div>
</div>
</li>
</ul>
<ul class="sections">
<li id="title">
<div class="annotation">
<h1>utils.coffee</h1>
</div>
</li>
<li id="section-1">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-1">¶</a>
</div>
<p>The <code>util</code> module houses a number of utility functions used
throughout Pow.</p>
</div>
<div class="content"><div class='highlight'><pre>
fs = <span class="hljs-built_in">require</span> <span class="hljs-string">"fs"</span>
path = <span class="hljs-built_in">require</span> <span class="hljs-string">"path"</span>
async = <span class="hljs-built_in">require</span> <span class="hljs-string">"async"</span>
{execFile} = <span class="hljs-built_in">require</span> <span class="hljs-string">"child_process"</span>
{Stream} = <span class="hljs-built_in">require</span> <span class="hljs-string">"stream"</span></pre></div></div>
</li>
<li id="section-2">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-2">¶</a>
</div>
<p>Asynchronously and recursively create a directory if it does not
already exist. Then invoke the given callback.</p>
</div>
<div class="content"><div class='highlight'><pre>exports.mkdirp = <span class="hljs-function"><span class="hljs-params">(dirname, callback)</span> -></span>
fs.lstat (p = path.normalize dirname), <span class="hljs-function"><span class="hljs-params">(err, stats)</span> -></span>
<span class="hljs-keyword">if</span> err
paths = [p].concat(p = path.dirname p <span class="hljs-keyword">until</span> p <span class="hljs-keyword">in</span> [<span class="hljs-string">"/"</span>, <span class="hljs-string">"."</span>])
async.forEachSeries paths.reverse(), <span class="hljs-function"><span class="hljs-params">(p, next)</span> -></span>
fs.exists p, <span class="hljs-function"><span class="hljs-params">(exists)</span> -></span>
<span class="hljs-keyword">if</span> exists <span class="hljs-keyword">then</span> next()
<span class="hljs-keyword">else</span> fs.mkdir p, <span class="hljs-number">0</span>o755, <span class="hljs-function"><span class="hljs-params">(err)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> next()
, callback
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> stats.isDirectory()
callback()
<span class="hljs-keyword">else</span>
callback <span class="hljs-string">"file exists"</span></pre></div></div>
</li>
<li id="section-3">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-3">¶</a>
</div>
<p>A wrapper around <code>chown(8)</code> for taking ownership of a given path
with the specified owner string (such as <code>"root:wheel"</code>). Invokes
<code>callback</code> with the error string, if any, and a boolean value
indicating whether or not the operation succeeded.</p>
</div>
<div class="content"><div class='highlight'><pre>exports.chown = <span class="hljs-function"><span class="hljs-params">(path, owner, callback)</span> -></span>
error = <span class="hljs-string">""</span>
exec [<span class="hljs-string">"chown"</span>, owner, path], <span class="hljs-function"><span class="hljs-params">(err, stdout, stderr)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err, stderr
<span class="hljs-keyword">else</span> callback <span class="hljs-literal">null</span></pre></div></div>
</li>
<li id="section-4">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-4">¶</a>
</div>
<p>Spawn a Bash shell with the given <code>env</code> and source the named
<code>script</code>. Then collect its resulting environment variables and pass
them to <code>callback</code> as the second argument. If the script returns a
non-zero exit code, call <code>callback</code> with the error as its first
argument, and annotate the error with the captured <code>stdout</code> and
<code>stderr</code>.</p>
</div>
<div class="content"><div class='highlight'><pre>exports.sourceScriptEnv = <span class="hljs-function"><span class="hljs-params">(script, env, options, callback)</span> -></span>
<span class="hljs-keyword">if</span> options.call
callback = options
options = {}
<span class="hljs-keyword">else</span>
options ?= {}</pre></div></div>
</li>
<li id="section-5">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-5">¶</a>
</div>
<p>Build up the command to execute, starting with the <code>before</code>
option, if any. Then source the given script, swallowing any
output written to stderr. Finally, dump the current environment to
a temporary file.</p>
</div>
<div class="content"><div class='highlight'><pre> cwd = path.dirname script
filename = makeTemporaryFilename()
command = <span class="hljs-string">"""
<span class="hljs-subst">#{options.before ? <span class="hljs-string">"true"</span>}</span> &&
source <span class="hljs-subst">#{quote script}</span> > /dev/null &&
env > <span class="hljs-subst">#{quote filename}</span>
"""</span></pre></div></div>
</li>
<li id="section-6">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-6">¶</a>
</div>
<p>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.</p>
</div>
<div class="content"><div class='highlight'><pre> exec [<span class="hljs-string">"bash"</span>, <span class="hljs-string">"-c"</span>, command], {cwd, env}, <span class="hljs-function"><span class="hljs-params">(err, stdout, stderr)</span> -></span>
<span class="hljs-keyword">if</span> err
err.message = <span class="hljs-string">"'<span class="hljs-subst">#{script}</span>' failed to load:\n<span class="hljs-subst">#{command}</span>"</span>
err.stdout = stdout
err.stderr = stderr
callback err
<span class="hljs-keyword">else</span> readAndUnlink filename, <span class="hljs-function"><span class="hljs-params">(err, result)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> callback <span class="hljs-literal">null</span>, parseEnv result</pre></div></div>
</li>
<li id="section-7">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-7">¶</a>
</div>
<p>Get the user’s login environment by spawning a login shell and
collecting its environment variables via the <code>env</code> command. (In case
the user’s shell profile script prints output to stdout or stderr,
we must redirect <code>env</code> output to a temporary file and read that.)</p>
<p>The returned environment will include a default <code>LANG</code> variable if
one is not set by the user’s shell. This default value of <code>LANG</code> is
determined by joining the user’s current locale with the value of
the <code>defaultEncoding</code> parameter, or <code>UTF-8</code> if it is not set.</p>
</div>
<div class="content"><div class='highlight'><pre>exports.getUserEnv = <span class="hljs-function"><span class="hljs-params">(callback, defaultEncoding = <span class="hljs-string">"UTF-8"</span>)</span> -></span>
filename = makeTemporaryFilename()
loginExec <span class="hljs-string">"exec env > <span class="hljs-subst">#{quote filename}</span>"</span>, <span class="hljs-function"><span class="hljs-params">(err)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> readAndUnlink filename, <span class="hljs-function"><span class="hljs-params">(err, result)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> getUserLocale (locale) ->
env = parseEnv result
env.LANG ?= <span class="hljs-string">"<span class="hljs-subst">#{locale}</span>.<span class="hljs-subst">#{defaultEncoding}</span>"</span>
callback <span class="hljs-literal">null</span>, env</pre></div></div>
</li>
<li id="section-8">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-8">¶</a>
</div>
<p>Execute a command without spawning a subshell. The command argument
is an array of program name and arguments.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">exec</span> = <span class="hljs-params">(command, options, callback)</span> -></span>
<span class="hljs-keyword">unless</span> callback?
callback = options
options = {}
execFile <span class="hljs-string">"/usr/bin/env"</span>, command, options, callback</pre></div></div>
</li>
<li id="section-9">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-9">¶</a>
</div>
<p>Single-quote a string for command line execution.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">quote</span> = <span class="hljs-params">(string)</span> -></span> <span class="hljs-string">"'"</span> + string.replace(<span class="hljs-regexp">/\'/g</span>, <span class="hljs-string">"'\\''"</span>) + <span class="hljs-string">"'"</span></pre></div></div>
</li>
<li id="section-10">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-10">¶</a>
</div>
<p>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.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">makeTemporaryFilename</span> = -></span>
tmpdir = process.env.TMPDIR ? <span class="hljs-string">"/tmp"</span>
timestamp = <span class="hljs-keyword">new</span> Date().getTime()
random = parseInt Math.random() * Math.pow(<span class="hljs-number">2</span>, <span class="hljs-number">16</span>)
filename = <span class="hljs-string">"pow.<span class="hljs-subst">#{process.pid}</span>.<span class="hljs-subst">#{timestamp}</span>.<span class="hljs-subst">#{random}</span>"</span>
path.join tmpdir, filename</pre></div></div>
</li>
<li id="section-11">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-11">¶</a>
</div>
<p>Read the contents of a file, unlink the file, then invoke the
callback with the contents of the file.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">readAndUnlink</span> = <span class="hljs-params">(filename, callback)</span> -></span>
fs.readFile filename, <span class="hljs-string">"utf8"</span>, <span class="hljs-function"><span class="hljs-params">(err, contents)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> fs.unlink filename, <span class="hljs-function"><span class="hljs-params">(err)</span> -></span>
<span class="hljs-keyword">if</span> err <span class="hljs-keyword">then</span> callback err
<span class="hljs-keyword">else</span> callback <span class="hljs-literal">null</span>, contents</pre></div></div>
</li>
<li id="section-12">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-12">¶</a>
</div>
<p>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 <code>-l</code>
option. If that fails, retry without <code>-l</code>; some shells, like tcsh,
cannot be started as non-interactive login shells. If that fails,
bubble the error up to the callback.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">loginExec</span> = <span class="hljs-params">(command, callback)</span> -></span>
getUserShell (shell) ->
login = [<span class="hljs-string">"login"</span>, <span class="hljs-string">"-qf"</span>, process.env.LOGNAME, shell]
exec [login..., <span class="hljs-string">"-i"</span>, <span class="hljs-string">"-c"</span>, command], <span class="hljs-function"><span class="hljs-params">(err, stdout, stderr)</span> -></span>
<span class="hljs-keyword">if</span> err
exec [login..., <span class="hljs-string">"-c"</span>, command], callback
<span class="hljs-keyword">else</span>
callback <span class="hljs-literal">null</span>, stdout, stderr</pre></div></div>
</li>
<li id="section-13">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-13">¶</a>
</div>
<p>Invoke <code>dscl(1)</code> to find out what shell the user prefers. We cannot
rely on <code>process.env.SHELL</code> because it always seems to be
<code>/bin/bash</code> when spawned from <code>launchctl</code>, regardless of what the
user has set.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">getUserShell</span> = <span class="hljs-params">(callback)</span> -></span>
command = [<span class="hljs-string">"dscl"</span>, <span class="hljs-string">"."</span>, <span class="hljs-string">"-read"</span>, <span class="hljs-string">"/Users/<span class="hljs-subst">#{process.env.LOGNAME}</span>"</span>, <span class="hljs-string">"UserShell"</span>]
exec command, <span class="hljs-function"><span class="hljs-params">(err, stdout, stderr)</span> -></span>
<span class="hljs-keyword">if</span> err
callback process.env.SHELL
<span class="hljs-keyword">else</span>
<span class="hljs-keyword">if</span> matches = stdout.trim().match <span class="hljs-regexp">/^UserShell: (.+)$/</span>
[match, shell] = matches
callback shell
<span class="hljs-keyword">else</span>
callback process.env.SHELL</pre></div></div>
</li>
<li id="section-14">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-14">¶</a>
</div>
<p>Read the user’s current locale preference from the OS X defaults
database. Fall back to <code>en_US</code> if it can’t be determined.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">getUserLocale</span> = <span class="hljs-params">(callback)</span> -></span>
exec [<span class="hljs-string">"defaults"</span>, <span class="hljs-string">"read"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"AppleLocale"</span>], <span class="hljs-function"><span class="hljs-params">(err, stdout, stderr)</span> -></span>
locale = stdout?.trim() ? <span class="hljs-string">""</span>
locale = <span class="hljs-string">"en_US"</span> <span class="hljs-keyword">unless</span> locale.match <span class="hljs-regexp">/^\w+$/</span>
callback locale</pre></div></div>
</li>
<li id="section-15">
<div class="annotation">
<div class="pilwrap ">
<a class="pilcrow" href="#section-15">¶</a>
</div>
<p>Parse the output of the <code>env</code> command into a JavaScript object.</p>
</div>
<div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">parseEnv</span> = <span class="hljs-params">(stdout)</span> -></span>
env = {}
<span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> stdout.split <span class="hljs-string">"\n"</span>
<span class="hljs-keyword">if</span> matches = line.match <span class="hljs-regexp">/([^=]+)=(.+)/</span>
[match, name, value] = matches
env[name] = value
env</pre></div></div>
</li>
</ul>
</div>
</body>
</html>