File attributes and directories

In the previous chapters, there was a series of discussions around ordinary file I/O operations, such as opening files, reading and writing files, closing files, etc. This chapter will put aside file I/O related topics and discuss the Linux file system. Other features and file-related attributes; we will start with the system call stat, which can be used to return a structure containing a variety of file attributes (including file timestamp, file ownership, file permissions, etc.), and explain each element in the stat structure one by one. A member will learn all the attributes of a file, and then will introduce you to various system calls used to change file attributes; in addition, you will also be introduced to symbolic links and directory-related operations in the Linux system.

File types in Linux systems (7 types)

Everything under Linux is a file. As the core concept of Linux system design, files are particularly important under Linux system. In the previous chapters, we took ordinary files (text files, binary files, etc.) as examples to explain knowledge related to file I/O; although most files in the Linux system are ordinary files, they are not There are more than just ordinary files, so this section will introduce you to the file types in the Linux system.

Under the Windows system, the operating system generally identifies the file type through the file name suffix, such as C language header file .h, C language source file .c, .txt text file, compressed package file .zip, etc., in the Windows operating system When opening a file, the file name suffix will be recognized first to get the file type, and then the corresponding program will be called to open it; for example, for a .c file, a C code editor will be used to open it; for a .zip file, You will use decompression software to open it.

However, under the Linux system, the type of a file is not identified by the file suffix name, but this does not mean that everyone can add a suffix to the file casually; both the file name and the suffix are for "people" to see. Although the Linux system does not identify files by suffix, file suffixes must also be standardized and added according to the functional attributes of the file itself. For example, C source files have a .c suffix, and C header files have a .h suffix. The shell script file has the .sh suffix for our own convenience to view and browse.

There are a total of 7 file types under the Linux system, which are introduced to you in turn below.

Ordinary document

Regular files are the most common in Linux systems, such as text files and binary files. The source code files we write are all regular files, which are files in a general sense. The data in the ordinary file is stored in the system disk, and the content in the file can be accessed. The content in the file is stored and accessed in bytes.

Ordinary files can be divided into two major categories: text files and binary files.

⚫ Text file: The content in the file is composed of text. The so-called text refers to ASCII code characters. The contents in the file are essentially numbers (because the computer itself only has 0 and 1, and the file contents stored on the disk are also composed of 0 and 1), and the numbers in the text file should be understood as the numbers represented by this number. Corresponding ASCII character codes; such as common .c, .h, .sh, .txt, etc. are all text files. The advantage of text files is that they are convenient for people to read, browse and write.
⚫ Binary files: What is stored in binary files are essentially numbers, but for binary files, these numbers are not text character encodings, but real numbers. For example, executable files under Linux systems, .o files, and .bin files obtained after compiling C code are all binary files.

Under Linux systems, you can use the stat command or the ls command to view the file type, as shown below:

Insert image description here
Insert image description here

The stat command is very friendly and will intuitively display the file type;

For the ls command, the file type is not displayed intuitively, but is represented by symbols. In the string of characters displayed in the red box in Figure 5.1.2, the first character (' - ' ) is used to indicate the file type, and the minus sign '-' indicates that the file is an ordinary file; in addition, let's take a look at what characters are used to represent other file types:

⚫ ' - ': Ordinary file
⚫ ' d ': Directory file
⚫ ' c ': Character device file
⚫ ' b ': Block device file
⚫ ' l ': Symbolic link file
⚫ ' s ': Socket file
⚫ ' p ': pipe file

directory file

A directory is a folder. A folder is also a kind of file in the Linux system, a special file. Similarly, we can also use the vi editor to open the folder, as shown below:

Insert image description here

As you can see, the folder contains the path of the folder and the files stored in the folder. As a special file, the folder itself is not suitable for reading and writing using the file I/O method introduced earlier. Under the Linux system, there will be some special system calls for reading and writing folders. This part I’ll introduce it to you later.

Character device files and block device files

Readers who have studied Linux driver programming development should be familiar with file types such as character device files (character) and block device files (block). Under the Linux system, everything is a file, including various hardware devices. Device files (character device files, block device files) correspond to hardware devices. In Linux systems, hardware devices will correspond to a device file. Applications can control and use hardware devices, such as LCD displays, by reading and writing device files. screen, serial port, audio, buttons, etc. In the advanced part of this tutorial, we will introduce to you how to control and use hardware devices through device files.

In Linux systems, hardware devices can be divided into character devices and block devices, so there are two file types: character device files and block device files. Although there is a device file, the device file does not correspond to a file on the disk. That is to say, the device file does not exist on the disk, but is virtualized by the file system and is generally maintained by memory. When the system is shut down, The device files will disappear; character device files are generally stored in the /dev/ directory of the Linux system, so /dev is also called the virtual file system devfs. Taking the Ubuntu system as an example, as follows:

Insert image description here

In the above figure, agpgart, autofs, btrfs-control, console, etc. are all character device files, while loop0 and loop1 are block device files.

Symbolic link file

A symbolic link file (link) is similar to a shortcut file in Windows systems. It is a special file whose content points to another file path. When a symbolic link file is operated on, the system will transfer the operation according to the situation. Go to the file it points to instead of operating on itself. For example, when reading the contents of a symbolic link file, you actually read the contents of the file it points to.

If you understand the shortcuts under Windows, it will be easy to understand the symbolic link files under Linux. The cdrom, cdrw, fd, initctl and other files in Figure 5.1.4 are all symbolic link files, and the file path pointed by the arrow is the file pointed to by the symbolic link file.

pipeline file

Pipe files (pipe) are mainly used for inter-process communication. I will explain it in detail when I learn the relevant knowledge.

socket file

Socket files are also a way of inter-process communication. Different from pipe files, they can communicate between processes on different hosts. In fact, they are network communications. When you learn the knowledge related to network programming, you can Introduce everyone.

stat function

You can use the stat command under Linux to view the file attributes. In fact, this command internally obtains the file attributes by calling the stat() function. The stat function is a system call in Linux and is used to obtain file-related information. The function prototype is as follows (Can be viewed through the "man 2 stat" command):

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);

The meanings of function parameters and return values ​​are as follows:
pathname: used to specify a file path whose attributes need to be viewed.
buf: struct stat type pointer, used to point to a struct stat structure variable. When calling the stat function, you need to pass in a pointer to the struct stat variable. The obtained file attribute information is recorded in the struct stat structure. I will introduce to you what information is recorded in the struct stat structure later.
Return value: 0 is returned on success; -1 is returned on failure and error is set.

struct stat structure

struct stat is a structure defined by the kernel and declared in the <sys/stat.h> header file, so it can be used at the application layer. All elements in this structure add up to form the attribute information of the file. The content of the structure is as follows Shown:

struct stat
{
    
    
    dev_t st_dev;            /* 文件所在设备的ID */
    ino_t st_ino;            /* 文件对应inode 节点编号*/
    mode_t st_mode;          /* 文件对应的模式*/
    nlink_t st_nlink;        /* 文件的链接数*/
    uid_t st_uid;            /* 文件所有者的用户ID */
    gid_t st_gid;            /* 文件所有者的组ID */
    dev_t st_rdev;           /* 设备号(指针对设备文件)*/
    off_t st_size;           /* 文件大小(以字节为单位)*/
    blksize_t st_blksize;    /* 文件内容存储的块大小*/
    blkcnt_t st_blocks;      /* 文件内容所占块数*/
    struct timespec st_atim; /* 文件最后被访问的时间*/
    struct timespec st_mtim; /* 文件内容最后被修改的时间*/
    struct timespec st_ctim; /* 文件状态最后被改变的时间*/
};

st_dev: This field is used to describe the device where this file is located. If it is not commonly used, you can ignore it.
st_ino: the inode number of the file.
st_mode: This field is used to describe the mode of the file. For example, the file type and file permissions are recorded in this variable. For an introduction to this variable, please see Section 5.2.2.
st_nlink: This field is used to record the number of hard links of the file, that is, how many hard link files have been created for the file. Link files can be divided into soft link (symbolic link) files and hard link files. I will introduce these contents later.
st_uid, st_gid: These two fields are used to describe the user ID of the file owner and the group ID of the file owner respectively. We will introduce them to you later.
st_rdev: This field records the device number. The device number is only for device files, including character device files and block device files, so ignore it.
st_size: This field records the size (logical size) of the file in bytes.
st_atim, st_mtim, st_ctim: These three fields are used to record the time when the file was last accessed, the time when the file content was last modified, and the time when the file status was last changed. They are all struct timespec type variables. Please see 5.2 for detailed introduction. 3 bars.

st_mode variable

st_mode is a member variable in the struct stat structure. It is a 32-bit unsigned integer data. This variable records the file type and file permissions. Its representation method is as follows:

Insert image description here

When you see Figure 5.2.1, do you feel familiar? Indeed, a similar figure was used when introducing the third parameter mode of the open function in the previous chapter, as shown in Figure 2.3.2. The only difference is that the mode parameter of the open function only involves
the 12 bits S, U, G, and O, and does not include the 4 bits used to describe the file type.
The 3 bits corresponding to O are used to describe the permissions of other users; the
3 bits corresponding to G are used to describe the permissions of users in the same group; the 3 bits corresponding to
U are used to describe the permissions of the file owner; the 3 bits corresponding to
S are used to describe the permissions of the file owner; 3 bits are used to describe the special permissions of the file.
The expression content of these bits corresponds to the mode parameter of the open function and will not be repeated here. Similarly, the macro definitions representing permissions in the mode parameter can also be used here. These macro definitions are as follows (the following numbers are expressed in octal notation):

S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others (not in group) have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission

For example, to determine whether the file owner has executable permissions on the file, you can test it through the following method (assuming st is a struct stat type variable):

