fs
The fs module provides synchronous, Node.js-compatible file system APIs for JSH applications.
API names and argument formats follow the Node.js fs module, but every function runs synchronously on top of the JSH virtual file system. Behaviors that differ from Node.js are listed under Notes for each function.
Paths and mounts
JSH mounts several kinds of file systems into a single virtual directory tree. Paths passed to fs functions are virtual paths, and relative paths are resolved against the current working directory.
Supported features depend on the mount type.
| Mount type | Example | Characteristics |
|---|---|---|
| Host directory | /work, /tmp | Directly backed by the operating system file system. All APIs are supported. |
| Memory-backed | /lib | File contents are kept in memory. Path-based APIs such as readFile/writeFile are supported, but write file descriptors (open(path, 'w'), etc.) fail with ENOTSUP. fsync() on a read descriptor also returns ENOTSUP. The maximum size of a single file is 1GiB. |
| Read-only (embedded) | /usr, /sbin | Files embedded in the executable. Write operations fail with EACCES, and fsync() on a descriptor returns ENOTSUP. |
| Shared file system | Depends on the service configuration | Shared storage provided by the service controller to multiple JSH processes. The maximum size of a single file is 1GiB. |
rename(); it fails with an EXDEV error. In that case, copy the file with copyFile() or cp() and then remove the original.Regardless of the mount type, the root directory of each mount (for example, /work or /lib) is a mount point. Intermediate directories that exist only because of nested mounts (for example, /a when only /a/b is mounted) are also treated as mount points. A mount point cannot be removed with rmdir(), unlink(), or rm(), and cannot be the source or destination of rename(). All such operations fail with EBUSY, and the mount point and mount configuration are left unchanged. Files and directories inside a mount point can be removed or moved as usual.
Error handling
When a file system operation fails, an Error object in the same format as Node.js is thrown.
| Property | Description |
|---|---|
code | Error code string. e.g. ENOENT, EEXIST |
errno | Error number (negative). e.g. -2 |
syscall | Name of the failed operation. e.g. open, mkdir, rmdir |
path | Target path. Not included in errors from file-descriptor-based functions (read(), write(), close(), fstat(), fsync(), etc.). |
dest | Destination path for rename() errors |
The error message format is CODE: description, syscall 'path', for example ENOENT: no such file or directory, open '/work/none.txt'.
| |
Main error codes
| Code | errno | Meaning |
|---|---|---|
EPERM | -1 | Operation not permitted |
ENOENT | -2 | No such file or directory |
EIO | -5 | Input/output error |
EBADF | -9 | Bad file descriptor. A closed or nonexistent descriptor, or writing to a descriptor opened read-only, etc. |
EACCES | -13 | Permission denied, including writes to a read-only mount |
EBUSY | -16 | Resource busy. Raised when removing or moving a mount point |
EEXIST | -17 | Already exists |
EXDEV | -18 | Move between different mounts |
ENOTDIR | -20 | Not a directory |
EISDIR | -21 | Operation not allowed on a directory |
EINVAL | -22 | Invalid argument |
EFBIG | -27 | File too large. Exceeds the 1GiB limit of memory-backed and shared file systems |
ENOSPC | -28 | No space left on device |
EROFS | -30 | Read-only file system |
ENAMETOOLONG | -36 | File name too long |
ENOTEMPTY | -39 | Directory not empty |
ELOOP | -40 | Too many levels of symbolic links |
ENOTSUP | -95 | Operation not supported |
ECONFLICT | -32009 | (JSH only) Another process modified the same file on the shared file system after the descriptor was opened |
errnovalues are fixed to the values in the table above (Linux values) regardless of the platform. They may differ from theerrnonumbers reported by Node.js on macOS or Windows, so usecoderather thanerrnoto identify errors.- When an argument such as
offset,length,position, orlenis out of range, aRangeErroris thrown withcodeset toERR_OUT_OF_RANGEand noerrno.
readFile()
Reads a file and returns its contents as a string (default: utf8) or a byte array.
Syntax
readFile(path[, options])optionsis an encoding string or an{ encoding }object. Specifyencoding: nullto get a byte array.- The whole file is read into memory. For large files, read in chunks with
createReadStream()oropen()/read().
Usage example
| |
writeFile()
Writes data to a file. Creates the file if it does not exist, or overwrites it if it does.
Syntax
writeFile(path, data[, options])- Fails with
ENOENTif the parent directory does not exist. Callmkdir(dir, { recursive: true })first if needed. - On memory-backed mounts and the shared file system, files larger than 1GiB cannot be written.
Usage example
| |
appendFile()
Appends data to the end of a file. Creates the file if it does not exist.
Syntax
appendFile(path, data[, options])Usage example
| |
countLines()
Counts the number of lines in a file (based on newlines). This function is specific to JSH.
Syntax
countLines(path)Usage example
| |
exists()
Returns true/false indicating whether a file or directory exists.
Syntax
exists(path)- Symbolic links are followed. A link whose target does not exist (a broken link) returns
false. Uselstat()to check whether the link itself exists. - Also returns
falsewhen the check fails for other reasons, such as insufficient permissions. Useaccess()orstat()if you need the cause of the failure.
Usage example
| |
stat()
Returns metadata for a file or directory. If the path is a symbolic link, information about the link target is returned.
Syntax
stat(path)Returned fields
name: File name (the last path component)size: Size in bytesmode: File mode object.String(st.mode)returns a string such asdrwxr-xr-x.mtime,atime,ctime,birthtime: In the current implementation, all have the same value as the modification time (mtime).isFile(),isDirectory(),isSymbolicLink()isBlockDevice(),isCharacterDevice(),isFIFO(),isSocket()
Usage example
| |
lstat()
Returns file metadata. Unlike stat(), if the last path component is a symbolic link, the link is not followed and information about the link itself is returned. The returned fields are the same as stat(). Intermediate directories created by nested mounts are reported as directories, just as with stat().
Syntax
lstat(path)Usage example
| |
readdir()
Reads directory entries.
- Default: returns a
string[]of entry names. withFileTypes: true: returns entry objects withnameand type methods (isFile(),isDirectory(),isSymbolicLink(), etc.).recursive: true: returns entries recursively, including subdirectories.
Syntax
readdir(path[, options])Notes
- Non-recursive listings include the
.and..entries. This differs from Node.js, so always skip these two entries when walking directories. Otherwise, the same directory may be traversed recursively forever. - Recursive listings (
recursive: true) do not include.and...- String results are paths relative to the starting directory (e.g.
a/b/file.txt). - The
pathproperty of an entry object is the relative path of the directory containing the entry; it is''for entries directly under the starting directory. This differs from Node.jsDirent.path(the full path of the parent directory).
- String results are paths relative to the starting directory (e.g.
- Recursive listings do not descend into symbolic links that point to directories. Such links are returned only as entries.
- If a subdirectory cannot be read during a recursive listing (e.g. insufficient permissions), the whole listing fails with an error. The
pathof the error object points to the failed subdirectory.
Usage example
| |
mkdir()
Creates a directory.
Syntax
mkdir(path[, options])Options
recursive: Iftrue, missing parent directories are created as well, and existing directories are skipped without error. The default isfalse.mode: Permissions of the directory to create. The default is0o755, and it applies to every directory newly created during recursive creation. On host directories, the operating system umask may also apply.
Behavior
- Non-recursive call (
recursive: false)- Fails with
ENOENTif the parent directory does not exist. Parent directories are not created automatically. - Fails with
EEXISTif the target already exists, whether it is a directory or a file.
- Fails with
- Recursive call (
recursive: true)- Fails with
EEXISTif a file with the same name exists in the middle of the path. Node.js returnsENOTDIRin this case. - Always returns
undefined. Unlike Node.js, the first created path is not returned.
- Fails with
Usage example
| |
rmdir()
Removes an empty directory.
Syntax
rmdir(path[, options])- Fails with
ENOTEMPTYif the directory is not empty, orENOTDIRif the target is a file. - With
{ recursive: true }, all child entries are removed first. This option is deprecated in Node.js, sorm(path, { recursive: true })is recommended for new code. - During recursive removal, symbolic links pointing to directories are removed as links only; the contents of the link target are not deleted.
- Fails with
EBUSYif a mount point is specified. Recursive removal handles mount points in the same way as rm().
rm()
Removes a file, symbolic link, or directory.
Syntax
rm(path[, options])Options
recursive: Iftrue, removes the directory and all of its contents.force: Iftrue, ignores the error when the target does not exist (ENOENT).
Notes
- The target type is determined with
lstat(). A symbolic link is removed as a link only, even if it points to a directory; the target directory and its contents are preserved. - Without
recursive, only an empty directory can be removed. A non-empty directory fails withENOTEMPTY. force: trueignores onlyENOENT. Other errors, such as insufficient permissions (EACCES) and I/O errors (EIO), are thrown as is.EBUSYraised for a mount point is not ignored either.- If an error occurs during recursive removal, the operation stops immediately. Entries already removed are not restored, so the tree may be left partially deleted.
- Mount points are an exception. When the target itself or a directory under it is a mount point, only its contents are removed and it is left as an empty directory. Removal continues with the remaining entries, and after all entries are processed,
EBUSYis thrown with the path of the first mount point encountered. The result is the same as runningrm -ron a mounted directory on Linux.
Usage example
| |
unlink()
Removes a file or symbolic link.
Syntax
unlink(path)- Cannot be used on directories; fails with
EISDIR. Usermdir()orrm()for directories. - When a symbolic link is specified, only the link is removed and the target file is kept.
- Fails with
EBUSYif a mount point is specified. - On Windows, files with the read-only attribute are also removed.
unlink(), rmdir(), rm(), or rename() leaves the parent directory in place. Memory-backed mounts and the shared file system behave the same way.rename()
Renames or moves a file or directory within the same mount.
Syntax
rename(oldPath, newPath)- If a file exists at the destination path, it is overwritten according to the operating system
renamebehavior. - Moving between different mounts fails with
EXDEV. In this case, thedestproperty of the error object contains the destination path. - Fails with
EBUSYif the source or destination is a mount point.
copyFile()
Copies a single file.
Syntax
copyFile(src, dest[, flags])- By default, an existing destination file is overwritten.
- If
constants.COPYFILE_EXCLis specified inflags, it fails withEEXISTwhen the destination file exists. COPYFILE_FICLONEandCOPYFILE_FICLONE_FORCEare ignored, and a regular copy is performed.- Only file contents are copied. The permissions and modification time of the source file are not preserved.
- The whole file is read into memory before writing, so it is not suitable for copying very large files.
cp()
Copies a file or directory.
Syntax
cp(src, dest[, options])Options
recursive: Must betrueto copy a directory. If omitted, copying a directory fails withEISDIR.force: Iftrue, existing files at the destination are overwritten.
Notes
- The default value of
forceisfalse. If a destination file already exists, it fails withEEXIST. This differs from Node.jscpSync(), which overwrites by default. - Symbolic links in the source are followed and their target contents are copied. The links themselves are not copied.
- Only file contents are copied; permissions and modification times are not preserved.
symlink()
Creates a symbolic link.
Syntax
symlink(target, path)- If
targetis a relative path, it is resolved relative to the directory containing the link, as in Node.js, and stored as the relative path. - If
targetstarts with/, it is treated as a JSH virtual path. - If the link target would point outside the mounted area (e.g.
../../../etc/passwd), it fails withEACCES. - The Node.js third argument (
type) is not supported.
Usage example
| |
readlink()
Reads the target path of a symbolic link.
Syntax
readlink(path)- The return value is always a fully resolved absolute virtual path. Even if the link is stored as a relative path (
v1.csv), it is returned as/work/data/v1.csv. This differs from Node.js, which returns the stored string as is. - Throws
EINVALfor a path that is not a symbolic link. - Throws
ENOENTif the link points outside the mounted area.
realpath()
Returns the real path with all symbolic links, ., and .. resolved.
Syntax
realpath(path)- Symbolic links in intermediate directories are resolved as well as the last path component.
- Throws
ENOENTif a path component does not exist, andELOOPif more than 40 links must be followed, for example due to a link cycle. - The return value is a JSH virtual path.
access()
Checks whether a path exists and whether it can be accessed with the specified permissions. Returns undefined if accessible; otherwise throws an error.
Syntax
access(path[, mode])modeis a bitwise OR ofconstants.F_OK(existence, default),R_OK(read),W_OK(write), andX_OK(execute).- Throws
ENOENTif the path does not exist,EACCESif permission is denied, andEINVALifmodeis outside the range 0-7.
Notes
- On host directory mounts, the operating system permission check is used.
- On Windows, the behavior is the same as Node.js.
- Checking
W_OKon a file with the read-only attribute throwsEPERM. X_OKis treated the same as the existence check (F_OK).
- Checking
- On memory-backed, embedded, and shared file systems, the JSH process is treated as the file owner, and the decision is based on the owner permission bits of the file mode. For example, a
0o600file passesR_OK/W_OKchecks, and a0o004file fails theR_OKcheck. Groups and ACLs are not considered. W_OKchecks on read-only mounts always fail.- The state may change between checking with
access()and opening the file. Where possible, open the file directly and handle the error.
Usage example
| |
truncate()
Sets the file size to the specified length.
Syntax
truncate(path[, len])- If
lenis omitted, the file is truncated to0. - If
lenis smaller than the current size, only the firstlenbytes are kept. - If
lenis larger than the current size, the file is extended and the added part is filled with0bytes. - Throws a
RangeError(ERR_OUT_OF_RANGE) iflenis negative or not an integer. - On memory-backed mounts and the shared file system, a length larger than 1GiB fails with
EFBIG, and the file is left unchanged.
open()
Opens a file and returns a numeric file descriptor.
Syntax
open(path, flags[, mode])flags: A string flag or a numeric flag combiningconstants.O_*values.mode: Permissions used when a new file is created. The default is0o666.
String flags
| Flag | Meaning |
|---|---|
r | Read. Fails if the file does not exist |
r+ | Read/write. Fails if the file does not exist |
w | Write. Creates the file if missing, truncates it if it exists |
w+ | Read/write. Creates the file if missing, truncates it if it exists |
a | Append. Creates the file if missing |
a+ | Read/append. Creates the file if missing |
wx, wx+, ax, ax+ | Same as above, but fails with EEXIST if the file already exists |
Notes
- String flags not listed in the table above are treated as
r(read-only) without error. A typo does not raise an error, and a subsequentwrite()may fail withEBADF, so specify flag strings exactly. - On memory-backed mounts (e.g.
/lib), only read descriptors can be opened. Write flags fail withENOTSUP, so usewriteFile()/appendFile()instead. - Opening a file on a read-only (embedded) mount with write flags fails with
EACCES. - Always close descriptors with
close()when you are done.
close()
Closes a file descriptor.
Syntax
close(fd)- A descriptor that is already closed or does not exist fails with
EBADF. - On the shared file system, changes are saved when the descriptor is closed. If another process modified the same file after the descriptor was opened, it fails with
ECONFLICT.
read()
Reads data from a file descriptor into a buffer and returns the number of bytes read. Returns 0 at the end of the file.
Syntax
read(fd, buffer, offset, length[, position])buffer: AUint8Array,Buffer, or number array that receives the dataoffset: Position in the buffer to start writing. The default is0.length: Maximum number of bytes to read. The default isbuffer.length - offset.position: Position in the file to start readingnullor omitted: Reads from the current file position, and the current position advances by the number of bytes read.- Integer of 0 or greater: Reads from the specified position, and the current file position does not change.
Notes
- Throws a
RangeError(ERR_OUT_OF_RANGE) ifoffset,length, orpositionis out of range, for example whenoffset + lengthexceeds the buffer size orpositionis negative. - Reading from a descriptor opened write-only fails with
EBADF. - On Linux/macOS, reading from a directory descriptor fails with
EISDIR. Usereaddir()to read directory contents.
Usage example
| |
write()
Writes buffer or string data to a file descriptor and returns the number of bytes written.
Syntax
write(fd, data[, offset[, length[, position]]])data: AUint8Array,Buffer, number array, or string. Strings are converted to UTF-8 bytes before writing.offset: Byte position indatato start writing from. The default is0.length: Number of bytes to write. The default is fromoffsetto the end. Specifying0writes nothing.position: Position in the file to start writingnullor omitted: Writes at the current file position, and the current position advances by the number of bytes written.- Integer of 0 or greater: Writes at the specified position, and the current file position does not change. If the position is beyond the end of the file, the gap is filled with
0bytes.
Notes
- The argument order is
(fd, data, offset, length, position)even when writing a string. The Node.jswriteSync(fd, string, position, encoding)form is not supported. For example, inwrite(fd, 'Z', 3),3is interpreted asoffset, not as the file position, soERR_OUT_OF_RANGEis thrown. - For strings,
offsetandlengthare in UTF-8 bytes, not characters. - Descriptors opened in append mode, such as
aanda+, ignorepositionand always write at the end of the file. - Throws a
RangeError(ERR_OUT_OF_RANGE) ifoffset,length, orpositionis out of range. - Writing to a descriptor opened read-only fails with
EBADF. - On the shared file system, a write that makes the file larger than 1GiB fails with
EFBIG.
Usage example
| |
fstat()
Returns metadata for a file descriptor.
Syntax
fstat(fd)- The returned object provides
name,size,mode,mtime, and type methods (isFile(), etc.).atime,ctime, andbirthtimeare not included.
fchmod(), fchown()
Changes mode/owner by file descriptor.
Syntax
fchmod(fd, mode)
fchown(fd, uid, gid)- On Windows, these do nothing and succeed for file descriptors opened on host directory mounts.
- Other mounts follow their own filesystem behavior. For example, descriptors on read-only (embedded) mounts throw
EACCES.
fsync(), fdatasync()
Flushes pending file data to storage.
Syntax
fsync(fd)
fdatasync(fd)- Currently,
fdatasync()behaves the same asfsync(). - Descriptors opened on read-only (embedded) mounts and read descriptors on memory-backed mounts (for example,
/lib) have nothing to synchronize, soENOTSUPis thrown. This differs from Node.js. - On the shared file system, if another process modified the same file after the descriptor was opened, it fails with
ECONFLICT.
chmod(), chown()
Changes mode/owner by path.
On Windows, chmod/chown are no-op compatible behaviors on host directory mounts. Memory-backed, embedded, and shared file systems follow their own behavior.
Syntax
chmod(path, mode)
chown(path, uid, gid)createReadStream(), createWriteStream()
Creates stream objects compatible with EventEmitter-based usage.
Syntax
createReadStream(path[, options])
createWriteStream(path[, options])Usage example
| |
platform(), arch()
Returns runtime platform and architecture strings.
Syntax
platform()
arch()Usage example
| |
constants
Constant object containing access, copy, and file open flags.
Main fields
- Access:
F_OK(0),R_OK(4),W_OK(2),X_OK(1) - Copy:
COPYFILE_EXCL,COPYFILE_FICLONE,COPYFILE_FICLONE_FORCE - File open:
O_RDONLY,O_WRONLY,O_RDWR,O_CREAT,O_EXCL,O_TRUNC,O_APPEND
The actual values of the file open constants may differ by platform, so use the constants instead of hard-coded numbers.
Usage example
| |
Aliases
For readability, this document introduces APIs with non-Sync names.
For Node.js compatibility, Sync suffixed aliases are also provided. Both names refer to the same function, and both run synchronously. Callback- or Promise-based APIs are not provided.
Examples: readFileSync, writeFileSync, appendFileSync, readdirSync, mkdirSync, rmSync, statSync, openSync, closeSync, readSync, writeSync, fstatSync, fsyncSync, fdatasyncSync.
Examples
Example 1: Read and Parse JSON File
| |
Example 2: Write Log File
| |
Example 3: Directory Tree Walker
Non-recursive readdir() results include . and .., so they must always be skipped.
| |
If you only need all of the paths, you can use the recursive option.
| |
Example 4: File Backup
| |
Example 5: Safe File Write
Write to a temporary file first, then replace the target with rename(). Because rename() works only within the same mount, create the temporary file in the same directory as the target file.
| |
Example 6: Handling Errors by Code
| |