Manipulating files and directories are basic operations for any program. Since Node.js is a server side platform and can interact with the computer that it’s running on directly, being able to manipulate files is a basic feature. Fortunately, Node.js has a fs
module built into its library. It has many functions that can help with manipulating files and folders. File and directory operation that are supported include basic ones like manipulating and opening files in directories. Likewise, it can do the same for files. It can do this both synchronously and asynchronously. It has an asynchronous API that have functions that support promises. Also it can show statistics for a file. Almost all the file operations that we can think of can be done with the built in fs
module. In this article, we will get the file descriptor of a file and then use that to manipulate the permissions with the fchmod
family of functions and change ownership of the file with the fchown
family of functions.
Get the File Descriptor with the Open Function
A file descriptor of a file is an unique identifier of a file which we can use to do various operations to it. To get the file descriptor of a file, we can use the promise version of the open
function and all its variants to do so. The promise version of the open
function takes 2 arguments. The first is the file path, which can be a string, a Buffer object or an URL object. The second is the flag for what can our program do with the file after it’s opened. They can be one of the following strings:
'a'
– Opens a file for appending, which means adding data to the existing file. The file is created if it does not exist.'ax'
– Like'a'
but exception thrown if the path exists.'a+'
– Open file for reading and appending. The file is created if it doesn’t exist.'ax+'
– Like'a+'
but exception thrown if the path exists.'as'
– Opens a file for appending in synchronous mode. The file is created if it does not exist.'as+'
– Opens a file for reading and appending in synchronous mode. The file is created if it does not exist.'r'
– Opens a file for reading. An exception is thrown if the file doesn’t exist.'r+'
– Opens a file for reading and writing. An exception is thrown if the file doesn’t exist.'rs+'
– Opens a file for reading and writing in synchronous mode.'w'
– Opens a file for writing. The file is created (if it does not exist) or overwritten (if it exists).'wx'
– Like'w'
but fails if the path exists.'w+'
– Opens a file for reading and writing. The file is created (if it does not exist) or overwritten (if it exists).'wx+'
– Like'w+'
but exception thrown if the path exists.
The promise version of the open
function resolves to a file handle, which has the fd
property with the file descriptor.
Changing File Permission of File Referenced by the File Descriptor with the fs.fchmod Function
After we got the file descriptor, we can use it to change the permission of the file that’s identified by the file descriptor. To do so, we use the fchmod
function. The function takes 3 arguments. The first is the file descriptor, which is an integer. The second is the mode, which is the permission and sticky bits we want to change for the file. The mode consists of 3 octal digits, like with Unix or Linux file permissions. The leftmost digit is the permission for the owner. The middle digit is the permission of the group, and the rightmost digit is the permission for others. Valid number include the following:
7
— read, write, and execute6
— read and write5
— read and execute4
— read only3
— write and execute2
— write only1
— execute only0
— no permission
The final argument is a callback which is called when the file permission change operation ends. It has an err
parameter which is null
when the operation succeeds and has an object with the error information when the operation fails.
We can use the fchmod
function to change the file permission asynchronously like in the following code:
const fsPromises = require("fs").promises;
const fs = require("fs");(async () => {
try {
const { fd } = await fsPromises.open("./files/file.txt", "r");
fs.fchmod(fd, 0o777, err => {
if (err) throw err;
console.log("File permission change succcessful");
});
} catch (error) {
console.log(error);
}
})();
To confirm that the fchmod
function worked as we expected, we can do the run the following commands in Linux systems. One way is to run ls -l
, like in the command below:
ls -l ./files/file.txt
When we run that, we get some output like:
-rwxrwxrwx 1 hauyeung hauyeung 16 Nov 2 12:26 ./files/file.txt
The leftmost column has the permissions. rwxrwxrwx
is the same as 777
which means that the fchmod
function call worked correctly.
We can also use the stat
command.
File: './files/file.txt'
Size: 16 Blocks: 0 IO Block: 512 regular file
Device: eh/14d Inode: 22799473115106242 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1000/hauyeung) Gid: ( 1000/hauyeung)
Access: 2019-11-02 12:26:47.996290200 -0700
Modify: 2019-11-02 12:26:47.996290200 -0700
Change: 2019-11-03 10:34:58.342785500 -0800
Birth: -
We can see from the Access
field that we have 0777
as the permission, which means that the fchmod
operation worked correctly.
There’s also a synchronous version of the fchmod
function called the fchmodSync
function. The function takes 2 arguments. The first is the file descriptor, which is an integer. The second is the mode, which is the permission and sticky bits we want to change for the file. The mode should be the same as the ones we have above. It returns undefined
.
We can use it along with the openSync
function, which is the synchronous version of the open
function to get the file descriptor, and then use the file descriptor returned from the openSync
function as an argument for the fchmodSync
function, like in the following code:
const fs = require("fs");try {
const fd = fs.openSync("./files/file.txt", "r");
fs.fchmodSync(fd, 0o777);
console.log("File permission change succcessful");
} catch (error) {
console.log(error);
}
The openSync
function takes the same arguments as the open
function but without the callback function as the third argument. When we run the code above, we should get the same output as the asynchronous version that we have previously.
Changing File Ownership of File Referenced by the File Descriptor with the fs.fchown Function
The fchown
function let us change the ownership of a file asynchronously. It takes 4 arguments. The first argument is the file descriptor which is an integer. The second argument is the UID, which is the user ID of the user. The third argument is the GID, which is the group ID, and the fourth argument is a callback function which has an err
parameter which is null
is the chown
operation is successful, otherwise err
will be an error object. The callback function is called when the chown
operation is finished whether it’s successful or not.
To use the fchown
function, we can write something like the following code:
const fsPromises = require("fs").promises;
const fs = require("fs");(async () => {
try {
const { fd } = await fsPromises.open("./files/file.txt", "r");
fs.fchown(fd, 1000, 1000, err => {
if (err) throw err;
console.log("File permission change succcessful");
});
} catch (error) {
console.log(error);
}
})();
with UID 1000 and group with GID 1000. To find out the UID and GID of the user and group of the current user in Linux systems, we can use the id
command.
The fchown
function has a synchronous version called fchownSync
. It takes 3 arguments. The first argument is the file descriptor which is an integer. The second argument is the UID, which is the user ID of the user. The third argument is the GID, which is the group ID. It returns undefined
. We can use it along with the openSync
function to get the file descriptor like in the following code:
const fs = require("fs");try {
const fd = fs.openSync("./files/file.txt", "r");
fs.fchownSync(fd, 1000, 1000);
console.log("File permission change succcessful");
} catch (error) {
console.log(error);
}
To confirm that the fchown
or fchownSync
worked as we expected, we can do the run the following commands in Linux systems. One way is to run ls -l
, like in the command below:
ls -l ./files/file.txt
We can also use the stat
command. For example, we can run stat ./files/file.txt
to get the ownership information of ./files/file.txt
from the Uid
and Gid
output. With the stat
command, we should get output that looks something like:
File: './files/file.txt'
Size: 16 Blocks: 0 IO Block: 512 regular file
Device: eh/14d Inode: 22799473115106242 Links: 1
Access: (0555/-r-xr-xr-x) Uid: ( 1000/hauyeung) Gid: ( 1000/hauyeung)
Access: 2019-11-02 12:26:47.996290200 -0700
Modify: 2019-11-02 12:26:47.996290200 -0700
Change: 2019-11-02 12:44:45.037581600 -0700
Birth: -
Also, we can filter out the information we don’t need and just get the UID and GID with the "%U %G"
format sequence. For example, we can run stat -c “%U %G” ./files/file.txt
to get the UID and GID only for file.txt
.
With the fchown
function, we can change the ownership of a file given the file descriptor of the file. There are 2 asynchronous versions of the function, one is the regular asynchronous version and one is the promise version. There’s also one synchronous version of the chown
function, which is the chownSync
function. They both change the owner by passing in the UID and GID of the user of your choice. UID is the user ID and GID is the group ID. We can change the permission of the file identified with the file descriptor with the fchown
function. . There are 2 asynchronous versions of the fchown
function, one is the regular asynchronous version and one is the promise version. There’s also a synchronous version of the fchown
function called the fchownSync
function. We can confirm that the operations worked with the ls -l
or stat
commands on POSIX systems.