if (st.st_mode & S_IXUSR) {
    
    
	//有权限
} else {
    
    
	//无权限
}

Here we focus on the 4 bits of "file type". These 4 bits are used to describe the type of the file. For example, whether the file is an ordinary file, a link file, or a directory, etc., then you can It can be judged by these 4 bit data, as shown below:

S_IFSOCK 0140000 socket(套接字文件)
S_IFLNK 0120000 symbolic link(链接文件)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道文件)

Note that the above numbers are represented in octal mode. In C language, to represent a number in octal mode, a 0 (zero) needs to be added in front of the number. So it can be seen from the above that when the number corresponding to the 4 bits of "File Type" is 14 (octal), it means that the file is a socket file. When the number corresponding to the 4 bits of "File Type" is 12 (octal), it means that the file is a link file; when the number corresponding to the 4 bits of "file type" is 10 (octal), it means that the file is an ordinary file, etc.
Therefore, it is very simple to determine the file type through the st_mode variable, as follows (assuming that st is a struct stat type variable):

/* 判断是不是普通文件*/
if ((st.st_mode & S_IFMT) == S_IFREG)
{
    
    
    /* 是*/
}
/* 判断是不是链接文件*/
if ((st.st_mode & S_IFMT) == S_IFLNK)
{
    
    
    /* 是*/
}

The S_IFMT macro is a file type field bitmask:

S_IFMT 0170000

In addition to this judgment, we can also use the macros packaged by the Linux system to make judgments, as shown below (m is the st_mode variable):

S_ISREG(m) #判断是不是普通文件,如果是返回true,否则返回false
S_ISDIR(m) #判断是不是目录,如果是返回true,否则返回false
S_ISCHR(m) #判断是不是字符设备文件,如果是返回true,否则返回false
S_ISBLK(m) #判断是不是块设备文件,如果是返回true,否则返回false
S_ISFIFO(m) #判断是不是管道文件,如果是返回true,否则返回false
S_ISLNK(m) #判断是不是链接文件,如果是返回true,否则返回false
S_ISSOCK(m) #判断是不是套接字文件,如果是返回true,否则返回false

With these macros, you can determine the file type as follows:

/* 判断是不是普通文件*/
if (S_ISREG(st.st_mode))
{
    
    
    /* 是*/
}
/* 判断是不是目录*/
if (S_ISDIR(st.st_mode))
{
    
    
    /* 是*/
}

This is all about the st_mode variable.

struct timespec structure

This structure is defined in the <time.h> header file and is a time-related structure in the Linux system. If the application contains the <time.h> header file, you can use the structure in the application. The content of the structure is as follows:

struct timespec
{
    
    
    time_t tv_sec;           /* 秒*/
    syscall_slong_t tv_nsec; /* 纳秒*/
};

There are only two member variables in the struct timespec structure, one second (tv_sec) and one nanosecond (tv_nsec). time_t actually refers to the long int type, so it can be seen that the time represented by this structure can be accurate to nanoseconds. , Of course, for the time attribute of the file, such high precision is not required, and it often only needs to be accurate to the second level.
In the Linux system, time_t time refers to a time period, the number of seconds that have passed from a certain point in time to a certain point in time. For example, for the three time attributes of a file, it refers to a certain time in the past. The number of seconds elapsed from the time point (this time point is a starting base time point) to the time point when the file was last accessed, the file content was last modified, and the file status was last changed. time_t time
is called calendar time under Linux, which is described in detail in 7.2 Subtotal.
As can be seen from the sample code 5.2.1, the struct stat structure contains three file-related time attributes, but what we get here is only the time value in seconds + microseconds. For us, we do not use viewing. What we generally like is the time expressed in the form of "2020-10-10 18:30:30", which is intuitive and clear. Is there any way to get the time expressed in this form through seconds? The answer is of course yes. For example, we can use localtime()/localtime_r() or strftime() to get a time expression that is more convenient for us to view. The introduction and usage of these functions are described in detail in Section 7.2.4.

practise

This is the end of this section. It mainly introduces the stat function and a series of knowledge derived from it. In order to consolidate what you have learned in this section, here are some simple programming exercises that you can complete based on the knowledge you have learned in this section.
(1) Get the inode node number and file size of the file and print them out.
(2) Obtain the type of file and determine whether the file has readable and writable permissions for other users (Other).
(3) Obtain the time attribute of the file, including the time when the file was last accessed, the time when the file content was last modified, and the time when the file status was last changed, and print it out in string form, including time, date, and representation. Custom.
The above are some simple programming exercises compiled based on the content of this section. The author will give the corresponding sample code below.
(1)Practical programming exercise 1

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    struct stat file_stat;
    int ret;
    /* 获取文件属性*/
    ret = stat("./test_file", &file_stat);
    if (-1 == ret)
    {
    
    
        perror("stat error");
        exit(-1);
    }
    /* 打印文件大小和inode 编号*/
    printf("file size: %ld bytes\n"
           "inode number: %ld\n",
           file_stat.st_size,
           file_stat.st_ino);
    exit(0);
}

Before testing, use the ls command to view the inode node and size of the test_file file, as follows: As
Insert image description here
can be seen from the figure, the size of this file is 8864 bytes, and the inode number is 3701841; then compile our test program and run:
Insert image description here
(2)Practical programming exercise 2

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    struct stat file_stat;
    int ret;
    /* 获取文件属性*/
    ret = stat("./test_file", &file_stat);
    if (-1 == ret)
    {
    
    
        perror("stat error");
        exit(-1);
    }
    /* 判读文件类型*/
    switch (file_stat.st_mode & S_IFMT)
    {
    
    
    case S_IFSOCK:
        printf("socket");
        break;
    case S_IFLNK:
        printf("symbolic link");
        break;
    case S_IFREG:
        printf("regular file");
        break;
    case S_IFBLK:
        printf("block device");
        break;
    case S_IFDIR:
        printf("directory");
        break;
    case S_IFCHR:
        printf("character device");
        break;
    case S_IFIFO:
        printf("FIFO");
        break;
    }
    printf("\n");
    /* 判断该文件对其它用户是否具有读权限*/
    if (file_stat.st_mode & S_IROTH)
        printf("Read: Yes\n");
    else
        printf("Read: No\n");
    /* 判断该文件对其它用户是否具有写权限*/
    if (file_stat.st_mode & S_IWOTH)
        printf("Write: Yes\n");
    else
        printf("Write: No\n");
    exit(0);
}

Test:
Insert image description here
(3) Programming Practice 3

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
    
    
    struct stat file_stat;
    struct tm file_tm;
    char time_str[100];
    int ret;
    /* 获取文件属性*/
    ret = stat("./test_file", &file_stat);
    if (-1 == ret)
    {
    
    
        perror("stat error");
        exit(-1);
    }
    /* 打印文件最后被访问的时间*/
    localtime_r(&file_stat.st_atim.tv_sec, &file_tm);
    strftime(time_str, sizeof(time_str),
             "%Y-%m-%d %H:%M:%S", &file_tm);
    printf("time of last access: %s\n", time_str);
    /* 打印文件内容最后被修改的时间*/
    localtime_r(&file_stat.st_mtim.tv_sec, &file_tm);
    strftime(time_str, sizeof(time_str),
             "%Y-%m-%d %H:%M:%S", &file_tm);
    printf("time of last modification: %s\n", time_str);
    /* 打印文件状态最后改变的时间*/
    localtime_r(&file_stat.st_ctim.tv_sec, &file_tm);
    strftime(time_str, sizeof(time_str),
             "%Y-%m-%d %H:%M:%S", &file_tm);
    printf("time of last status change: %s\n", time_str);
    exit(0);
}

Test:
Insert image description here
You can use the stat command to view these time attributes of the test_file file and compare whether the program prints them correctly:
Insert image description here

fstat and lstat functions

We introduced the stat system call to you earlier. In addition to the stat function, you can also use the fstat and lstat system calls to obtain file attribute information. fstat, lstat have the same functions as stat, but have slightly different parameters and details.

fstat function

The difference between fstat and stat is that stat obtains file attribute information from the file name, without opening the file first; while the fstat function obtains file attribute information from the file descriptor, so before using the fstat function, you need to open the file to obtain the file description. symbol. Whether you should use stat
or fstat depends on the specific situation; for example, if you don't want to get the file attribute information by opening the file, then use stat. If the file is already open, then use fstat.
The fstat function prototype is as follows (can be viewed through the "man 2 fstat" command):

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *buf);

The first parameter fd represents the file descriptor, and the second parameter and return value are the same as stat. Examples of using the fstat function are as follows:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    struct stat file_stat;
    int fd;
    int ret;
    /* 打开文件*/
    fd = open("./test_file", O_RDONLY);
    if (-1 == fd)
    {
    
    
        perror("open error");
        exit(-1);
    }
    /* 获取文件属性*/
    ret = fstat(fd, &file_stat);
    if (-1 == ret)
        perror("fstat error");
    close(fd);
    exit(ret);
}

lstat function

The difference between lstat() and stat and fstat is that for symbolic link files, stat and fstat look up the file attribute information corresponding to the file pointed to by the symbolic link file, while lstat looks up the attribute information of the symbolic link file itself.
The lstat function prototype is as follows:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int lstat(const char *pathname, struct stat *buf);

The function parameter list and return value are the same as the stat function, and the usage method is also the same, so I won’t repeat them here!

File owner

