Chapter 4. Files and Directories

This chapter centers on I/O for regular files.

restrict keyword

Added in C99, restrict keyword is used to tell the compiler which pointer references can be optimized, by indicating that the object to which the pointer refers is accessed in the function only via that pointer. [p26]

stat, fstat, fstatat, and lstat Functions


#include <sys/stat.h>

int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);

/* All four return: 0 if OK, −1 on error */

The buf argument is a pointer to a structure that we must supply. The functions fill in the structure.

struct stat {
    mode_t    st_mode;
    ino_t    st_ino;
    dev_t    st_dev;
    dev_t    st_rdev;
    nlink_t    st_nlink;
    uid_t    st_uid;
    gid_t    st_gid;
    off_t    st_size;
    struct timespec    st_atim;
    struct timespec    st_mtim;
    struct timespec    st_ctim;
    blksize_t    st_blksize;
    blkcnt_t    st_blocks;

timespec structure

The timespec structure type defines time in terms of seconds and nanoseconds. It includes at least the following fields:

time_t tv_sec;
long tv_nsec;

File Types

This program prints the type of file for each command-line argument.

Set-User-ID and Set-Group-ID

Every process has six or more IDs associated with it:

Every file has an owner and a group owner:

When we execute a program file, the effective user ID of the process is usually the real user ID, and the effective group ID is usually the real group ID. However, we can also set special flags in the file’s mode word (st_mode) that says:

These two bits in the file’s mode word are called the set-user-ID (setuid) bit and the set-group-ID (setgid) bit.

For example:

Because a process that is running set-user-ID to some other user usually assumes extra permissions, it must be written carefully.


File Access Permissions

  1. Whenever we want to open any type of file by name, we must have execute permission in each directory mentioned in the name, including the current directory, if it is implied. Read permission for a directory and execute permission for a directory mean different things. Read permission lets us read the directory, obtaining a list of all the filenames in the directory. Execute permission lets us pass through the directory when it is a component of a pathname that we are trying to access. [p100]
  2. We cannot create a new file in a directory unless we have write permission and execute permission in the directory.

Ownership of New Files and Directories

  1. The user ID of a new file is set to the effective user ID of the process
  2. The group ID of a new file can be the effective group ID of the process; or group ID of the directory in which the file is being created.

FreeBSD 8.0 and Mac OS X 10.6.8 always copy the new file’s group ID from the directory.

access and faccessat Functions


#include <unistd.h>

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);

/* Both return: 0 if OK, −1 on error */

These functions test accessibility based on the real user and group IDs.

The flag argument can be used to change the behavior of faccessat. If the AT_EACCESS flag is set, the access checks are made using the effective user and group IDs.

umask Function

The Single UNIX Specification requires that the umask command support a symbolic mode of operation. Unlike the octal format, the symbolic format specifies which permissions are to be allowed instead of which ones are to be denied.

$ umask  # first print the current file mode creation mask
$ umask -S  # print the symbolic form
$ umask 027  # print the symbolic form
$ umask -S  # print the symbolic form

chmod, fchmod, and fchmodat Functions


#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);

/* All three return: 0 if OK, −1 on error */

chmod automatically clears the following permission bits under the following conditions:

  1. Setting sticky bit on a regular file without superuser privileges (Solaris)
  2. If the group ID of the new file does not equal either the effective group ID of the process or one of the process’s supplementary group IDs and if the process does not have superuser privileges, then the set-group-ID bit is automatically turned off. On FreeBSD 8.0, Linux 3.2.0 and Mac OS X 10.6.8, if a process that does not have superuser privileges writes to a file, the set-user-ID and set-group-ID bits are automatically turned off.

Sticky Bit

Sticky Bit (S_ISVTX), or saved-text bit in the later versions of the UNIX System.

chown, fchown, fchownat, and lchown Functions


#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);

/* All four return: 0 if OK, −1 on error */

Only the superuser can change the ownership of a file (FreeBSD 8.0, Linux 3.2.0, and Mac OS X 10.6.8)

When _POSIX_CHOWN_RESTRICTED is in effect, a non-superuser can’t change the user ID of your files; A nonsuperuser can change the group ID of files that he owns, but only to groups that he belongs to.

File Size

The st_size member of the stat structure contains the size of the file in bytes. This field is meaningful only for regular files, directories, and symbolic links.

FreeBSD 8.0, Mac OS X 10.6.8, and Solaris 10 also define the file size for a pipe as the number of bytes that are available for reading from the pipe.

Most contemporary UNIX systems provide two fields:

Be aware that different versions of the UNIX System use units other than 512-byte blocks for st_blocks. Use of this value is nonportable.

Holes in a File

$ ls -l core
-rw-r--r-- 1 sar 8483248 Nov 18 12:18 core
$ du -s core
272 core

File Truncation


#include <unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);

/* Both return: 0 if OK, −1 on error */

These two functions truncate an existing file to length bytes. If the previous size of the file was greater than length, the data beyond length is no longer accessible. Otherwise, if the previous size was less than length, the file size will increase and the data between the old end of file and the new end of file will read as 0 (a hole is probably created in the file).

