Search code examples
node.jslinuxdaemonfssystemd

nodejs - fs.stat isFile() always returns true when executed as daemon/service from systemd


Usually when executing a nodejs script with an fs.stat on a file that doesn't exist (config.js), as expected, it passes the callback an error; However when executing the same script as a deamon with systemd the fs.stat provides no error and stat.isFile() returns true.

Am I doing something wrong?
Is this a bug?
Is this a feature?

  • OS: Arch linux
  • Nodejs: v9.3.0

server.js (the script I mentioned):

const fs = require('fs');

fs.stat('./config.json', (err, stat) => {
    console.log('err:', err);
    if (err) return;
    console.log('stat:', stat);
    console.log('stat.isFile():', stat.isFile());
});

/etc/systemd/system/nat-server.service (systemd file):

[Unit]
Description=Network address transaltion server
Documentation=https://github.com/jkeveren/nat-server
After=network.target

[Service]
Type=simple
User=jkeveren
ExecStart=/usr/bin/node /home/jkeveren/mega/code/util/nat-server/server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Normal (desired) output:

err: { Error: ENOENT: no such file or directory, stat './config.json'
  errno: -2,
  code: 'ENOENT',
  syscall: 'stat',
  path: './config.json' }

systemd output (notice the last line):

Dec 19 13:49:59 jji-li systemd[1]: Started Network address transaltion server.
Dec 19 13:50:00 jji-li node[17498]: err: null
Dec 19 13:50:00 jji-li node[17498]: stat: Stats {
Dec 19 13:50:00 jji-li node[17498]:   dev: 2051,
Dec 19 13:50:00 jji-li node[17498]:   mode: 33188,
Dec 19 13:50:00 jji-li node[17498]:   nlink: 1,
Dec 19 13:50:00 jji-li node[17498]:   uid: 0,
Dec 19 13:50:00 jji-li node[17498]:   gid: 0,
Dec 19 13:50:00 jji-li node[17498]:   rdev: 0,
Dec 19 13:50:00 jji-li node[17498]:   blksize: 4096,
Dec 19 13:50:00 jji-li node[17498]:   ino: 15,
Dec 19 13:50:00 jji-li node[17498]:   size: 67,
Dec 19 13:50:00 jji-li node[17498]:   blocks: 8,
Dec 19 13:50:00 jji-li node[17498]:   atimeMs: 1513681349595.1235,
Dec 19 13:50:00 jji-li node[17498]:   mtimeMs: 1513681349595.1235,
Dec 19 13:50:00 jji-li node[17498]:   ctimeMs: 1513681349595.1235,
Dec 19 13:50:00 jji-li node[17498]:   birthtimeMs: 1513681349595.1235,
Dec 19 13:50:00 jji-li node[17498]:   atime: 2017-12-19T11:02:29.595Z,
Dec 19 13:50:00 jji-li node[17498]:   mtime: 2017-12-19T11:02:29.595Z,
Dec 19 13:50:00 jji-li node[17498]:   ctime: 2017-12-19T11:02:29.595Z,
Dec 19 13:50:00 jji-li node[17498]:   birthtime: 2017-12-19T11:02:29.595Z }
Dec 19 13:50:00 jji-li node[17498]: stat.isFile(): true

Solution

  • When systemd runs a program it uses / as the working directory, which makes perfect sense, it just didn't cross my mind for some reason or another.

    This means that when fs.stat looks for ./config.json it looks for /config.json which does exist from a previous run of this script when it had a config generator.

    The solution is to replace:

    fs.stat('./config.json', (err, stat) => {
    

    with:

    fs.stat(path.join(__dirname, './config.json'), (err, stat) => {
    

    Hopefully this will help anyone (including me) who has the same issue (again) in the future.