Linux is a multi-user operating system. There are usually several different users in the system. Each file in the Linux system has a user and user group associated with it. Through this information, the owner and user group of the file can be determined. Group to which you belong.
The file owner indicates "who" the file belongs to, that is, which user it belongs to. Generally speaking, when a file is created, its owner is the user who created the file. For example, if the currently logged-in user is dt and a file is created using the touch command, then the owner of the file is dt; similarly, the same is true when the open function is called in a program to create a new file. Who is the user executing the program? Who is the owner of the file.
The group to which the file belongs indicates which user group the file belongs to. In Linux, the system does not identify different users and user groups through user names or user group names, but through IDs. ID is a number. The Linux system will assign an ID to each user or user group and associate the user name or user group name with the corresponding ID, so the system can identify it through the user ID (UID) or group ID (GID). different users and user groups.
Tips: User ID is referred to as UID, and user group ID is referred to as GID. These are the basic knowledge of the Linux operating system. If you are not familiar with the concepts of users and user groups, it is recommended to learn these basic knowledge by yourself first.
For example, you can use the ls command or the stat command to view the owner and group of the file, as shown below:

Insert image description here

As can be seen from the above figure, the user ID of the testApp.c file is 1000, and the user group ID is also 1000.
The user ID and group ID of the file are specified by st_uid and st_gid respectively in the struct stat structure. Since every file under Linux has a user ID and group ID associated with it, the same is true for a process. There are 5 or more IDs associated with a process, as shown in the following table:

Insert image description here
⚫ The actual user ID and the actual group ID identify who we are, that is, who is the user executing the process and the group to which the user belongs; the actual user ID and the actual group ID determine the user and group to which the process belongs.
⚫ The effective user ID, effective group ID and affiliated group ID of the process are used for file access permission check. Please see section 5.4.1 for details.

Effective user ID and effective group ID

First of all, for the effective user ID and effective group ID, this is a concept held by the process, and there is no such attribute for the file! The effective user ID and effective group ID are from the perspective of the operating system, and are used to determine whether the user currently executing the process has the corresponding permissions on a certain file in the current environment.
In the Linux system, when a process reads or writes a file, the system first determines whether the process has read and write permissions for the file. How to determine? Naturally, it is judged by the permission bits of the file. The st_mode field in the struct stat structure records the permission bits and file type of the file. The relevant content about file permission checking will be described in Section 5.5.
When performing permission check, the file permission check is not performed through the actual user and actual group of the process, but the file permission check is performed through the effective user and effective group. Generally, in most cases, the effective user of a process is equal to the actual user (the effective user ID is equal to the actual user ID), and the effective group is equal to the actual group (the effective group ID is equal to the actual group ID).
Then you may ask, under what circumstances is the effective user ID not equal to the actual user ID, and the effective group ID is not equal to the actual group ID? So about this issue, I will announce it to you later!
Tips: The essence of "whether the process has xx permissions on the file" referred to in the article is whether the user currently executing the process has xx permissions on the file
. Unless otherwise specified, the description in the text is intended!

chown function

chown is a system call that can be used to change the owner (user ID) and group (group ID) of a file. In fact,
there is also a chown command under the Linux system. This command is also used to change the owner and group of the file. For example, change the
owner and group of the testApp.c file to root:

sudo chown root:root testApp.c

Figure 5.4.2 Use the chown command to modify the file owner and group.

It can be seen that the owner and group of the file can indeed be changed through this command. This command actually calls the chown function to implement the function. The prototype of the chown function is as follows (can be viewed through the "man 2 chown" command):

#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);

首先,使用该命令需要包含头文件<unistd.h>。
函数参数和返回值如下所示:
pathname:用于指定一个需要修改所有者和所属组的文件路径。
owner:将文件的所有者修改为该参数指定的用户(以用户ID 的形式描述);
group:将文件的所属组修改为该参数指定的用户组(以用户组ID 的形式描述);
返回值:成功返回0;失败将返回-1,兵并且会设置errno。
该函数的用法非常简单,只需指定对应的文件路径以及相应的owner 和group 参数即可!如果只需要修改文件的用户ID 和用户组ID 当中的一个,那么又该如何做呢?方法很简单,只需将其中不用修改的ID(用户ID 或用户组ID)与文件当前的ID(用户ID 或用户组ID)保持一致即可,即调用chown 函数时传入的用户ID 或用户组ID 就是该文件当前的用户ID 或用户组ID,而文件当前的用户ID 或用户组ID 可以通过
stat 函数查询获取。
虽然该函数用法很简单,但是有以下两个限制条件:
⚫ 只有超级用户进程能更改文件的用户ID;
⚫ 普通用户进程可以将文件的组ID 修改为其所从属的任意附属组ID,前提条件是该进程的有效用户ID 等于文件的用户ID;而超级用户进程可以将文件的组ID 修改为任意值。
所以,由此可知,文件的用户ID 和组ID 并不是随随便便就可以更改的,其实这种设计是为系统安全着想,如果系统中的任何普通用户进程都可以随便更改系统文件的用户ID 和组ID,那么也就意味着任何普通用户对系统文件都有任意权限了,这对于操作系统来说将是非常不安全的。
测试
接下来看一些chown 函数的使用例程,如下所示:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    if (-1 == chown("./test_file", 0, 0))
    {
    
    
        perror("chown error");
        exit(-1);
    }
    exit(0);
}

The code is very simple. Directly call the chown function to change the user ID and user group ID of the test_file file to 0 and 0. 0 refers to the root
user and root user group. Next we test:

Insert image description here

Before running the test code, first use the stat command to see that the user ID and user group ID of the test_file file are both equal to 1000, and then execute the test program. The result is an error "Operation not permitted", indicating that the operation is not allowed; then re-execute the program. Add sudo at this time, as follows:

Insert image description here

At this point, you can see that no error message was printed after execution, indicating that the chown function call was successful, and you can also see through the stat command that the user ID and group ID of the file have indeed been modified to 0 (that is, the root user) . The reason is that with the addition of sudo to execute the application, the application can temporarily obtain the root user's permissions, that is, it will run the program as the root user, which means that the user ID of the application at this time (that is, The actual user ID mentioned earlier) becomes the ID of the root superuser (that is, 0), and naturally the chown function can be called successfully.
Under the Linux system, you can use the two system calls getuid and getgid to obtain the user ID and user group
ID of the current process respectively. The user ID and user group ID of the process mentioned here refer to the actual user ID and actual group of the process. ID, the prototypes of these two system call functions are as follows:

#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
gid_t getgid(void);

We can add a statement to print the user ID in sample code 5.4.1, as follows:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    printf("uid: %d\n", getuid());
    if (-1 == chown("./test_file", 0, 0))
    {
    
    
        perror("chown error");
        exit(-1);
    }
    exit(0);
}

Repeat the above test:
Insert image description here
It is obvious that the user IDs of the same application are different when executed twice, because the addition of sudo makes the user ID of the application change from the original ordinary user ID 1000 to the super user. ID 0 makes the process become a superuser process, so
no error will be reported when calling the chown function.
This is all about chown. In practical application programming, the probability of this system call being used is not much, but you still need to know the theoretical knowledge.

fchown and lchown functions

These two are also system calls and have the same function as the chown function, but there are slight differences in parameters and details. The difference between the two functions fchown() and lchown()
and chown() is like the difference between fstat(), lstat() and stat. This section will not reiterate this issue. If you are not familiar with this, For clarity, please refer to Section 5.3. The specific use of fchown, lchown or chown depends on the situation.

File access permissions

The st_mode field in the struct stat structure records the access permission bits of the file. When referring to files, it refers to any type of file introduced to you earlier, not just ordinary files; all file types (directories, device files) have access permissions, and there may be
many People think that only ordinary files have access rights. This is a misunderstanding!

Normal and special permissions

File permissions can be divided into two broad categories, namely general permissions and special permissions (also called additional permissions). Ordinary permissions include reading, writing, and executing files, while special permissions include some additional permissions on files, such as Set-User-ID, Set-Group-ID, and Sticky. Next, ordinary permissions and special permissions are introduced respectively.
Common permissions
Each file has 9 common access permission bits, which can be divided into 3 categories, as shown in the following table:

Insert image description here

For example, you can use the ls command or the stat command to view the 9 access rights of the file, as shown below:

Insert image description here

In each line of printed information, the preceding string describes the 9 access permissions and file type of the file, such as "-rwxrwxr-
x":

Insert image description here

The first character indicates the type of the file. As introduced to you before, "-" indicates that the file is an ordinary file.
r means read permission;
w means write permission;
x means execute permission;
- means no such permission.
Every time a process reads, writes, or executes a file, the kernel checks the access permission of the file to determine whether the process has the corresponding permission for the file. The permission check of the file involves the owner of the file (st_uid), the group to which the file belongs (st_gid) and other users. Of course, this refers to the perspective of the file; for the process, it is involved in the file permission check. The effective user, effective user group, and subordinate group users of the process.
How to judge the permissions, first of all, it is necessary to figure out which type of "role" the process belongs to for the file that needs to be operated: ⚫
If the effective user ID of the process is equal to the file owner ID (st_uid), it means that the process is owned by the file
⚫ If the process's effective user ID is not equal to the file's owner ID, it means that the process is not the file's owner; but one of the process's effective user group ID or the process's subordinate group ID is equal to the file's group ID (st_gid), then it means that the process exists as a member of the group to which the file belongs, that is, a user member of the same group as the group to which the file belongs.
⚫ If the effective user ID of the process is not equal to the file owner ID, and the effective user group ID of the process or all the affiliated group IDs of the process are not
equal to the group ID (st_gid) of the file, it means that the process exists in the role of other users .
⚫ If the effective user ID of the process is equal to 0 (root user), there is no need to perform permission checks and it directly has the highest permissions on the file.
After determining which "role" the process belongs to for the file, the corresponding permissions can be directly "checked". Next, let’s talk about additional special permissions for files.
Special permissions
In addition to recording the 9 common permissions of the file, the st_mode field also records 3 special permissions of the file, which is the S field permission bit shown in Figure 5.2.1. Among the three bits of the S field, from high to The low bits represent the set-user-ID bit permission, set-group-ID bit permission and sticky bit permission of the file in sequence, as shown below:

