This corresponds to chapter 4 of the original book (Files and Table of Contents)
Files and Directories
This chapter is mainly about opening files, reading and writing files. Start with the stat function. The stat series of functions are used to return information about a file. Most of the information can be viewed using the ls -l command in a stat structure
The stat function has four variants
// Return 0 for success, -1 for error
#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);
Copy the code
The actual definition of the stat structure may vary, but the basic form is
struct stat {
mode_t st_mode; /* File type and access permissions */
ino_t st_ino; /* File I node number */
dev_t st_dev; /* Device serial number */
dev_t st_rdev; /* The serial number of the special file */
nlink_t st_nlink; /* Number of links */
uid_t st_uid; /* File owner's uid */
gid_t st_gid; /* File owner gid */
off_t st_size; /* For normal files, this field indicates the size of the file */
struct timespec st_atim; /* The last time the file was accessed */
struct timespec st_mtim; /* Time when the file was last modified */
struct timespec st_ctim; /* The last time the file state changed */
blksize_t st_blksize; /* Optimal I/O block size */
blkcnt_t st_blocks; /* Number of allocated disks */
};
Copy the code
We introduce a few major fields
1. File type
The following file types are available
Common file
. These are common files, and it makes no difference to the Unix kernel whether the data is text or binary. Interpretation of the contents of a normal file is performed by the application that processes the fileDirectory file
. Such files contain the names of other files and Pointers to information directly related to those files. Any process that has read files on a directory can read the contents of that directory, but only the kernel can write directory files directly.Block special file
. This type of file provides buffered access to devices, such as disks, in units of fixed length at a time.Character special file
. This type of file provides unbuffered access to the device with variable length per access. All devices in the system are either character-special files or block-special filesFIFO
. Named pipes, this type of file is used for interprocess communicationsocket
. Socket for interprocess network communicationA symbolic link
. This type of file points to another file
Macros can be used to determine the st_mode field in a STAT structure to determine the type of file
macro | type of file |
---|---|
S_ISREG() | Common file |
S_ISDIR() | directory |
S_ISCHR() | Character special file |
S_ISBLK() | Block special file |
S_ISFIFO() | The pipe |
S_ISLNK() | A symbolic link |
S_ISSOCK() | socket |
S_TYPEISMQ() | The message queue |
S_TYPEISSEM() | A semaphore |
S_TYPEISSHM() | The Shared memory |
Let’s look at a case code from the book
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr;
for (i = 1; i < argc; i++) {
printf("%s: ", argv[i]);
if (lstat(argv[i], &buf) < 0) {
err_ret("lstat error");
continue;
}
if (S_ISREG(buf.st_mode))
ptr = "regular";
else if (S_ISDIR(buf.st_mode))
ptr = "directory";
else if (S_ISCHR(buf.st_mode))
ptr = "character special";
else if (S_ISBLK(buf.st_mode))
ptr = "block special";
else if (S_ISFIFO(buf.st_mode))
ptr = "fifo";
else if (S_ISLNK(buf.st_mode))
ptr = "symbolic link";
else if (S_ISSOCK(buf.st_mode))
ptr = "socket";
else
ptr = "** unknown mode **";
printf("%s\n", ptr);
}
exit(0);
}
Copy the code
This program basically prints the file specified by the command line argument, using lstat so that if you open a symbolic link, it opens the file to which it points
Let’s compile the executable first and then test it (I can’t find the directory for the pipe on Ubuntu so it’s one less than the original book)
$ gcc filetype.c -lapue
$ ./a.out /etc/passwd /etc /dev/log /dev/tty /dev/sda /dev/stdin
Copy the code
Access permissions
The ids associated with a process have 6 or more actual user ids, actual group ids, valid user ids, valid group ids, subordinate group ids, saved Settings user ids, Saved Settings group ID The user ID and group ID of the Settings file are contained in the STAT structure st_mode, which also contains the access permission bit for the file. Each file has nine access bits
The access and faccessat functions
Access and Faccessat test permissions based on actual user ids and actual group ids
#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
// Returns 0 on success, -1 on error
Copy the code
Mode flag
If the pathname parameter is an absolute directory, or fd is AT_FDCWD and pathname is a relative parameter. Flag is used to change the behavior of the faccessat function. If flag is set to AT_EACCESS, access checks use the valid user ID and group ID of the process, not the actual user ID and group ID. Let’s look at an example from the book
#include "apue.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
if(argc ! =2)
err_quit("usage: a.out <pathname>");
if (access(argv[1], R_OK) < 0)
err_ret("access error for %s", argv[1]);
else
printf("read access OK\n");
if (open(argv[1], O_RDONLY) < 0)
err_ret("open error for %s", argv[1]);
else
printf("open for reading OK\n");
exit(0);
}
Copy the code
Let’s compile the example from the debug book
$ gcc access.c -lapue
$ ls -l a.out
-rwxrwxr-x 1 yuanzhihong yuanzhihong 17584 Jun 5 21:16 a.out
$ ./a.out a.out
read access OK
open for reading OK
$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1085 Jun 5 21:15 /etc/shadow
$ ./a.out /etc/shadow
access error for /etc/shadow: Permission denied
open error for /etc/shadow: Permission denied
$ su root
Password:
# ./a.out /etc/shadow
read access OK
open for reading OK
# chown root a.out
# chmod u+s a.out
# su yuanzhihong
$ ls -l a.out
-rwsrwxr-x 1 root yuanzhihong 17584 Jun 5 21:16 a.out
$ ./a.out /etc/shadow
access error for /etc/shadow: Permission denied
open for reading OK
Copy the code
In this case,Open can open a file, but if you set the user ID of the file, the program will assume that the actual user cannot read the specified file
Umask function
Umask creates a mask word for the process setting file mode and returns the original file mask word
#include <sys/stat.h>
mode_t umask(mode_t cmask);
// Returns the original file masking word, where the cmask argument is bitbit or of constants such as S_IRUSR
Copy the code
When a process creates a file or directory, it creates a mask word using file mode. The open and creat functions have a mode argument that specifies the masked word. Let’s do the example in the book. Create two files, one called foo and one called bar. When the first file is created, the umask value is 0. When the second file is created, the umask value is denied to the group and other users
#include "apue.h"
#include <fcntl.h>
#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
int main(void)
{
umask(0);
if (creat("foo", RWRWRW) < 0)
err_sys("creat error for foo");
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (creat("bar", RWRWRW) < 0)
err_sys("creat error for bar");
exit(0);
}
Copy the code
Let’s compile and test it
$ gcc umask.c -lapue
$ umask
022
$ ./a.out
$ ls -l foo bar
-rw------- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 bar
-rw-rw-rw- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 foo
$ umask
022
Copy the code
As you can see, changing a process’s file mode to create a mask word does not affect its parent process’s mask word
Chmod series of functions
The chmod family of functions is used to change access to existing files
#include <sys/stat.h>
int chmode(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);
Return 0 on success, -1 on error
Copy the code
Chmod operates on specified files, while fchmod operates on already opened files. The fchmodat function and chmod function are the same in the following two cases: one is that pathname is an absolute directory; the other is that fd is AT_FDPWD and pathname is a relative path. The flag parameter can change the behavior of fchmodat. When AT_SYMLINK_NOFOLLOE is set, symbolic links will not be followed. In order to change the permission bit of a file, the valid user ID of the process must be equal to the owner ID of the file, or the process must have superuser privileges.
Let’s do an example from the book
#include "apue.h"
int main(void)
{
struct stat statbuf;
/* turn on set-group-ID and turn off group-execute */
if (stat("foo", &statbuf) < 0)
err_sys("stat error for foo");
if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("chmod error for foo");
/* set absolute mode to "rw-r--r--" */
if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
err_sys("chmod error for bar");
exit(0);
}
Copy the code
Let’s compile and test this program
$ gcc changemod.c -lapue $ ls -l foo bar -rw------- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 bar -rw-rw-rw- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 foo $ ./a.out $ ls -l foo bar -rw-r--r-- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 bar -rw-rwSrw- 1 yuanzhihong yuanzhihong 0 Jun 5 21:36 fooCopy the code
Chown series of functions
The chown series of functions are used to change the user ID and group ID of a file. If either of the owner and group parameters is -1, the corresponding ID remains unchanged
#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 fchwonat(int fd, const char *pathname, uid_t owner, gid_t group);
int lchwon(const char *pathname, uid_t owner, gid_t group);
// Return 0 for success, -1 for error
Copy the code
The truncation function
To truncate a file, use the truncate function and the ftruncate function
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
// If successful, return 0; Returns -1 on failure
Copy the code
If the previous length is less than the specified length, the file length is increased; If the file becomes longer, zeros are filled in between the end of the previous file and the end of the new file.
The file system
You can divide a disk into one or more partitions. Each partition can contain one file system. Nodes are fixed-length record entries that contain most of the information about the file.
Link series functions
Any file can have more than one directory entry pointing to its I node. To create a link to an existing file, use the link function or linkat function
#include <unistd.h>
int link(const char *pathname, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
Return 0 on success, -1 on failure
Copy the code
These two functions create a new directory entry, newPath, which refers to the existing file ExistingPath. If newPath already exists, an error is returned. Only the last component in newPath is created; the rest of the path should already exist. For the linkat function, the existing file is specified with efd and existingpath arguments, and the newpathname is specified with NFD and newpathname. By default, if either pathname is a relative path, it needs to be evaluated by the corresponding file descriptor. If either of the two file descriptors is set to AT_FDCWD, the corresponding pathname is computed from the relative directory. If either pathname is an absolute path, the corresponding file descriptor argument is ignored. When an existing file is a symbolic link, the flag parameter controls whether the linkat function creates a link to an existing symbolic link or a link to an existing symbolic link target. If the AT_SYMLINK_FOLLOW flag is set in flag, a link to the symbolic link target is created. If the flag is cleared, create a link to the symbolic link itself to remove an existing directory entry, call the unlink function
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
// If successful, return 0; Returns -1 on failure
Copy the code
These two functions remove the directory entry and subtract one from the link count of the file referenced by pathName. If there are other links to the file, the file’s data can still be accessed through those other links. If an error occurs, no changes are made to the file. In order to unlink a file, you must have write and execute permission on the directory entry. If pathName is a relative path, the unlinkat function computes the pathname relative to the directory represented by the fd file descriptor argument. If the fd parameter is set to AT_FDCWD, the pathname is computed relative to the current working directory of the calling process. If pathName is an absolute pathname, the fd argument is ignored. The flag parameter gives a way to change the default behavior of unlinkat. When the AT_REMOVEDIR flag is set, the unlinkat function can be similar to rmdir to remove directories. If the flag is cleared, unlinkat performs the same operation as Unlink.
Let’s look at the example from the book
#include "apue.h"
#include <fcntl.h>
int main(void)
{
if (open("tempfile", O_RDWR) < 0)
err_sys("open error");
if (unlink("tempfile") < 0)
err_sys("unlink error");
printf("file unlinked\n");
sleep(15);
printf("done\n");
exit(0);
}
Copy the code
Let’s compile and debug this program
$ gcc unlink.c -lapue
$ ls -l tempfile
-rw-r--r-- 1 yuanzhihong yuanzhihong 242 Jun 6 10:44 tempfile
$ df /home
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb 263174212 5544872 244191184 3% /
$ ./a.out& ## execute in background
[1] 2947
file unlinked
$ ls -l tempfile The ## file has been deleted
ls: cannot access 'tempfile': No such file or directory
$ df /home ## You can see that there is more space available. I am using a different Ubuntu than the original book
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb 263174212 5544920 244191136 3% /
done Program execution completed
[1] + 2947 done ./a.out
$ df /home
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb 263174212 5544920 244191136 3% /
Copy the code
This feature of Unlink is often used to ensure that if a program crashes, the temporary files it creates are not left behind. The contents of a file are removed only when the process closes or terminates (operating system specific). We can also use the remove function to remove a link to a file or directory. For files, remove has the same function as Unlink. For directories, remove has the same function as RMdir
#include <stdio.h>
int remove(const char *pathname);
Return 0 on success, -1 on failure
Copy the code
Rename function
Files can be renamed using either the rename or renameat functions
#include <stdio.h>
int rename(const char *oldname, const char *newpathname);
int renameat(int oldfd, const char *oldpathname, int newfd, const char *newname);
Copy the code
If newname already exists and is not a directory, delete the directory entry and rename oldName to newname. If it is a referenced symbolic link, the symbolic link itself is processed, not the linked file
Create and read symbolic links
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
// Returns 0 on success, -1 on error
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);
// Read the symbolic link itself
// If successful, return the number of bytes read; If there is an error, return -1
Copy the code
Time of file
Each file maintains three time fields
Function to change file access and modify time
#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);
#include <sys/time.h>
int utime(const char *pathname, const struct timeval times[2]);
// Returns 0 on success; Returns -1 on failure
// The first element of times contains the access time and the second element contains the modification time
Copy the code
Let’s take an example from the book: get the last modification and access time of the file, truncate it, and reset the original time back
#include "apue.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
int i, fd;
struct stat statbuf;
struct timespec times[2].
for (i = 1; i < argc; i++) {
if (stat(argv[i], &statbuf) < 0) { /* fetch current times */
err_ret("%s: stat error", argv[i]);
continue;
}
if ((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0) { /* truncate */
err_ret("%s: open error", argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) /* reset times */
err_ret("%s: futimens error", argv[i]);
close(fd);
}
exit(0);
}
Copy the code
Let’s compile and debug this program
$ ls -l changemod.c -rw-r--r-- 1 yuanzhihong yuanzhihong 429 Jun 5 21:48 changemod.c $ ls -lu changemod.c -rw-r--r-- 1 yuanzhihong yuanzhihong 429 Jun 5 21:49 changemod.c $ date Sun Jun 6 11:24:42 CST 2021 $ gcc zap.c -lapue $ ./a.out changemod.c $ ls -l changemod.c -rw-r--r-- 1 yuanzhihong yuanzhihong 0 Jun 5 21:48 changemod.c $ ls -lu changemod.c -rw-r--r-- 1 yuanzhihong yuanzhihong 0 Jun 5 21:49 changemod.c $ ls -lc changemod.c -rw-r--r-- 1 yuanzhihong yuanzhihong 0 Jun 6 11:25 changemod.cCopy the code
As we can see, the last modification time and the last access time are unchanged. However, the state change time is changed to the time when the program is running
Directory dependent function
Use the mkdir and mkdirat functions to create directories and the rmdir function to delete directories
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
// Return 0 on success, -1 on error
#include <unistd.h>
int rmdir(const char *pathname);
// You can delete an empty directory, return 0 on success, -1 on error
Copy the code
Read directories Pointers to the DIR structure returned by opendir or fdopendir are used by the other five functions. Readdir returns the first directory entry in a directory.
#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dp);
// Returns a pointer on success, NULL on error
void rewinddir(DIR *dp);
int closedir(DIR *dp);
// Returns 0 on success, -1 on error
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);
Copy the code
Let’s take an example from the book
#include "apue.h"
#include <dirent.h>
#include <limits.h>
/* function type that is called for each filename */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int
main(int argc, char *argv[])
{
int ret;
if(argc ! =2)
err_quit("usage: ftw
"
);
ret = myftw(argv[1], myfunc); /* does it all */
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
if (ntot == 0)
ntot = 1; /* avoid divide by 0; print 0 for all counts */
printf("Regular Files = %7ld, %5.2f %%\n", nreg,
nreg*100.0/ntot);
printf("Directories = %7ld, %5.2f %%\n", ndir,
ndir*100.0/ntot);
printf("Block Special = %7ld, %5.2f %%\n", nblk,
nblk*100.0/ntot);
printf("Char special = %7ld, %5.2f %%\n", nchr,
nchr*100.0/ntot);
printf("FIFOs = %7ld, %5.2f %%\n", nfifo,
nfifo*100.0/ntot);
printf("symbolic links = %7ld, %5.2f %%\n", nslink,
nslink*100.0/ntot);
printf("Sockets = %7ld, %5.2f %%\n", nsock,
nsock*100.0/ntot);
exit(ret);
}
/* * Descend through the hierarchy, starting at "pathname". * The caller's func() is called for every file. */
#define FTW_F 1 /* file other than directory */
#define FTW_D 2 /* directory */
#define FTW_DNR 3 /* directory that can't be read */
#define FTW_NS 4 /* file that we can't stat */
static char *fullpath; /* contains full pathname for every file */
static size_t pathlen;
static int /* we return whatever func() returns */
myftw(char *pathname, Myfunc *func)
{
fullpath = path_alloc(&pathlen); /* malloc PATH_MAX+1 bytes */
/* ({Prog pathalloc}) */
if (pathlen <= strlen(pathname)) {
pathlen = strlen(pathname) * 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL)
err_sys("realloc failed");
}
strcpy(fullpath, pathname);
return(dopath(func));
}
/* * Descend through the hierarchy, starting at "fullpath". * If "fullpath" is anything other than a directory, we lstat() it, * call func(), and return. For a directory, we call ourself * recursively for each name in the directory. */
static int /* we return whatever func() returns */
dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret, n;
if (lstat(fullpath, &statbuf) < 0) /* stat error */
return(func(fullpath, &statbuf, FTW_NS));
if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
return(func(fullpath, &statbuf, FTW_F));
/* * It's a directory. First call func() for the directory, * then process each filename in the directory. */
if((ret = func(fullpath, &statbuf, FTW_D)) ! =0)
return(ret);
n = strlen(fullpath);
if (n + NAME_MAX + 2 > pathlen) { /* expand path buffer */
pathlen *= 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL)
err_sys("realloc failed");
}
fullpath[n++] = '/';
fullpath[n] = 0;
if ((dp = opendir(fullpath)) == NULL)
return(func(fullpath, &statbuf, FTW_DNR));
while((dirp = readdir(dp)) ! =NULL) {
if (strcmp(dirp->d_name, ".") = =0 ||
strcmp(dirp->d_name, "..") = =0)
continue; /* Ignore "." and ".." * /
strcpy(&fullpath[n], dirp->d_name); /* Appends */ after "/"
if((ret = dopath(func)) ! =0) /* Recursively uses */
break;
}
fullpath[n- 1] = 0; /* erase everything from slash onward */
if (closedir(dp) < 0)
err_ret("can't close directory %s", fullpath);
return(ret);
}
static int
myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch (type) {
case FTW_F:
switch (statptr->st_mode & S_IFMT) {
case S_IFREG: nreg++; break;
case S_IFBLK: nblk++; break;
case S_IFCHR: nchr++; break;
case S_IFIFO: nfifo++; break;
case S_IFLNK: nslink++; break;
case S_IFSOCK: nsock++; break;
case S_IFDIR: /* directories should have type = FTW_D */
err_dump("for S_IFDIR for %s", pathname);
}
break;
case FTW_D:
ndir++;
break;
case FTW_DNR:
err_ret("can't read directory %s", pathname);
break;
case FTW_NS:
err_ret("stat error for %s", pathname);
break;
default:
err_dump("unknown type %d for pathname %s", type, pathname);
}
return(0);
}
Copy the code
The section about the table of contents is (and most difficult)
Compile and debug this program
$ gcc ftw8.c -lapue $ ./a.out .. /.. / ue.3e Regular files = directories = 368, 60.43 % directories = 33, 5.42 % block special = 0, 0.00 % char special = 0, 0.00 % FIFOs = 0, 0.00 % Symbolic Links = 208, 34.15% Sockets = 0, 0.00 %Copy the code
Chdir family of functions
Each process has a current working directory, which is the starting point for searching all relative pathnames. The process calls chdir or fchdir to change the current working directory.
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
// If 0 is returned, success is achieved. If -1 is returned, failure is achieved
Copy the code
An example from the book
#include "apue.h"
int main(void)
{
if (chdir("/tmp") < 0)
err_sys("chdir failed");
printf("chdir to /tmp succeeded\n");
exit(0);
}
Copy the code
Compile and debug
$ gcc mycd.c -lapue
$ pwd
/home/yuanzhihong/learing/apue/apue.3e/filedir
$ ./a.out
chdir to /tmp succeeded
$ pwd
/home/yuanzhihong/learing/apue/apue.3e/filedir
Copy the code
You can see that. The current working directory of the shell executing mycd has not changed
#include <unistd.h>
char *getcwd(char *buf, size_t size);
// Get the current absolute directory, buff on success, NULL on failure
Copy the code