Homepage

# Debugging Electron to run shell scripts in a packaged application

I’ve been lately working on Etcher, a beautiful Electron application to burn ISO/IMG images to removable drives in all major operating systems.

In Windows, the maximum length of a file path is 260 characters. To workaround this limitation, improve code protection and other perks, Electron encourages the developer to package her application as a read-only tar-like format for distribution called asar.

For this to work, Electron patches some functions from the Node.js API to treat an asar package as a virtual directory. One of the patched functions is child_process.execFile, which when called on a file inside an asar archive, will extract the file into a temporary location and execute from there.

So far, so good. Etcher makes use of an npm module to list the connected drives called drivelist, which calls shell scripts (bash and batch) implemented for all supported operating systems using child_process.execFile, and parses the result.

As soon as we package the application and try running drivelist we get the following mysterious error:

Error: spawn EACCES


Turns out asar discards execution permissions from the file it archives, which can be easily proved with some help from the handy asar command line utility tool:

# Install the command line utility tool
$npm install -g asar # Create a directory containing an executable file$ mkdir asar_test
$touch asar_test/foo$ chmod 755 asar_test/foo

# Check that the file has execution permissions for the everyone
$ls -l asar_test/foo -rwxr-xr-x 1 jviotti staff 0 Nov 26 21:54 asar_test/foo* # Pack the directory and extract the resulting archive$ asar pack asar_test app.asar
$asar extract app.asar output # The file indeed lost its execution permissions$ ls -l output/foo
-rw-r--r--  1 jviotti  staff  0 Nov 26 21:57 asar_test/foo


By looking closer at asar, we can see it stores a serialized 8 bytes JSON header at the beginning of the archive, indexing the files and directories stored in the archive along with boolean executable properties, which are set to true if the file has execution permissions at the point where the archive is created:

if process.platform isnt 'win32' and stat.mode & 0o100
node.executable = true


This can be easily fixed by checking the executable property using the stat method provided by asar in the child_process.execFile Electron patch, and manually adding the execution permission using fs.chmod after the extraction to a temporary location.

See the following pull request that implements this fix: https://github.com/atom/electron/pull/3595

After the above fix, the application runs flawlessly on UNIX based operating systems, however we get a new mysterious error when running on Windows:

Error: spawn UNKNOWN


By taking another closer look at Electron and asar, we can see that Electron extracts files from the archive with a generic .tmp extension. This comes from Chromium’s CreateTemporaryFile utility, which makes use of GetTempFileName from the Windows API, which according to the docs:

The GetTempFileName function creates a temporary file name of the following form:

<path>\<pre><uuuu>.TMP

Since Windows has no idea how to execute a .tmp file, it fails with an UNKNOWN error. The solution is to make Electron append the file extension when extracting files, so we get a lovely .bat after .tmp.

See the following pull request that implements this fix: https://github.com/atom/electron/pull/3648

At the end of the day, we can successfully run scripts from an packaged Electron application in all operating system!