Insert image description here

These three permissions are represented by three macros: S_ISUID, S_ISGID and S_ISVTX:

S_ISUID 04000 set-user-ID bit
S_ISGID 02000 set-group-ID bit (see below)
S_ISVTX 01000 sticky bit (see below)

Again, the above numbers are expressed in octal notation. If the corresponding bit number is 1, it means that the permission is set, and if it is 0, it means that the permission is not set; for example, the st_mode variable is used to determine whether the set-user-ID bit permission is set on the file. The code is as follows:

if (st.st_mode & S_ISUID) {
    
    
	//设置了set-user-ID 位权限
} else {
    
    
	//没有设置set-user-ID 位权限
}

What are the specific functions of these three permission bits? Next, I will give you a brief introduction:
⚫ When a process operates on a file, a permission check will be performed. If the set-user-ID bit permission of the file is set, the kernel will set the effective ID of the process to the file's User ID (file owner ID) means that the process directly obtains the permissions of the file owner and operates the file as the file owner.
⚫ When the process operates on a file, a permission check will be performed. If the set-group-ID bit permission of the file is set, the kernel will set the effective user group ID of the process to the user group ID of the file (the group ID to which the file belongs ), which means that the process directly obtains the permissions of the group members to which the file belongs and operates the file as a member of the group to which the file belongs.
Seeing this, you may ask, what if two permission bits are set at the same time? Regarding this question, we can conduct corresponding tests later, and the answer will naturally be revealed!
Of course, the functions of the set-user-ID bit and set-group-ID bit permissions are not that simple. This document will not describe other functions, because these special permission bits are rarely used in practice. In addition, the Sticky bit permissions will no longer be introduced to you. The author does not know much about it. Interested readers can check related books by themselves.
Most files under the Linux system do not have the set-user-ID bit permission and the set-group-ID bit permission set, so usually the effective user of the process is equal to the actual user (the effective user ID is equal to the actual user ID). Effective Group equals actual group (effective group ID equals actual group ID).

Directory permissions

We have been talking about the read, write, and execute permissions of files, so don't we need corresponding permissions for operations such as creating files and deleting files? This is not the case. For example: sometimes deleting files or creating files will prompt "insufficient permissions", as shown below:

Figure 5.5.3 Create file, delete file

That means deleting files and creating files also require corresponding permissions. So where do these permissions come from? The answer is the catalog. A directory (folder) is also a kind of file under the Linux system, and has the same permission scheme (S/U/G/O) as ordinary files, but the meaning of these permissions refers otherwise.
⚫ Read permission of the directory: You can list (for example: through the ls command) the contents under the directory (that is, what files are in the directory).
⚫ Write permission of the directory: files can be created and deleted in the directory.
⚫ Execution permission of the directory: You can access the files in the directory, such as reading, writing, and executing operations on the files in the directory.
With the read permission to the directory, the user can only view the list of files in the directory, for example, use the ls command to view: through the
Insert image description here
"ls -l" command, you can see that the 2_chapter directory has only read permission for the file owner, and the current operating user is The owner of the directory, dt, then checked the files in the directory through the "ls 2_chapter" command, and indeed obtained 3 files in the directory: file1, file2, and file3, indicating that only the read permission can be used to view the files in the
directory file, the name of the file is displayed; but you will see some "insufficient permissions" messages printed above. This is because the Ubuntu distribution has aliased the ls command, and carries some options when executing the ls command, and these options Some information of the file will be accessed, which leads to the problem of "insufficient permissions". This also means that if you only have read permissions, you cannot access the files in the directory; in order to ensure that the ls command itself is used, the path needs to be given during execution. The full path of /bin/ls:
Insert image description here
If you want to access the files in the directory, such as viewing the inode node, size, permissions and other information of the file, you also need to have execution permissions on the directory.
On the other hand, if you have execute permission on the directory but no read permission, you can still access it as long as you know the names of the files in the directory, but you cannot list the contents of the directory (that is, the names of other files contained in the directory).
If you want to create a file or delete an existing file in a directory, you need to have both execute and write permissions on the directory.
So it can be seen that if you need to read, write or execute a file, you not only need to have read, write or execute permissions on the file itself, but also need to have execution permissions on the directory where the file is located.

Check file permission access

Through the previous introduction, everyone should know that the file permission check not only discusses the permissions of the file itself, but also involves the permissions of the directory where the file is located. Only when both are satisfied at the same time can the permission check of the operating system be passed, and then the permissions of the file can be passed. Perform related operations on files; therefore, before performing related operations on files in the program, you need to first check whether the user executing the process has the corresponding permissions for the file. So how to check? You can use the access system call. The function prototype is as follows:

#include <unistd.h>
int access(const char *pathname, int mode);

First, you need to include the header file <unistd.h> to use this function.
The meanings of function parameters and return values ​​are as follows:
pathname: the file path that requires permission checking.
mode: This parameter can take the following values:
⚫ F_OK: Check whether the file exists
⚫ R_OK: Check whether you have read permission
⚫ W_OK: Check whether you have write permission
⚫ X_OK: Check whether you have execution permission
. In addition to being used alone, you can also pass Combined together with the bitwise OR operator "|".
Return value: If the check item passes, 0 is returned, indicating that it has the corresponding permissions and the file exists; otherwise, -1 is returned. If multiple check items are combined, -1 will be returned as long as any one of them fails.
Test:
Check whether the file exists through the access function. If it exists, continue to check whether the user executing the process has read, write, and execute permissions on the file.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MY_FILE "./test_file"
int main(void)
{
    
    
    int ret;
    /* 检查文件是否存在*/
    ret = access(MY_FILE, F_OK);
    if (-1 == ret)
    {
    
    
        printf("%: file does not exist.\n", MY_FILE);
        exit(-1);
    }
    /* 检查权限*/
    ret = access(MY_FILE, R_OK);
    if (!ret)
        printf("Read permission: Yes\n");
    else
        printf("Read permission: NO\n");
    ret = access(MY_FILE, W_OK);
    if (!ret)
        printf("Write permission: Yes\n");
    else
        printf("Write permission: NO\n");
    ret = access(MY_FILE, X_OK);
    if (!ret)
        printf("Execution permission: Yes\n");
    else
        printf("Execution permission: NO\n");
    exit(0);
}

Next compile and test:
Insert image description here

Modify file permissions chmod

Under the Linux system, you can use the chmod command to modify file permissions. The internal implementation method of this command is actually to call the chmod function. The chmod function is
a system call. The function prototype is as follows (you can view it through the "man 2 chmod" command):

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);

First, you need to include the header file <sys/stat.h> to use this function.
The function parameters and return values ​​are as follows:
pathname: the file path that needs to be modified. If the parameter refers to a symbolic link, the file whose permissions are actually changed is the file pointed to by the symbolic link, not the symbolic link file itself.
mode: This parameter is used to describe file permissions. It is the same as the third parameter of the open function. It will not be repeated here. It can be described directly using octal data, or the corresponding permission macro can be used (single or through the bitwise OR operator " | "combination).
Return value: Returns 0 on success; -1 on failure and sets errno.
File permissions are very important attributes for files and cannot be modified by any user. If you want to change file permissions, you must either use the superuser (root) process or the effective user ID of the process and the user ID of the file ( file owner) matches.
fchmod function
This function has the same function as chmod, with slightly different parameters. The difference between fchmod() and chmod() is that file descriptors are used instead of file paths, just like the difference between fstat and stat. The function prototype looks like this:

#include <sys/stat.h>
int fchmod(int fd, mode_t mode);

The file descriptor fd is used instead of the file path pathname, and other functions are the same.
test

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    int ret;
    ret = chmod("./test_file", 0777);
    if (-1 == ret)
    {
    
    
        perror("chmod error");
        exit(-1);
    }
    exit(0);
}

In the above code, by calling the chmod function, the permissions of the test_file file in the current directory are modified to 0777 (octal representation, which can also be represented by macros such as S_IRUSR, S_IWUSR, etc.), that is, the file owner, the user of the group to which the file belongs, and Other users have read, write, and execute permissions. Next, compile the test:
Insert image description here
before executing the program, the permissions of the test_file file are rw-r–r– (0644). After the program execution is completed, check the file permissions again to be
rwxrwxrwx (0777). Successfully modified!

umask function

There is a umask command under Linux, execute it under Ubuntu system to see:

Insert image description here
You can see that the command prints "0002". What does this number mean? This starts with the role of the umask command. The umask
command is used to view/set the permission mask. The permission mask is mainly used to shield the permissions of new files. The permission mask is expressed in the same way as the file permissions, but the special permission bits need to be removed, and umask cannot mask the special permission bits.
When creating a new file, the actual permissions of the file are not equal to the permissions we set. For example: when calling the open function to create a new file, the actual permissions of the file are not equal to the permissions described by the mode parameter, but the actual permissions are obtained through the following relationship:

mode & ~umask

For example, when calling the open function to create a new file, the mode parameter is specified as 0777. Assuming that the umask is 0002, the actual permissions are:

0777 & (~0002) = 0775

When I introduced you to the mode parameter of the open function earlier, I did not mention umask to you, so I will explain it to you again here.
The umask permission mask is an attribute of a process that is used to indicate which permission bits should be blocked when the process creates a new file or directory. The umask of a process
is usually inherited from its parent process (the content related to parent and child processes will be introduced to you in later chapters). For example, for an
application executed under the Ubuntu shell terminal, its umask is inherited from the shell process.
Of course, the Linux system provides the umask function for setting the permission mask of the process. This function is a system call, and the function prototype is as follows (can be viewed through the "man 2 umask" command):

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);

