Current directory is a symlink?

852 views
Skip to first unread message

Kevin

unread,
Feb 26, 2011, 10:50:03 AM2/26/11
to nodejs
Hi,

I'm trying to detect if the current directory is a symlink, and if so
when it was last modified (as a precursor to having a node process
shut itself down when its code goes out of date, ie a "live" symlink
switches to a new version of the code). The trouble is that
process.cwd() always resolves the symlink:


kevin@rivendell:~/weblink$ cat test.js
try{
require('child_process').exec('pwd',
function (error, stdout, stderr) {
console.log('pwd: ' + stdout);
}
);

console.log( 'cwd: ' + process.cwd() );
console.log( 'read: ' + require('fs').readlinkSync('.') );
} catch(e){console.log(e.message);}

kevin@rivendell:~/weblink$ node test.js
cwd: /home/kevin/web // This is the absolute dir
EINVAL, Invalid argument '.' // This is from the readlinkSync not
running on a symlink
pwd: /home/kevin/weblink // This is the symlink


Does anyone know of a better way than spawning a child processes to
detect what the symlink for the current dir is? I made sure that I
cd'd into the symlink before running this, not into the absolute dir.

Thanks,
Kevin

Mihai Călin Bazon

unread,
Feb 26, 2011, 3:54:01 PM2/26/11
to nod...@googlegroups.com
On Sat, Feb 26, 2011 at 5:50 PM, Kevin <kbo...@gmail.com> wrote:
> Does anyone know of a better way than spawning a child processes to
> detect what the symlink for the current dir is?

Your question seems a bit illogical, I mean, in order for the whole
thing to work you'd have to make sure that you run the program like
this:

cd /path/to/symlink
./script.js

The following would fail:

/path/to/symlink/script.js

Why don't you better detect the timestamp of script.js, rather than
that of the directory it runs from?

Cheers,
--
Mihai Bazon,
http://mihai.bazon.net/blog

Kevin

unread,
Feb 26, 2011, 4:08:46 PM2/26/11
to nodejs
Yeah, maybe there's a better deployment model, but the way I'm
currently doing it for some non-Node daemons (which I want to switch
to node) is:

* Tag a version of the code
* Export that version into (eg) /usr/local/code/v57
* Symlink /usr/local/code/live to /usr/local/code/v57
* Run the script from /usr/local/code/live, and have it periodically
check the mtime of the cwd
* If the mtime has changed from what it was at launch, then have the
process gracefully die
* Let supervisord restart the script from /usr/local/code/live

That way, we can preload a new version of the code (eg into /usr/local/
code/v58) and then, on switching the symlink, the outdated process
will get nicely replaced with the updated one.