File Systems

Most UNIX file systems support case-sensitive filenames. On Mac OS X, however, the HFS file system is case-preserving with case-insensitive comparisons.

Figure 4.14 Cylinder group’s i-nodes and data blocks in more detail


#include <unistd.h>

int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath,
           int flag);

/* Both return: 0 if OK, −1 on error */

When a file is closed, the kernel first checks the count of the number of processes that have the file open. If this count has reached 0, the kernel then checks the link count; if it is 0, the file’s contents are deleted.

When the AT_REMOVEDIR flag is set, then the unlinkat function can be used to remove a directory, similar to using rmdir.


#include <stdio.h>

int remove(const char *pathname);

/* Returns: 0 if OK, −1 on error */

rename and renameat Functions


#include <stdio.h>

int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

/* Both return: 0 if OK, −1 on error */

It is possible to introduce loops into the file system by using symbolic links. Most functions that look up a pathname return an errno of ELOOP when this occurs.

On Linux, the ftw and nftw functions record all directories seen and avoid processing a directory more than once, so they don’t display this behavior.


#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);

/* Both return: 0 if OK, −1 on error */

Because the open function follows a symbolic link, we need a way to open the link itself and read the name in the link.


#include <unistd.h>

ssize_t readlink(const char *restrict pathname, char *restrict buf,
                 size_t bufsize);

ssize_t readlinkat(int fd, const char *restrict pathname,
                   char *restrict buf, size_t bufsize);

/* Both return: number of bytes read if OK, −1 on error */

These functions combine the actions of open, read, and close.

File Times

Field Description Example ls(1) option
st_atim last-access time of file data read -u
st_mtim last-modification time of file data write default
st_ctim last-change time of i-node status chmod, chown -c

The system does not maintain the last-access time for an i-node. The functions access and stat don’t change any of the three times.

futimens, utimensat, and utimes Functions


#include <sys/stat.h>

int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);

/* Both return: 0 if OK, −1 on error */

In both functions, the first element of the times array argument contains the access time, and the second element contains the modification time.

#include <sys/time.h>

int utimes(const char *pathname, const struct timeval times[2]);

/* Returns: 0 if OK, −1 on error */

We are unable to specify a value for the changed-status time, st_ctim (the time the i-node was last changed), as this field is automatically updated when the utime function is called.

mkdir, mkdirat, and rmdir Functions


#include <unistd.h>

int rmdir(const char *pathname);

/* Returns: 0 if OK, −1 on error */

For a directory, we normally want at least one of the execute bits enabled, to allow access to filenames within the directory.

Solaris 10 and Linux 3.2.0 also have the new directory inherit the set-group-ID bit from the parent directory. Files created in the new directory will then inherit the group ID of that directory. With Linux, the file system implementation determines whether this behavior is supported. For example, the ext2, ext3, and ext4 file systems allow this behavior to be controlled by an option to the mount(1) command.

Reading Directories


#include <dirent.h>

DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
/* Both return: pointer if OK, NULL on error */

struct dirent *readdir(DIR *dp);
/* Returns: pointer if OK, NULL at end of directory or error */

void rewinddir(DIR *dp);
int closedir(DIR *dp);
/* Returns: 0 if OK, −1 on error */

long telldir(DIR *dp);
/* Returns: current location in directory associated with dp */

void seekdir(DIR *dp, long loc);

The dirent structure defined in is implementation dependent, with at least the following two members:

    ino_t  d_ino;                 /* i-node number */
    char   d_name[];              /* null-terminated filename */

The DIR structure is an internal structure used by these seven functions to maintain information about the directory being read. The purpose of the DIR structure is similar to that of the FILE structure maintained by the standard I/O library,

chdir, fchdir, and getcwd Functions


#include <unistd.h>

int chdir(const char *pathname);
int fchdir(int fd);

/* Both return: 0 if OK, −1 on error */


#include <unistd.h>

char *getcwd(char *buf, size_t size);

/* Returns: buf if OK, NULL on error */

Device Special Files

On Linux 3.2.0, dev_t is a 64-bit integer, only 12 bits are used for the major number and 20 bits are used for the minor number. Linux defines these macros in <sys/sysmacros.h>, which is included by <sys/types.h>.

The st_dev value for every filename on a system is the device number of the file system containing that filename and its corresponding i-node.

Only character special files and block special files have an st_rdev value. This value contains the device number for the actual device.

Doubts and Solutions


Section 4.21 on rmdir [p130]:

If the link count of the directory becomes 0 with this call, and if no other process has the directory open, then the space occupied by the directory is freed. If one or more processes have the directory open when the link count reaches 0, the last link is removed and the dot and dot-dot entries are removed before this function returns. Additionally, no new files can be created in the directory.

Does "link count" here mean number of entries (except dot and dot-dot)? Otherwise, this contradicts "any leaf directory (a directory that does not contain any other directories) always has a link count of 2" in section 4.14 on page 115.