First, you need to include the header files <sys/types.h> and <sys/stat.h> to use this command.
The meanings of function parameters and return values ​​are as follows:
mask: the permission mask value that needs to be set. It can be found that the type of the make parameter is the same as the type corresponding to the mode parameter in the open function and chmod function, so its expression is also the same, as shown above. As we have introduced, you can either use numerical representation (such as octal numbers) or directly use macros (S_IRUSR, S_IWUSR, etc.).
Return value: Returns the umask value before setting, which is the old umask.
Test
Next we write a test code and use the umask() function to modify the umask permission mask of the process. The test code is as follows:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    mode_t old_mask;
    old_mask = umask(0003);
    printf("old mask: %04o\n", old_mask);
    exit(0);
}

In the above code, the umask function is used to set the umask of the process to 0003 (octal). The returned old_mask is the old umask value before being set, and then printed out: As can be seen from the printed information, the old umask is equal to
Insert image description here
0002 , this umask is inherited from the current vscode shell terminal. If the umask value of the process is not modified, the default is the umask inherited from the parent process.
Here again, the umask is an attribute of the process itself, and the umask of the A process has nothing to do with the umask of the B process (except for the parent-child process relationship). In the shell terminal, you can use the umask command to set the umask value of the shell terminal. However, after the shell terminal is closed and a terminal is opened again, the newly opened terminal will have nothing to do with the previously closed terminal!

File time attribute

Earlier I introduced to you the time attributes of three files: the time when the file was last accessed, the time when the file content was last modified, and the time when the file status was last changed, which are recorded in the st_atim, st_mtim and st_ctim variables of the struct stat structure respectively. , as shown below:
Insert image description here
⚫ The time when the file was last accessed: Access refers to reading the file content, and the time when the file content was last read. For example, using the read()
function to read the file content will change the time attribute;
⚫ The time when the file content was last modified: The file content changes. For example, using the write() function to write data to the file will change the time attribute; ⚫ The time when the
file status was last changed: The status change refers to the inode of the file The time when the node was last modified, such as changing the file's access permissions, changing the file's user ID, user group ID, changing the number of links, etc., but they did not change the actual content of the file, nor did they access (read) the file content. Why does a file state change refer to an inode node change? In Section 3.1, I introduced the inode node to you. The inode contains a lot of file information, such as: file byte size, file owner, file corresponding read/write/execution permissions, file timestamp (time attribute) , the block (block) of file data storage
, etc., so it can be seen that the change of the state refers to the change of the content of the inode node. For example, functions such as chmod() and chown() can change the time attribute.
Table 5.6.2 lists the effects of some system calls or C library functions on file time attributes. Some operations not only affect the time attributes of the file itself, but also affect the related time attributes of its parent directory.
Insert image description here

utime(), utimes() modify time attributes

Although the time attribute of the file will change when we perform related operations on the file (such as reading and writing), these changes are implicit and passive changes. In addition, you can also use the functions provided by the Linux system. The system call explicitly modifies the file's time attribute. This section will introduce how to use the utime() and utimes() functions to modify the time attributes of files.
Tips: You can only explicitly modify the last access time of the file and the time when the file content was last modified. You cannot explicitly modify the time when the file status was last changed. Can you think about why? I leave this as a thinking question for everyone!
utime() function
utime() function prototype is as follows:

#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);

First, the use of this function needs to include the header files <sys/types.h> and <utime.h>.
The meanings of function parameters and return values ​​are as follows:
filename: the path of the file whose time attribute needs to be modified.
Times: Modify the time attribute to the time value specified by this parameter. Times is a pointer of struct utimbuf structure type. I will introduce it to you later. If the times parameter is set to NULL, the access time and modification time of the file will be Set to the current system time.
Return value: return value 0 if successful; return -1 and set errno if failed.
Let’s take a look at the struct utimbuf structure:

struct utimbuf {
    
    
	time_t actime; /* 访问时间*/
	time_t modtime; /* 内容修改时间*/
};

This structure contains two members of time_t type, which are used to represent access time and content modification time respectively. The time_t type is actually the long int type, so these two times are in seconds, so it can be seen that utime The () function can only set the time attribute of the file with a precision of seconds.
Similarly for files, the time attribute is also one of the very important attributes of the file. Modification of the file time attribute is not something that any user can modify at will. Only the following two processes can modify it: ⚫ Super user process (named
after process running as root).
⚫ Effective user ID The process that matches the file user ID (file owner).
⚫ When the parameter times is equal to NULL, the process that has write permissions on the file.
User processes other than the above three cases will not be able to modify the file timestamp.
utime test
Next we write a simple test program, using the utime() function to modify the access time and content modification time of the file. The sample code is as follows:

#include <sys/types.h>
#include <utime.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MY_FILE "./test_file"
int main(void)
{
    
    
    struct utimbuf utm_buf;
    time_t cur_sec;
    int ret;
    /* 检查文件是否存在*/
    ret = access(MY_FILE, F_OK);
    if (-1 == ret)
    {
    
    
        printf("Error: %s file does not exist!\n", MY_FILE);
        exit(-1);
    }
    /* 获取当前时间*/
    time(&cur_sec);
    utm_buf.actime = cur_sec;
    utm_buf.modtime = cur_sec;
    /* 修改文件时间戳*/
    ret = utime(MY_FILE, &utm_buf);
    if (-1 == ret)
    {
    
    
        perror("utime error");
        exit(-1);
    }
    exit(0);
}

The above code attempts to modify the access time and content modification time of the test_file file to the current system time. The time() function is used in the program
. time() is a Linux system call used to obtain the current time (you can also directly set the times parameter to NULL, so you do not need to use the time function to obtain the current time). The unit is seconds. , this function will be introduced to you in the following chapters, let’s take a brief look at it here. Next, compile the test. Before running the program, first use the stat command to view the timestamp of the test_file file, as follows:

Insert image description here

Next compile the program and run the tests:

Insert image description here

You will find that after executing the test program, the access time and content modification time of the test_file file have been changed to the current time (you can use the date command to view the current system time), and you will find that the status change time has also been changed to the current time. Of course, this is not modified in the program, but automatically modified by the kernel. Why is this? If you understand the knowledge content introduced before, you can completely understand this problem, and I will not reiterate it here!
The utimes() function
utimes() is also a system call. Its function is the same as the utime() function, but there are slight differences in parameters and details. The biggest difference between utimes() and utime() is that the former can specify time values ​​with microsecond-level precision. , its function prototype is as follows:

#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);

First, you need to include the header file <sys/time.h> to use this function.
The meanings of function parameters and return values ​​are as follows:
filename: the file path to which the time attribute needs to be modified.
Times: Modify the time attribute to the time value specified by this parameter. Times is an array of struct timeval structure type. The array has two elements. The first element is used to specify the access time, and the second element is used to specify the content. The modification time will be introduced to you later. If
the times parameter is NULL, the access time and modification time of the file will be set to the current time.
Return value: Returns 0 on success; -1 on failure, and errno will be set.
Let’s take a look at the struct timeval structure:

struct timeval
{
    
    
    long tv_sec;  /* 秒*/
    long tv_usec; /* 微秒*/
};

This structure contains two member variables tv_sec and tv_usec, which are used to represent seconds and microseconds respectively.
utimes() follows the same timestamp modification permission rules as utime().
utimes test

#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#define MY_FILE "./test_file"
int main(void)
{
    
    
    struct timeval tmval_arr[2];
    time_t cur_sec;
    int ret;
    int i;
    /* 检查文件是否存在*/
    ret = access(MY_FILE, F_OK);
    if (-1 == ret)
    {
    
    
        printf("Error: %s file does not exist!\n", MY_FILE);
        exit(-1);
    }
    /* 获取当前时间*/
    time(&cur_sec);
    for (i = 0; i < 2; i++)
    {
    
    
        tmval_arr[i].tv_sec = cur_sec;
        tmval_arr[i].tv_usec = 0;
    }
    /* 修改文件时间戳*/
    ret = utimes(MY_FILE, tmval_arr);
    if (-1 == ret)
    {
    
    
        perror("utimes error");
        exit(-1);
    }
    exit(0);
}

The code will not be introduced to you anymore. The function is the same as the sample code 5.6.2. You can compile and run the test yourself.

futimens(), utimensat() modify time attributes

除了上面给大家介绍了两个系统调用外,这里再向大家介绍两个系统调用,功能与utime()和utimes()函数功能一样,用于显式修改文件时间戳,它们是futimens()和utimensat()。
这两个系统调用相对于utime 和utimes 函数有以下三个优点:
⚫ 可按纳秒级精度设置时间戳。相对于提供微秒级精度的utimes(),这是重大改进!
⚫ 可单独设置某一时间戳。譬如,只设置访问时间、而修改时间保持不变,如果要使用utime()或utimes()
来实现此功能,则需要首先使用stat()获取另一个时间戳的值,然后再将获取值与打算变更的时间戳一同指定。
⚫ 可独立将任一时间戳设置为当前时间。使用utime()或utimes()函数虽然也可以通过将times 参数设置为NULL 来达到将时间戳设置为当前时间的效果,但是不能单独指定某一个时间戳,必须全部设置为当前时间(不考虑使用额外函数获取当前时间的方式,譬如time())。
futimens()函数
futimens 函数原型如下所示(可通过"man 2 utimensat"命令查看):

#include <fcntl.h>
#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);