Combine this with the fact that we're running processes across an
unknown number of VMs which mount the code via a network share (where
"unknown" == "can't hit an API on each node process or send a signal
or anything like that, as we don't know which machines are running the
process"), and that model becomes quite useful.

Admittedly, I could either spawn a "pwd" command or check a text file
containing the latest version number instead of checking a symlink,
but it's the way we currently do it so it was the first thing I looked
at for Node as well.

Any ideas?

Cheers,
Kevin

ps Update: I just checked our other code, and they are spawning a
"pwd" to get the current symlinked dir as well :( I was hoping to
copy their system calls... Maybe I'll dig into the pwd source code a
little.


On Feb 26, 8:54 pm, Mihai Călin Bazon <mihai.ba...@gmail.com> wrote:

Mihai Călin Bazon

unread,
Feb 26, 2011, 4:16:13 PM2/26/11
to nod...@googlegroups.com
Here is a slightly modified workflow that would be just as convenient, but less hackish: (changes in bold)


* Tag a version of the code
* Export that version into (eg) /usr/local/code/v57
* Symlink /usr/local/code/live to /usr/local/code/v57
* Run the script from /usr/local/code/live, and have it periodically
check the mtime of /usr/local/code/live/.timestamp

* If the mtime has changed from what it was at launch, then have the
process gracefully die
* Let supervisord restart the script from /usr/local/code/live

It's a really small change.  Do it, you will thank me later. :-)

-Mihai

Correction: you probably won't thank me later, but if you don't do it, you will be sorry later. ;-)
> --
> You received this message because you are subscribed to the Google Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.

Kevin

unread,
Feb 26, 2011, 4:22:56 PM2/26/11
to nodejs
Heh, cheers, that's effectively like keeping the latest version in a
file somewhere. The main thing I was trying to enable was having
"relocatable" code (ie allowing the source tree to be in different
dirs in different environments, so I didn't have to hard-code where
the "timestamp" file was). This needs me to be able to read the cwd
as the symlink, which is the problem I'm having.

Just in case:
setTimeout(function(){if (problemSolved) console.log('Thanks
Mihai');}, 3600);

Cheers,
Kevin

On Feb 26, 9:16 pm, Mihai Călin Bazon <mihai.ba...@gmail.com> wrote:
> Here is a slightly modified workflow that would be just as convenient, but
> less hackish: (changes in bold)
>
> * Tag a version of the code
> * Export that version into (eg) /usr/local/code/v57
> * Symlink /usr/local/code/live to /usr/local/code/v57
> * Run the script from /usr/local/code/live, and have it periodically
> check the mtime of */usr/local/code/live/.timestamp*

Mihai Călin Bazon

unread,
Feb 26, 2011, 4:28:39 PM2/26/11
to nod...@googlegroups.com
On Sat, Feb 26, 2011 at 11:22 PM, Kevin <kbo...@gmail.com> wrote:
[...] dirs in different environments, so I didn't have to hard-code where
the "timestamp" file was). [...]

var timestamp_file = require("path").join(process.cwd(), ".timestamp");
// we really don't care if we're there through a symlink or not
 
Just in case:
 setTimeout(function(){if (problemSolved) console.log('Thanks
Mihai');}, 3600);

problemSolved = true; // you're welcome ;-)

Cheers,

Kevin

unread,
Feb 26, 2011, 4:33:41 PM2/26/11
to nodejs
Unfortunately... if the code is running as (eg) /usr/local/code/v57
then the .timestamp file won't change, as the updated one will be in /
usr/local/code/v58 but the running process will always be checking /
usr/local/code/v57/.timestamp

The problem, still, is how to make process.cwd() return "/usr/local/
code/live" and not "/usr/local/code/v57".

problemSolved = false; // Not yet...

Kevin

On Feb 26, 9:28 pm, Mihai Călin Bazon <mihai.ba...@gmail.com> wrote:

Kevin

unread,
Feb 26, 2011, 4:46:14 PM2/26/11
to nodejs
Ah, I just found the answer - the $PWD env var has it.

console.log(process.env.PWD);

Thanks,
Kevin

Mihai Călin Bazon

unread,
Feb 26, 2011, 4:50:26 PM2/26/11
to nod...@googlegroups.com
Ah, didn't know about it.  So you chose to be sorry later... :-)

(sorry, can't help saying it: you're solving the wrong problem, this solution might do the job but it's really poor).

Cheers,
-Mihai

--
You received this message because you are subscribed to the Google Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com.
To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.

Isaac Schlueter

unread,
Feb 27, 2011, 3:48:50 PM2/27/11
to nod...@googlegroups.com
To get the current working directory in node, use process.cwd(). The
PWD environ is not reliable.

PWD=pwned node -e 'process.env.PWD' // does this output the
current working dir?

Even in shell scripts, "$PWD" is not as good as "$(pwd)" for this
reason. Do not rely on it.

If you want the real path when running in a directory path that
contains symlinks, use:

var fs = require('fs')
fs.realpath(p, function (er, real) { ... })
// or
var real = fs.realpathSync(p) // throws on error

If you want to know the dir that a script lives in, check the
__dirname free variable. It should already be realpathed.

var scriptLivesInThisDir = __dirname

To get the real path of the cwd, you could do:

fs.realpath(process.cwd(), function (er, real) { ... })
// or
var real = fs.realpathSync(process.cwd())

There is no way in your script to detect if it was loaded via a
symlink. It always behaves as if it was loaded from its actual real
file location.

To find a file that exists in the same folder as your script, you can
use path.resolve() against the __dirname.

var tsFile = path.resolve(__dirname, '.timestamp')

To find it in the cwd, you can do:

var ts = path.resolve(process.cwd(), '.timestamp')

To find it in the real path of the cwd, you can do:

var ts = path.resolve(fs.realpathSync(process.cwd()), '.timestamp')
// or, equivalently:
var ts = fs.realpathSync(path.resolve(process.cwd(), '.timestamp'))

Does that answer your question(s)?

--i

Isaac Schlueter

unread,
Feb 27, 2011, 3:49:53 PM2/27/11
to nod...@googlegroups.com
Another thing: if you're going to be realpathing a lot, and don't
expect the file system to change out from under you while you do, you
can pass a "cache" object to fs.realpath, and it will use that to
avoid doing unnecessarily repetitive lstat calls. Check out how it's
used in lib/module.js.

--i

Kevin

unread,
Feb 27, 2011, 6:10:56 PM2/27/11
to nodejs
Hi Isaac,

Wow, that's quite a comprehensive answer, many thanks :)

The problem I'm struggling with is this:

"""
There is no way in your script to detect if it was loaded via a
symlink. It always behaves as if it was loaded from its actual real
file location.
"""

I'm not too worried about env-var hijacking, so $PWD is ok for my
current needs (this isn't an app for release or for running on the
open Internet or anything like that). Besides, that's what the pwd
command does as well (with the additional step of checking that the
resulting inode is the same one as ".").

Thanks again for your help, I think there just isn't really a good way
to do this. As Mihai said, I probably need a different solution to my
problem, although I'm still looking for a good one.

Kevin

Isaac Schlueter

unread,
Feb 27, 2011, 6:27:57 PM2/27/11
to nod...@googlegroups.com
Unless the environment variable is hijacked in some way, or you've
called process.chdir(), process.env.PWD will be identical to
process.cwd().

Why do you need to know the non-realpath'ed location of your js file?
process.env.PWD won't tell you this anyway. Incidentally, I wasn't
being clever enough when I said you couldn't get it. If you want to
know the non-realpath'ed location of the *main* script, you can look
at process.argv[1]. (Of course, if you're not in the main script,
then that doesn't help you much.)

What is the problem you're trying to solve, exactly? I read through
the previous emails, and didn't see anything that required that.

--i

Jacob Rothstein

unread,
Feb 27, 2011, 6:38:26 PM2/27/11
to nod...@googlegroups.com, Kevin
Hi Kevin,

If I understand your use case, couldn't you just check if the realpath
of the live symlink is the same as the realpath of the currently
running code?

if ( fs.realpathSync ( __dirname + '/../live' ) !==
fs.realpathSync ( __dirname ))
gracefulExit ()

–Jacob

Isaac Schlueter

unread,
Feb 27, 2011, 6:43:18 PM2/27/11
to nod...@googlegroups.com
Or even just:

if (fs.realpathSync(liveLocation).indexOf(__dirname) === 0) {
// it is live
} else {
gracefulExit()
}

--i

Kevin

unread,
Feb 28, 2011, 8:22:18 AM2/28/11
to nodejs
I initially tried using __dirname but it didn't work - I now realise
that it isn't defined in the REPL environment, only in scripts, and my
original playing around was just running "node" from the command-line.

Isaac: the problem I'm trying to solve is one of deployment, and
making it a trivial task to update the code in some long-running
processes which all have a fairly tight loop of repeating tasks (ie
wait for message on queue, do something with message, rinse and
repeat). Our current (non-node) stuff checks every so often if it is
the latest version of the code (ie if a later version has been
deployed), and it checks that by seeing if the place it's running from
(via the symlink) has an updated mtime. This works well because
Apache is setup to serve webpages via the symlink, so we don't need
any extra deployment steps to update web code and daemon code; it all
happens when the symlink changes.

Thanks again everyone for your help,
Kevin


On Feb 27, 11:43 pm, Isaac Schlueter <i...@izs.me> wrote:
> Or even just:
>
> if (fs.realpathSync(liveLocation).indexOf(__dirname) === 0) {
>   // it is live
>
> } else {
>   gracefulExit()
> }
>
> --i
>
> On Sun, Feb 27, 2011 at 15:38, Jacob Rothstein
>
>
>
>
>
>
>
> <jacob.rothst...@gmail.com> wrote:
> > Hi Kevin,
>
> > If I understand your use case, couldn't you just check if the realpath
> > of the live symlink is the same as the realpath of the currently
> > running code?
>
> > if ( fs.realpathSync ( __dirname + '/../live' ) !==
> >     fs.realpathSync ( __dirname ))
> >    gracefulExit ()
>
> > –Jacob
>
> >> For more options, visit this group athttp://groups.google.com/group/nodejs?hl=en.
Reply all
Reply to author
Forward
0 new messages