fs-attrs
v1.0.0
Published
Read and change basic Linux file attributes (lsattr/chattr) from Node.js
Maintainers
Readme
fs-attrs
Read and change basic file attributes directly from Node.js — the same attributes
lsattr(1) and chattr(1) operate on.
This is a native addon that calls ioctl(2), so there is no shelling out to external binaries at runtime, but a native build toolchain is required to install this module.
If you're looking to work with extended attributes (xattr), see fs-xattr.
Linux only.
Install
npm install fs-attrsBuilding from source needs a C++ toolchain and linux/fs.h (the
linux-libc-dev / kernel-headers package).
Usage
const { lsattr, chattr } = require('fs-attrs');
await lsattr('/home/me/test'); // => [ 'i' ]
await chattr('/var/log/audit.log', '+a'); // append-only
await chattr('/tmp/scratch', '-A'); // stop suppressing atime updates
await chattr('/srv/data', '=dA'); // exactly 'd' and 'A', nothing elseBoth functions also accept an open file descriptor in place of a path:
const handle = await fs.promises.open('/srv/data/file', 'r');
try {
await chattr(handle.fd, '+C');
console.log(await lsattr(handle.fd));
} finally {
await handle.close();
}API
lsattr(path: string): Promise<string[]>
lsattr(fd: number): Promise<string[]>
Resolves with the letters of the attributes that are set, in lsattr(1) order.
Only the attributes in the tables below are reported; other bits the kernel may
set (FS_EXTENT_FL, FS_DAX_FL, …) are ignored.
chattr(path: string, attributes: string): Promise<void>
chattr(fd: number, attributes: string): Promise<void>
Reads the current flags, applies attributes, and writes the result back.
attributes uses chattr(1) syntax:
| Spec | Effect |
| -------- | ---------------------------------------------------------- |
| +i | add i |
| -i | remove i |
| =i | set exactly i, clearing every other writable attribute |
| = | clear every writable attribute |
| +ci | add both c and i |
| +i-A | add i and remove A in one call |
| +i, -A | commas and spaces are ignored separators |
When the same letter appears in more than one clause, the last one wins:
'+A-A' leaves A unset.
Naming a read-only attribute (E, I, N, V) rejects with a TypeError.
Attributes
These can be read and written:
| Letter | Flag | Meaning |
| ------ | -------------------- | ----------------------------- |
| a | FS_APPEND_FL | append only |
| A | FS_NOATIME_FL | do not update atime |
| c | FS_COMPR_FL | compressed |
| C | FS_NOCOW_FL | no copy-on-write |
| d | FS_NODUMP_FL | no dump |
| D | FS_DIRSYNC_FL | synchronous directory updates |
| i | FS_IMMUTABLE_FL | immutable |
| j | FS_JOURNAL_DATA_FL | data journalling |
| P | FS_PROJINHERIT_FL | project hierarchy |
| s | FS_SECRM_FL | secure deletion |
| S | FS_SYNC_FL | synchronous updates |
| t | FS_NOTAIL_FL | no tail-merging |
| T | FS_TOPDIR_FL | top of directory hierarchy |
| u | FS_UNRM_FL | undeletable |
Which of these actually work depends on the filesystem and on your privileges.
ext4 has no C; btrfs has no j and rejects c together with C; setting i
or a requires CAP_LINUX_IMMUTABLE. Unsupported combinations surface as
ENOTSUP, EINVAL, or EPERM from the kernel.
These are read-only — lsattr reports them, chattr refuses them:
| Letter | Flag | Meaning |
| ------ | ------------------- | ------------------------------- |
| E | FS_ENCRYPT_FL | encrypted |
| I | FS_INDEX_FL | hash-indexed directory |
| N | FS_INLINE_DATA_FL | data stored inline in the inode |
| V | FS_VERITY_FL | fs-verity protected |
The filesystem sets these as a side effect of other operations — enabling
encryption or fs-verity on a file, storing a small file's data inline, or
building a hashed directory index — and FS_IOC_SETFLAGS does not accept them.
Turning them on is a job for FS_IOC_SET_ENCRYPTION_POLICY, FS_IOC_ENABLE_VERITY,
or the filesystem itself, and none of them can be turned back off.
await lsattr('/srv/protected.bin'); // => [ 'V' ]
await chattr('/srv/protected.bin', '-V'); // TypeError: attribute 'V' is read-only…Errors
Failures from open(2) and ioctl(2) reject with an Error shaped like a
Node.js system error:
try {
await chattr('/etc/passwd', '+i');
} catch (err) {
err.code; // 'EPERM'
err.errno; // -1
err.syscall; // 'ioctl'
err.path; // '/etc/passwd' (or err.fd for the fd overloads)
err.message; // "EPERM: operation not permitted, ioctl '/etc/passwd'"
}Invalid arguments — a bad target type, an empty path, an unparsable attribute
string — reject with a TypeError, and the file is never touched.
Notes
- Paths follow symlinks, matching
chattr(1)on a non-recursive invocation. - A file descriptor you pass in is never closed, but it must stay open until
the returned promise settles. A path is opened
O_RDONLY | O_NONBLOCK | O_CLOEXECand closed again. =preserves everything it cannot write. It clears only the writable attributes, so the read-only ones above and kernel-internal flags such asFS_EXTENT_FLsurvive.chattr(1)writes the whole flags word instead.- The write is unconditional, matching
chattr(1): adding an attribute that is already set still issuesFS_IOC_SETFLAGS, and can still fail withEPERM.
Development
npm install # builds the addon
npm run rebuild # rebuild after editing src/
npm testLicense
MIT