The function prototype and return value have the following meanings:
fd: file descriptor.
times: Modify the time attribute to the time value specified by this parameter. Times points to an array with 2 struct timespec structure type variables. The array has two elements. The first element is used to specify the access time, and the second element is used to specify the access time. At the specified content modification time, this structure was introduced to you in section 5.2.3, so I won’t repeat it here!
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
So it can be seen that to use futimens() to set the file timestamp, you need to open the file first to obtain the file descriptor.
The timestamp of this function can be specified in one of the following four ways:
⚫ If the times parameter is a null pointer, that is, NULL, it means that both the access time and modification time are set to the current time.
⚫ If the times parameter points to an array of two struct timespec structure type variables, and the value of the tv_nsec field of any array element is set to UTIME_NOW, it means that the corresponding timestamp is set to the current time, and the corresponding tv_sec field is ignored.
⚫ If the times parameter points to an array of two struct timespec structure type variables, and the value of the tv_nsec field of any array element is set to UTIME_OMIT, it means that the corresponding timestamp remains unchanged, and the tv_sec field is ignored.
⚫ If the times parameter points to an array of two struct timespec structure type variables, and the value of the tv_nsec field is neither
UTIME_NOW nor UTIME_OMIT, in this case, the corresponding timestamp is set to
the value specified by the corresponding tv_sec and tv_nsec fields.
Tips: UTIME_NOW and UTIME_OMIT are two macro definitions.
Only the following processes can use the futimens() function to modify the file timestamp:
⚫ Super user process.
⚫ When the parameter times is equal to NULL, the process that has write permissions on the file.
⚫ Effective user ID The process that matches the file user ID (file owner).

futimens() test

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#define MY_FILE "./test_file"
int main(void)
{
    
    
    struct timespec tmsp_arr[2];
    int ret;
    int fd;
    /* 检查文件是否存在*/
    ret = access(MY_FILE, F_OK);
    if (-1 == ret)
    {
    
    
        printf("Error: %s file does not exist!\n", MY_FILE);
        exit(-1);
    }
    /* 打开文件*/
    fd = open(MY_FILE, O_RDONLY);
    if (-1 == fd)
    {
    
    
        perror("open error");
        exit(-1);
    }
/* 修改文件时间戳*/
#if 1
    ret = futimens(fd, NULL); // 同时设置为当前时间
#endif
#if 0
tmsp_arr[0].tv_nsec = UTIME_OMIT;//访问时间保持不变
tmsp_arr[1].tv_nsec = UTIME_NOW;//内容修改时间设置为当期时间
ret = futimens(fd, tmsp_arr);
#endif
#if 0
tmsp_arr[0].tv_nsec = UTIME_NOW;//访问时间设置为当前时间
tmsp_arr[1].tv_nsec = UTIME_OMIT;//内容修改时间保持不变
ret = futimens(fd, tmsp_arr);
#endif
    if (-1 == ret)
    {
    
    
        perror("futimens error");
        goto err;
    }
err:
    close(fd);
    exit(ret);
}

The code will no longer be introduced to you. You can compile and run the tests yourself.
The utimensat() function
utimesat() is the same as the futimens() function in function. It can also set the timestamp with nanosecond precision, set a certain timestamp independently, and independently set any timestamp as the current time. It is the same as futimes () There are some differences in parameters and details. To use the futimes() function, you need to open the file first and operate through the file descriptor. Utimesat() can directly use the file path to operate.
The utimensat function prototype is as follows:

#include <fcntl.h>
#include <sys/stat.h>
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);

First, to use this function, you need to include the header files <fcntl.h> and <sys/stat.h>.
The meanings of function parameters and return values ​​are as follows:
dirfd: This parameter can be the file descriptor of a directory, or the special value AT_FDCWD; if the pathname parameter specifies the absolute path of the file, this parameter will be ignored.
pathname: Specify the file path. If the pathname parameter specifies a relative path and the dirfd parameter is not equal to the special value
AT_FDCWD, the actual file path operated is parsed relative to the directory pointed to by the file descriptor dirfd. If the pathname parameter specifies a relative path and the dirfd parameter is equal to the special value AT_FDCWD, the actual file path is parsed relative to the current working directory of the calling process. The working directory of the process is introduced in Section 5.7.
times: The same meaning as the times parameter of futimens().
flags: This parameter can be 0 or set to AT_SYMLINK_NOFOLLOW. If set to
AT_SYMLINK_NOFOLLOW, when the file specified by the pathname parameter is a symbolic link, the timestamp of the symbolic link is modified, not the file it points to.
Return value: 0 is returned on success; -1 is returned on failure, and the timestamp will be set.
utimensat() follows the same timestamp modification permission rules as futimens().
utimensat() function test

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MY_FILE "/home/dt/vscode_ws/2_chapter/test_file"
int main(void)
{
    
    
    struct timespec tmsp_arr[2];
    int ret;
    /* 检查文件是否存在*/
    ret = access(MY_FILE, F_OK);
    if (-1 == ret)
    {
    
    
        printf("Error: %s file does not exist!\n", MY_FILE);
        exit(-1);
    }
/* 修改文件时间戳*/
#if 1
    ret = utimensat(-1, MY_FILE, NULL, AT_SYMLINK_NOFOLLOW); // 同时设置为当前时间
#endif
#if 0
tmsp_arr[0].tv_nsec = UTIME_OMIT;//访问时间保持不变
tmsp_arr[1].tv_nsec = UTIME_NOW;//内容修改时间设置为当期时间
ret = utimensat(-1, MY_FILE, tmsp_arr, AT_SYMLINK_NOFOLLOW);
#endif
#if 0
tmsp_arr[0].tv_nsec = UTIME_NOW;//访问时间设置为当前时间
tmsp_arr[1].tv_nsec = UTIME_OMIT;//内容修改时间保持不变
ret = utimensat(-1, MY_FILE, tmsp_arr, AT_SYMLINK_NOFOLLOW);
#endif
    if (-1 == ret)
    {
    
    
        perror("futimens error");
        exit(-1);
    }
    exit(0);
}

Symbolic links (soft links) and hard links

There are two types of link files in the Linux system, which are divided into soft link (also called symbolic link) files and hard link files. Soft link files are one of the seven file types under the Linux system mentioned earlier, and their functions are similar to Shortcuts under Windows. So what are hard link files? In this section, let’s talk about the differences between them.
First of all, from a usage perspective, there is no difference between the two. They are both the same as normal file access methods, supporting reading, writing and execution. So what's the difference between them? In the underlying principle, in order to illustrate this problem, let's first create a hard link file, as shown below:
Insert image description here
Tips: Use the ln command to create a soft link file or hard link file for a file. The usage is as follows:
Hard link: ln source file link File
soft link: ln -s source file link file
For other uses of this command, you can view the man manual.
As can be seen from Figure 5.7.1, the two hard link files created using the ln command and the source file test_file have the same inode number. Since the inodes are the same, it means that they
point to the same block of the physical hard disk, just The file names are just different. The created hard link file and the source file have a completely equal relationship with the file system. So you may want to ask, if you delete a hard link file or one of the source files, will the inode corresponding to the file and the data blocks of the file content on the disk be recycled by the file system? In fact, this is not the case, because the inode data structure will record the number of links to the file. This number of links refers to the number of hard links. The st_nlink member variable in the struct stat structure records the number of links to the file. These contents are mentioned above
. I’ve already introduced it to you.
When a hard link is created for a file, the number of links on the inode node will increase by one. Every time a hard link is deleted, the number of links on the inode node will decrease by one. Until it is 0, the inode node and the corresponding data block will Recycled by the file system means that the file has been deleted from the file system. As can be seen from Figure 5.7.1, using the "ls -li" command, the number of links is 3 (the number in front of the dt user name). We have obviously created 2 link files. Why is the number of links 3? In fact, the source file test_file itself is a hard link file, so here is 3.
When we delete any of the files, the number of links will be reduced, as shown below:
Insert image description here
Next, let’s talk about soft link files. Soft link files have different inode numbers from the source files, as shown in Figure 5.7.3, so That is to say, there are different data blocks between them, but the data block of the soft link file stores the path name of the source file. The link file can find the linked source file through this path. They are similar to a In the "master-slave" relationship, when the source file is deleted, the soft link file still exists, but at this time it points to an invalid file path. This kind of link file is called a dangling link, as shown in Figure 5.7.4.
Insert image description here
Insert image description here
It can also be seen from the figure that the number of links recorded in the inode node does not count soft links.
After introducing the differences between them, you may think that hard links have greater advantages than soft links. In fact, this is not the case. There are some limitations for hard links, as follows: ⚫ You cannot create hard links for directories
. Links (can be created by superusers, but only if supported by the underlying file system).
⚫ Hard links usually require that the linked file and the source file are in the same file system.
The use of soft link files does not have the above restrictions, and the advantages are as follows:
⚫ Can create soft links to directories;
⚫ Can span different file systems;
⚫ Can create soft links to non-existing files.

Create link file

Under Linux systems, you can use system calls to create hard link files or soft link files. This section will introduce how to create link files through these system calls.
Create a hard link link()
The link() system call is used to create a hard link file. The function prototype is as follows (can be viewed through the "man 2 link" command):

#include <unistd.h>
int link(const char *oldpath, const char *newpath);

First, you need to include the header file <unistd.h> to use this function.
The function prototype and return value have the following meanings:
oldpath: used to specify the path of the source file to be linked. Avoid specifying the oldpath parameter as a soft link file. It is meaningless to create a hard link for a soft link file, although no error will be reported.
newpath: used to specify a hard link file path. If the file path specified by newpath already exists, an error will occur.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
Link function test
Next we write a simple program to demonstrate how to use the link function:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    
    
    int ret;
    ret = link("./test_file", "./hard");
    if (-1 == ret)
    {
    
    
        perror("link error");
        exit(-1);
    }
    exit(0);
}

In the program, a hard link is created for the test_file file in the current directory through the link function. Compile and test:
Insert image description here
Create a soft link symlink()
The symlink() system call is used to create a soft link file. The function prototype is as follows (can be accessed through "man 2 symlink "Command view):

#include <unistd.h>
int symlink(const char *target, const char *linkpath);

First, you need to include the header file <unistd.h> to use this function.
The meanings of function parameters and return values ​​are as follows:
target: used to specify the path of the source file to be linked, and the target parameter can also specify a soft link file.
linkpath: used to specify a hard link file path. If the file path specified by newpath already exists, an error will occur.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
When creating a soft link, it is not required that the file path specified by the target parameter already exists. If the file does not exist, the created soft link will become a "dangling link".
Symlink function test
Next we write a simple program to demonstrate how to use the symlink function:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    
    
    int ret;
    ret = symlink("./test_file", "./soft");
    if (-1 == ret)
    {
    
    
        perror("symlink error");
        exit(-1);
    }
    exit(0);
}

In the program, a soft link soft is created for the test_file file in the current directory through the symlink function. Compile the test:
Insert image description here

Read soft link file

I introduced to you earlier that the path information of the linked file is stored in the data block of the soft link file, so how to read the path information stored in the soft link file? Do you think it is okay to use the read function? The answer is no, because before using the read function, you need to open
the file to get the file descriptor, but calling open to open a link file itself will not succeed, because the link file itself is not opened, but the link it points to file, so I cannot use read to read it, so what should I do? You can use the readlink system call.
The readlink function prototype is as follows:

#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

The meanings of function parameters and return values ​​are as follows:
pathname: the path of the soft link file to be read. It can only be a soft link file path, not other types of files, otherwise an error will be reported when calling the function.
buf: The buffer used to store path information.
bufsiz: read size, generally the read size needs to be larger than the byte size of the file path information stored in the link file data block.
Return value: Failure will return -1 and errno will be set; success will return the number of bytes read.
Readlink function test
Next we write a simple program to demonstrate how to use the readlink function:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
    
    
    char buf[50];
    int ret;
    memset(buf, 0x0, sizeof(buf));
    ret = readlink("./soft", buf, sizeof(buf));
    if (-1 == ret)
    {
    
    
        perror("readlink error");
        exit(-1);
    }
    printf("%s\n", buf);
    exit(0);
}

Use the readlink function to read the soft link file soft in the current directory, and print out the read information. The test is as follows:
Insert image description here

Table of contents

A directory (folder) is also a kind of file in the Linux system, and it is a special file. It can also be operated using the
system calls such as open and read introduced earlier and the C library functions. However, the directory is a special file. It is not suitable for reading and writing operations using the file I/O method described above. Under the Linux system, there are some special system calls or C library functions used to operate folders, such as: opening, creating folders, deleting folders, reading folders, and traversing files in folders, etc. Then This section will introduce you to the knowledge content related to the directory.

Directory storage format

Section 3.1 introduces the management form or storage form of ordinary files. This section talks about the storage form of special files such as directories in the file system. In fact, the storage method of directories in the file system is similar to that of regular files. Regular files It includes inode nodes and file content data storage blocks (blocks), as shown in Figure 3.1.1; but for directories, the storage form is composed of inode nodes and directory blocks, and what files are recorded in the directory blocks They are organized in this directory and their file names and corresponding inode numbers are recorded.
Its storage form is as shown in the figure below:
Insert image description here
There are multiple directory entries (or directory entries) in the directory block. Each directory entry (or directory entry) will correspond to a file in the directory, and the file is recorded in the directory entry. The file name and its inode node number, so through the directory block of the directory, you can traverse to find all the files in the directory and the corresponding inode node.
So the summary is as follows:
⚫ Ordinary files are composed of inode nodes and data blocks
⚫ Directories are composed of inode nodes and directory blocks

Create and delete directories

The open function can be used to create an ordinary file, but it cannot be used to create a directory file. Under the Linux system, system calls related to mkdir() and deletion of the directory rmdir are provided.

mkdir function
The function prototype is as follows:

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);

The meanings of function parameters and return values ​​are as follows:
pathname: the directory path to be created.
mode: Permission setting for the new directory. The setting method is the same as the mode parameter of the open function. The final permission is (mode & ~umask).
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
The pathname of the newly created directory specified by the pathname parameter. The pathname can be a relative path or an absolute path. If the specified pathname already exists, calling mkdir() will fail.
The mode parameter specifies the permissions of the new directory. The directory has the same permission bits as ordinary files, but its meaning is different from ordinary files. This is explained in 5.5.2 Subtotal.
mkdir function test

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(void)
{
    
    
    int ret;
    ret = mkdir("./new_dir", S_IRWXU |
                                 S_IRGRP | S_IXGRP |
                                 S_IROTH | S_IXOTH);
    if (-1 == ret)
    {
    
    
        perror("mkdir error");
        exit(-1);
    }
    exit(0);
}

In the above code, we create a directory new_dir in the current directory through the mkdir function, and set its permissions to 0755 (octal). Compile and run: rmdir The rmdir()
Insert image description here
function is used to delete a directory

#include <unistd.h>
int rmdir(const char *pathname);

First, you need to include the header file <unistd.h> to use this function.
The meanings of the function parameters and return values ​​are as follows:
pathname: the path name corresponding to the directory that needs to be deleted, and the directory must be an empty directory, that is, there are only two directory entries in the directory: . and...; the path name specified by pathname cannot be Soft link file, even if the link file points to an empty directory.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
rmdir function test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    
    
    int ret;
    ret = rmdir("./new_dir");
    if (-1 == ret)
    {
    
    
        perror("rmdir error");
        exit(-1);
    }
    exit(0);
}

Open, read and close directories

To open, read, and close a normal file, you can use open(), read(), and close(). For a directory, you can use opendir(), readdir(), and closedir() to open, read, and close the directory
. , next we will introduce to you the usage of these three C library functions.
Open file opendir
The opendir() function is used to open a directory and return a handle to the directory for subsequent operations. Opendir is a C library function. The opendir() function prototype is as follows:

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);

The meanings of function parameters and return values ​​are as follows:
name: Specifies the path name of the directory to be opened, which can be an absolute path or a relative path.
Return value: Success will return a handle to the directory, a DIR pointer (which is essentially a structure pointer), its function is similar to the file descriptor
fd returned by the open function, subsequent operations on the directory need to use the DIR pointer variable ;If the call fails, NULL is returned.
Read directory readdir
readdir() is used to read the directory and obtain the names and corresponding inode numbers of all files in the directory. The readdir() introduced here is a C library function (in fact, the Linux system also provides a readdir system call), and its function prototype is as follows:

#include <dirent.h>
struct dirent *readdir(DIR *dirp);

First, the header file <dirent.h> needs to be included to use this function.
The meanings of function parameters and return value are as follows:
dirp: directory handle DIR pointer.
Return value: Returns a pointer to the struct dirent structure, which represents the next directory entry in the directory stream pointed to by dirp. It returns NULL when the end of the directory stream is reached or an error occurs.
Tips: "Stream" is a concept abstracted from nature, which is somewhat similar to the water flow in nature. In file operations, the file content data is similar to the water stored in a pond. N bytes of data are read out or transferred N bytes of data are written to the file, and these data constitute a byte stream.
The concept of "flow" is dynamic, not static. When this concept is mentioned in programming, it is generally related to I/O, so it is often called I/O stream; but for special files such as directories, the data stored in the directory block is called directory stream, and the stored Directory entries (directory entries) one by one.
The content of the struct dirent structure is as follows:

struct dirent
{
    
    
    ino_t d_ino;             /* inode 编号*/
    off_t d_off;             /* not an offset; see NOTES */
    unsigned short d_reclen; /* length of this record */
    unsigned char d_type;    /* type of file; not supported by all filesystem types */
    char d_name[256];        /* 文件名*/
};

For the struct dirent structure, we only need to pay attention to the two fields d_ino and d_name, which record the inode number and file name of the file respectively. The other fields are not supported by all systems, so we will not introduce them to you. These fields are generally It won't be used either.
Each time readdir() is called, the next directory entry (directory entry) will be read from the directory stream pointed to by drip, and the struct dirent
structure pointer will be returned, pointing to the statically allocated struct dirent type structure. Each time Calling readdir() will overwrite this structure. Once the end of the directory is encountered or an error occurs, readdir() will return NULL. In the latter case, errno will be set to indicate the specific error. So how to distinguish whether it has reached the end of the directory or an error has occurred? You can judge it through the following code:

error = 0;
direntp = readdir(dirp);
if (NULL == direntp)
{
    
    
    if (0 != error)
    {
    
    
        /* 出现了错误*/
    }
    else
    {
    
    
        /* 已经到了目录末尾*/
    }
}

File names are not sorted when returning from readdir(), but in the natural order in which the files appear in the directory (this depends on the order in which the file system adds files to the directory, and the directory listing after removing files. method of filling the gaps).
When using opendir() to open a directory, the directory stream will point to the head of the directory list (0). After using readdir() to read a directory entry, the directory stream will move backward and point to the next directory entry. This is actually similar to open(). When using open() to open a file, the file position offset points to the head of the file by default. When using read() or write() to read or write, the file offset will automatically Move backward.
rewinddir The
rewinddir() function is a C library function that resets the directory stream to the starting point of the directory so that the next call to readdir() will start with the first file in the directory list. The rewinddir function prototype is as follows:

#include <sys/types.h>
#include <dirent.h>
void rewinddir(DIR *dirp);

First, you need to include the header file <dirent.h> to use this function.
The meanings of function parameters and return values ​​are as follows:
dirp: directory handle.
Return value: No return value.
Close the directory closedir function
The closedir() function is used to close the open directory and release the resources it uses. Its function prototype is as follows:

#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);

First, to use this function, you need to include the header files <sys/types.h> and <dirent.h>.
The meanings of function parameters and return values ​​are as follows:
dirp: directory handle.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
Exercise
Based on the knowledge learned in this section, you can do a simple programming exercise, open a directory and print out the names of all files in the directory and their corresponding inode numbers. Sample code looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <errno.h>
int main(void)
{
    
    
    struct dirent *dir;
    DIR *dirp;
    int ret = 0;
    /* 打开目录*/
    dirp = opendir("./my_dir");
    if (NULL == dirp)
    {
    
    
        perror("opendir error");
        exit(-1);
    }
    /* 循环读取目录流中的所有目录条目*/
    errno = 0;
    while (NULL != (dir = readdir(dirp)))
        printf("%s %ld\n", dir->d_name, dir->d_ino);
    if (0 != errno)
    {
    
    
        perror("readdir error");
        ret = -1;
        goto err;
    }
    else
        printf("End of directory!\n");
err:
    closedir(dirp);
    exit(ret);
}

Use opendir() to open the my_dir directory in the current directory. The files in this directory are as follows:
Insert image description here
Next, compile and run:
Insert image description here
It can be seen that sample code 5.8.4 can scan out all the files in the my_dir directory and print them. Show their names and inode
nodes.

The current working directory of the process

Each process under Linux has its own current working directory (current working directory). The current working directory is the starting point for the process to parse and search for relative path names (not the absolute path starting with a "/" slash). For example, when the open function is called in the code to open a file, and the incoming file path is expressed as a relative path, then when the process parses the relative path name, the current working directory of the process will be used as the reference directory.
Generally, when a process is running, the current working directory of its parent process will be inherited by the process and become the current working directory of the process. The current working directory of the process can be obtained through the getcwd function, as shown below:

#include <unistd.h>
char *getcwd(char *buf, size_t size);

This is a system call. Before using this function, you need to include the header file <unistd.h>.
The meanings of function parameters and return values ​​are as follows:
buf: getcwd() stores the string containing the absolute path of the current working directory in the buf buffer.
size: The size of the buffer. The allocated buffer size must be greater than the string length, otherwise the call will fail.
Return value: If the call succeeds, it will return a pointer to buf, if it fails, it will return NULL and set errno.
Tips: If the buf passed in is NULL and the size is 0, getcwd() will internally allocate a buffer as needed, and use the pointer to the buffer as the return value of the function. In order to avoid memory leaks, the caller uses After completion, free() must be called to release the memory space occupied by this buffer.
Testing
Next, we write a simple test program that reads the current working directory of the process:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
    
    
    char buf[100];
    char *ptr;
    memset(buf, 0x0, sizeof(buf));
    ptr = getcwd(buf, sizeof(buf));
    if (NULL == ptr)
    {
    
    
        perror("getcwd error");
        exit(-1);
    }
    printf("Current working directory: %s\n", buf);
    exit(0);
}

Compile and run:
Insert image description here
change the current working directory.
The system calls chdir() and fchdir() can be used to change the current working directory of the process. The function prototype is as follows:

#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);

First, using one of these two functions requires including the header file <unistd.h>.
The meanings of function parameters and return values ​​are as follows:
path: Change the current working directory of the process to the directory specified by the path parameter, which can be an absolute path or a relative path. The specified directory must exist, otherwise an error will be reported.
fd: Change the current working directory of the process to the directory specified by the fd file descriptor (for example, use the open function to open a directory).
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
The difference between these two functions is that the way to specify the directory is different. chdir() is specified in the form of a path, while fchdir() is specified through a file descriptor. The file descriptor can be obtained when opening the corresponding directory by calling open().
test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
    
    
    char buf[100];
    char *ptr;
    int ret;
    /* 获取更改前的工作目录*/
    memset(buf, 0x0, sizeof(buf));
    ptr = getcwd(buf, sizeof(buf));
    if (NULL == ptr)
    {
    
    
        perror("getcwd error");
        exit(-1);
    }
    printf("Before the change: %s\n", buf);
    /* 更改进程的当前工作目录*/
    ret = chdir("./new_dir");
    if (-1 == ret)
    {
    
    
        perror("chdir error");
        exit(-1);
    }
    /* 获取更改后的工作目录*/
    memset(buf, 0x0, sizeof(buf));
    ptr = getcwd(buf, sizeof(buf));
    if (NULL == ptr)
    {
    
    
        perror("getcwd error");
        exit(-1);
    }
    printf("After the change: %s\n", buf);
    exit(0);
}

The above program will obtain the current working directory before changing the working directory and print it out, and then call the chdir function to change the working directory of the process to the new_dir directory under the current directory. After the change is successful, the current working directory of the process will be obtained and printed. Come out, then compile and test:
Insert image description here

Delete Files

Earlier I introduced to you how to delete a directory by using the rmdir() function. Obviously this function cannot delete an ordinary file, so how to delete an ordinary file? The method is to call unlink() through the system or use the C library function remove().
Use the unlink function to delete files.
unlink() is used to delete a file (excluding directories). The function prototype is as follows:

#include <unistd.h>
int unlink(const char *pathname);

To use this function, you need to include the header file <unistd.h>.
The meanings of function parameters and return values ​​are as follows:
pathname: The path of the file to be deleted. You can use a relative path or an absolute path. If the file specified by the pathname parameter does not exist, the call to unlink() fails.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
I will introduce the link function to you earlier, which is used to create a hard link file. When creating a hard link, the number of links on the inode node will increase; the function of
unlink() is opposite to that of link(), and the unlink() system call is used to remove /Deletes a hard link (removes the directory entry from its parent directory).
So the unlink() system call essentially removes the directory entry corresponding to the file path specified by the pathname parameter (removes the directory entry from its parent directory), and sets the inode link count of the file to 1, if the file still has If there are other hard links, the data of the file can be accessed through other links; only when the link count becomes 0, the contents of the file can be deleted. Another condition also prevents deletion of the file's contents -
as long as a process has the file open, its contents cannot be deleted. When closing a file, the kernel will check the number of processes that open the file. If the count reaches 0, the kernel will check its link count. If the link count is also 0, then delete the corresponding content of the file (that is, the corresponding The inode and data blocks are reclaimed, if there are multiple hard links in a file, delete any one of the hard links, its
inode and data blocks are not reclaimed, and the data of the file can be accessed through other hard links).
The unlink() system call does not dereference the soft link. If the file specified by pathname is a soft link file, the soft link file itself will be deleted instead of the file specified by the soft link.
test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    
    
    int ret;
    ret = unlink("./test_file");
    if (-1 == ret)
    {
    
    
        perror("unlink error");
        exit(-1);
    }
    exit(0);
}

The above code calls unlink() to delete the test_file file in the current directory. Compile and test:
Insert image description here
Use the remove function to delete the file.
remove() is a C library function used to remove a file or empty directory. Its function prototype is as follows:

#include <stdio.h>
int remove(const char *pathname);

To use this function, you need to include the C library function header file <stdio.h>.
The meanings of function parameters and return values ​​are as follows:
pathname: the file or directory path to be deleted, which can be a relative path or a determined path.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
If the pathname parameter specifies a non-directory file, then remove() calls unlink(). If the pathname parameter specifies a directory, then remove() calls rmdir().
Like unlink() and rmdir(), remove() does not dereference soft links. If the pathname parameter specifies a soft link file, remove() will delete the link file itself, not the file it points to.
test

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    int ret;
    ret = remove("./test_file");
    if (-1 == ret)
    {
    
    
        perror("remove error");
        exit(-1);
    }
    exit(0);
}

File rename

This section introduces the rename() system call. With the help of rename(), you can rename files and move files to another directory in the same file system. The function prototype is as follows:

#include <stdio.h>
int rename(const char *oldpath, const char *newpath);

To use this function, you need to include the header file <stdio.h>.
The meanings of function parameters and return values ​​are as follows:
oldpath: original file path.
newpath: new file path.
Return value: 0 is returned on success; -1 is returned on failure and errno is set.
Calling rename() will rename an existing pathname oldpath to the pathname specified by the newpath parameter. The rename() call only operates directory entries and does not move file data (it does not change the file inode number and does not move the content stored in the file data block). Renaming does not affect other hard links pointing to the file, nor does it affect the files that have been opened. The process of the file (for example, the file was opened by another process before renaming and has not been closed).
Depending on the difference between oldpath and newpath, the following different situations need to be explained:
⚫ If the file or directory specified by the newpath parameter already exists, it will be overwritten;
⚫ If newpath and oldpath point to the same file, there will be no change (and the call success).
⚫ The rename() system call does not dereference the soft links in its two parameters. If oldpath is a soft link, the soft link will be renamed; if newpath is a soft link, it will be removed and overwritten.
⚫ If oldpath refers to a file rather than a directory, then newpath cannot be specified as the pathname of a directory. To rename a file to a directory, newpath must contain the new file name.
⚫ If oldpath refers to a directory, in which case newpath either does not exist or must be specified as an empty directory.
⚫ The files referred to by oldpath and newpath must be located in the same file system. From the previous introduction, we can draw this conclusion!
⚫ Cannot rename . (current directory) and ... (previous directory).
test

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    
    
    int ret;
    ret = rename("./test_file", "./new_file");
    if (-1 == ret)
    {
    
    
        perror("rename error");
        exit(-1);
    }
    exit(0);
}

Rename the test_file file in the current directory to new_file, and then compile the test:

Insert image description here

As can be seen from the figure, after using rename to rename the file, its inode number has not changed.

Guess you like

Origin blog.csdn.net/zhuguanlin121/article/details/132569084