Unix C, Day04

====================
第四课  文件系统(下)
====================

一、sync/fsync/fdatasync
------------------------

1. 大多数磁盘I/O都通过缓冲进行, 写入文件其实只是写入缓冲区,直到缓冲区满, 才将其排入写队列。

2. 延迟写降低了写操作的次数,提高了写操作的效率, 但可能导致磁盘文件与缓冲区数据不同步。

3. sync/fsync/fdatasync用于强制磁盘文件与缓冲区同步。

4. sync将所有被修改过的缓冲区排入写队列即返回, 不等待写磁盘操作完成。

5. fsync只针对一个文件,且直到写磁盘操作完成才返回。

6. fdatasync只同步文件数据,不同步文件属性。

#include <unistd.h>

void sync (void);

int fsync (
    int fd
);

成功返回0,失败返回-1。

int fdatasync (
    int fd
);

成功返回0,失败返回-1。

              +-fwrite-> 标准库缓冲 -fflush-+             sync
应用程序内存 -+                             +-> 内核缓冲 -fdatasync-> 磁盘(缓冲)
              +------------write------------+             fsync

二、fcntl
---------

#include <fcntl.h>

int fcntl (
    int fd,  // 文件描述符
    int cmd, // 操作指令
    ...      // 可变参数,因操作指令而异
);

对fd文件执行cmd操作,某些操作需要提供参数。

1. 常用形式
~~~~~~~~~~~

#include <fcntl.h>

int fcntl (int fd, int cmd);
int fcntl (int fd, int cmd, long arg);

成功返回值因cmd而异,失败返回-1。

cmd取值:

F_DUPFD - 复制fd为不小于arg的文件描述符。
      若arg文件描述符已用, 该函数会选择比arg大的最小未用值, 而非如dup2函数那样关闭之。
F_GETFL - 获取文件状态标志。 不能获取O_CREAT/O_EXCL/O_TRUNC。

F_SETFL - 追加文件状态标志。 只能追加O_APPEND/O_NONBLOCK。


F_GETFD - 获取文件描述符标志。

F_SETFD - 设置文件描述符标志。

目前仅定义了一个文件描述符标志位(file descriptor FLAG)FD_CLOEXEC:

0 - 在通过execve()函数所创建的进程中,
    该文件描述符依然保持打开。

1 - 在通过execve()函数所创建的进程中,
    该文件描述符将被关闭。


范例:dup.c、flags.c
/*
 * fcntl函数练习
 * */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) {
	int fd1 = open ("dup1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd1 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd1 = %d\n", fd1);

	int fd2 = open ("dup2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd2 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd2 = %d\n", fd2);
	/*
	int fd3 = dup2 (fd1, fd2);
	if (fd3 == -1) {
		perror ("dup2");
		return -1;
	}
	*/
	int fd3 = fcntl (fd1, F_DUPFD, fd2);
	if (fd3 == -1) {
		perror ("fcntl");
		return -1;
	}

	printf ("fd3 = %d\n", fd3);

	const char* text = "123";
	if (write (fd1, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	text = "456";
	if (write (fd2, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	text = "789";
	if (write (fd3, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	close (fd3);
	close (fd2);
	close (fd1);

	return 0;
}

/*
 * fcntl()练习 读写文件状态标志
 *  获取文件文件描述符表(file descriptor table)里文件状态标志flag
 *  */
#include <stdio.h>
#include <fcntl.h>

void pflags (int flags) {
	printf ("文件状态标志(%08X):", flags);

	struct {
		int flag;
		const char* desc;
	}	flist[] = {
		O_RDONLY,   "O_RDONLY",
		O_WRONLY,   "O_WRONLY",
		O_RDWR,     "O_RDWR",
		O_APPEND,   "O_APPEND",
		O_CREAT,    "O_CREAT",
		O_EXCL,     "O_EXCL",
		O_TRUNC,    "O_TRUNC",
		O_NOCTTY,   "O_NOCTTY",
		O_NONBLOCK, "O_NONBLOCK",
		O_SYNC,     "O_SYNC",
		O_DSYNC,    "O_DSYNC",
		O_RSYNC,    "O_RSYNC",
		O_ASYNC,    "O_ASYNC"
	};

	size_t i;
	int first = 1;
	for (i = 0; i < sizeof (flist) / sizeof (flist[0]); i++)
		if (flags & flist[i].flag) {
			printf ("%s%s", first ? "" : " | ", flist[i].desc);
			first = 0;
		}

	printf ("\n");
}

int main (void) {
	int fd = open ("flags.txt", O_WRONLY | O_CREAT | O_TRUNC |
		O_ASYNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	int flags = fcntl (fd, F_GETFL);
	if (flags == -1) {
		perror ("fcntl");
		return -1;
	}

	pflags (flags); // 不能获取O_CREATE/O_EXCL/O_TRUNC

	if (fcntl (fd, F_SETFL, O_RDWR | O_APPEND | O_NONBLOCK) == -1) {
		perror ("fcntl");
		return -1;
	}

	if ((flags = fcntl (fd, F_GETFL)) == -1) {
		perror ("fcntl");
		return -1;
	}

	pflags (flags); // 只能追加O_APPEND/O_NONBLOCK

	close (fd);

	return 0;
}



2. 文件锁
~~~~~~~~~

#include <fcntl.h>

int fcntl (int fd, int cmd, struct flock* lock);

其中:

struct flock {
    short int l_type;   // 锁的类型:
                        // F_RDLCK/F_WRLCK/F_UNLCK
                        // (读锁/写锁/解锁)
    short int l_whence; // 偏移起点: SEEK_SET/SEEK_CUR/SEEK_END (文件头/当前位置/文件尾)
                // 这里"偏移起点"作为锁区起始位置l_start的0位置参考点使用,用l_start相对l_whence的位置表明锁区位置
    off_t     l_start;  // 锁区偏移,从l_whence开始
    off_t     l_len;    // 锁区长度,0表示锁到文件尾
    pid_t     l_pid;    // 加锁进程,-1表示自动设置
};

cmd取值:

F_GETLK  - 测试lock所表示的锁是否可加。
           若可加则将lock.l_type置为F_UNLCK,
           否则通过lock返回当前锁的信息。


F_SETLK  - 设置锁定状态为lock.l_type,
           成功返回0,失败返回-1。
           若因其它进程持有锁而导致失败,
           则errno为EACCES或EAGAIN。

F_SETLKW - 设置锁定状态为lock.l_type,
           成功返回0,否则一直等待,
           除非被信号打断返回-1。

1) 既可以锁定整个文件,也可以锁定特定区域。

2) 读锁(共享锁)、写锁(独占锁/排它锁)、解锁。












3) 文件描述符被关闭(进程结束)时,自动解锁。

4) 劝谏锁(协议锁)、强制锁。

范例:lock1.c、lock2.c
/*
 * fcntl()练习
 * 协议锁练习
 * */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

// 加读锁
int rlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 加写锁
int wlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 解锁
int ulock (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, F_SETLK, &lock);
}

int main (void) {
	printf ("进程标识(PID):%u\n", getpid ());

	int fd = open ("lock.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	const char* text = "ABCDEFGHIJKLMNOPQR";
	if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	// 对EFGH加读锁
	printf ("对EFGH加读锁");
	if (rlock (fd, 4, 4, 0) == -1) {
		printf ("失败:%m\n");
		return -1;
	}

	printf ("成功!\n");

	// 对MNOP加写锁
	printf ("对MNOP加写锁");
	if (wlock (fd, 12, 4, 0) == -1) {
		printf ("失败:%m\n");
		return -1;
	}

	printf ("成功!\n");

	printf ("按<回车>,解锁MN...");
	getchar ();
	// 解锁NM
	ulock (fd, 12, 2);

	printf ("按<回车>,解锁EFGH...");
	getchar ();
	// 解锁EFGH
	ulock (fd, 4, 4);

	close (fd);

	return 0;
}

/*fcntl()练习
 * 协议锁
 */
#include <stdio.h>
#include <fcntl.h>

// 打印锁信息
void plock (struct flock lock) {
	if (lock.l_type == F_UNLCK)
		printf ("没有锁。\n");
	else {
		printf ("%d进程", lock.l_pid);

		switch (lock.l_whence/*l_whence是锁区的参考位置*/) {
			case SEEK_SET:
				printf ("在距文件头");
				break;
			case SEEK_CUR:
				printf ("在距当前位置");
				break;
			case SEEK_END:
				printf ("在距文件尾");
				break;
		}

		printf ("%d字节处,为%d字节加了", lock.l_start, lock.l_len);

		switch (lock.l_type) {
			case F_RDLCK:
				printf ("读锁。\n");
				break;
			case F_WRLCK:
				printf ("写锁。\n");
				break;
		}
	}
}

// 读锁测试
int rtest (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	if (fcntl (fd, F_GETLK, &lock) == -1)
		return -1;

	plock (lock);

	return 0;
}

// 写锁测试
int wtest (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	if (fcntl (fd, F_GETLK, &lock) == -1)
		return -1;

	plock (lock);

	return 0;
}

// 加读锁
int rlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 加写锁
int wlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 解锁
int ulock (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, F_SETLK, &lock);
}

int main (void) {
	int fd = open ("lock.txt", O_RDWR);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	// 对CDEF做读锁测试
	printf ("对CDEF做读锁测试。");
	if (rtest (fd, 2, 4) == -1) { 
		printf ("失败:%m\n");
		return -1;
	}

	// 对CDEF加读锁
	printf ("对CDEF加读锁");
	if (rlock (fd, 2, 4, 0) == -1)
		printf ("失败:%m\n");
	else {
		printf ("成功!\n");
		ulock (fd, 2, 4);
	}

	// 对CDEF做写锁测试
	printf ("对CDEF做写锁测试。");
	if (wtest (fd, 2, 4) == -1) {
		printf ("失败:%m\n");
		return -1;
	}

	// 对CDEF加写锁
	printf ("对CDEF加写锁");
	if (wlock (fd, 2, 4, 0) == -1)
		printf ("失败:%m\n");
	else {
		printf ("成功!\n");
		ulock (fd, 2, 4);
	}

	// 对KLMN做读锁测试
	printf ("对KLMN做读锁测试。");
	if (rtest (fd, 10, 4) == -1) {
		printf ("失败:%m\n");
		return -1;
	}

	// 对KLMN加读锁
	printf ("对KLMN加读锁");
	if (rlock (fd, 10, 4, 0) == -1)
		printf ("失败:%m\n");
	else {
		printf ("成功!\n");
		ulock (fd, 10, 4);
	}

	// 对KLMN做写锁测试
	printf ("对KLMN做写锁测试。");
	if (wtest (fd, 10, 4) == -1) {
		printf ("失败:%m\n");
		return -1;
	}

	// 对KLMN加写锁
	printf ("对KLMN加写锁");
	if (wlock (fd, 10, 4, 0) == -1)
		printf ("失败:%m\n");
	else {
		printf ("成功!\n");
		ulock (fd, 10, 4);
	}

	printf ("等待KLMN上的写锁被解除...\n");

	// 对KLMN加写锁
	printf ("对KLMN加写锁");
	if (wlock (fd, 10, 4, 1) == -1)
		printf ("失败:%m\n");
	else {
		printf ("成功!\n");
		ulock (fd, 10, 4);
	}

	close (fd);

	return 0;
}



5) 文件锁仅在不同进程间起作用。

6) 通过锁同步多个进程对同一个文件的读写访问。

范例:wlock.c、rlock.c
/*文件名: rlock.c
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

// 读锁测试
// 返回值:
//  1 - 可加读锁
//  0 - 不可加读锁
// -1 - 系统错误
int rtest (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	if (fcntl (fd, F_GETLK, &lock) == -1)
		return -1;

	if (lock.l_type == F_UNLCK)
		return 1;

	return 0;
}

// 加读锁
int rlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 解锁
int ulock (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, F_SETLK, &lock);
}

int main (int argc, char* argv[]) {
	int lock = 0;
	if (argc > 1)
		if (! strcmp (argv[1], "-l"))
			lock = 1;
		else
			goto usage;

	int fd = open ("wlock.txt", O_RDONLY);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	if (lock) {
		// 基于锁测试的非阻塞模式
		/*
		int unlock = 0;
		do {
			if ((unlock = rtest (fd, 0, 0)) == -1) {
				perror ("rtest");
				return -1;
			}

			if (! unlock) {
				printf ("该文件已被锁定,稍后再试...\n");
				// 空闲处理
				// ...
			}
		}	while (! unlock);

		if (rlock (fd, 0, 0, 0) == -1) {
			perror ("rlock");
			return -1;
		}
		*/
		// 基于锁失败的非阻塞模式
		/*
		while (rlock (fd, 0, 0, 0) == -1) {
			if (errno != EACCES && errno != EAGAIN) {
				perror ("rlock");
				return -1;
			}
	
			printf ("该文件已被锁定,稍后再试...\n");
			// 空闲处理
			// ...
		}
		*/
		// 阻塞模式
		if (rlock (fd, 0, 0, 1) == -1) {
			perror ("rlock");
			return -1;
		}
	}

	char buf[1024];
	ssize_t readed;

	while ((readed = read (fd, buf, sizeof (buf))) > 0)
		write (STDOUT_FILENO, buf, readed);
	printf ("\n");

	if (readed == -1) {
		perror ("read");
		return -1;
	}

	if (lock)
		if (ulock (fd, 0, 0) == -1) {
			perror ("ulock");
			return -1;
		}

	close (fd);

	return 0;

usage:
	fprintf (stderr, "用法:%s [-l]\n", argv[0]);
	return -1;
}

/*文件名:wlock.c
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

// 写锁测试
// 返回值:
//  1 - 可加写锁
//  0 - 不可加写锁
// -1 - 系统错误
int wtest (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	if (fcntl (fd, F_GETLK, &lock) == -1)
		return -1;

	if (lock.l_type == F_UNLCK)
		return 1;

	return 0;
}

// 加写锁
int wlock (int fd, off_t start, off_t len, int wait) {
	struct flock lock;

	lock.l_type   = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, wait ? F_SETLKW : F_SETLK, &lock);
}

// 解锁
int ulock (int fd, off_t start, off_t len) {
	struct flock lock;

	lock.l_type   = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start  = start;
	lock.l_len    = len;
	lock.l_pid    = -1;

	return fcntl (fd, F_SETLK, &lock);
}

int main (int argc, char* argv[]) {
	if (argc < 2)
		goto usage;

	int lock = 0;
	if (argc > 2)
		if (! strcmp (argv[2], "-l"))
			lock = 1;
		else
			goto usage;

	int fd = open ("wlock.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	if (lock) {
		// 基于锁测试的非阻塞模式
		/*
		int unlock = 0;
		do {
			if ((unlock = wtest (fd, 0, 0)) == -1) {
				perror ("wtest");
				return -1;
			}

			if (! unlock) {
				printf ("该文件已被锁定,稍后再试...\n");
				// 空闲处理
				// ...
			}
		}	while (! unlock);

		if (wlock (fd, 0, 0, 0) == -1) {
			perror ("wlock");
			return -1;
		}
		*/
		// 基于锁失败的非阻塞模式
		/*
		while (wlock (fd, 0, 0, 0) == -1) {
			if (errno != EACCES && errno != EAGAIN) {
				perror ("wlock");
				return -1;
			}

			printf ("该文件已被锁定,稍后再试...\n");
			// 空闲处理
			// ...
		}
		*/
		// 阻塞模式
		if (wlock (fd, 0, 0, 1) == -1) {
			perror ("wlock");
			return -1;
		}
	}

	size_t i, len = strlen (argv[1]);
	for (i = 0; i < len; i++) {
		if (write (fd, &argv[1][i], sizeof (argv[1][i])) == -1) {
			perror ("write");
			return -1;
		}
		sleep (1);
	}

	if (lock)
		if (ulock (fd, 0, 0) == -1) {
			perror ("ulock");
			return -1;
		}

	close (fd);

	return 0;

usage:
	fprintf (stderr, "用法:%s <字符串> [-l]\n", argv[0]);
	return -1;
}



三、stat/fstat/lstat
--------------------

获取文件属性。

#include <sys/stat.h>

int stat (
    const char*  path, // 文件路径
    struct stat* buf   // 文件属性
);

int fstat (
    int          fd, // 文件描述符
    struct stat* buf // 文件属性
);

int lstat (
    const char*  path, // 文件路径
    struct stat* buf   // 文件属性
);

成功返回0,失败返回-1。

stat函数跟踪软链接,lstat函数不跟踪软链接。

struct stat {
    dev_t     st_dev;     // 设备ID
    ino_t     st_ino;     // i节点号
    mode_t    st_mode;    // 文件类型和权限
    nlink_t   st_nlink;   // 硬链接数
    uid_t     st_uid;     // 属主ID
    gid_t     st_gid;     // 属组ID
    dev_t     st_rdev;    // 特殊设备ID
    off_t     st_size;    // 总字节数
    blksize_t st_blksize; // I/O块字节数
    blkcnt_t  st_blocks;  // 占用块(512字节)数
    time_t    st_atime;   // 最后访问时间
    time_t    st_mtime;   // 最后修改时间
    time_t    st_ctime;   // 最后状态改变时间
};

st_mode(0TTSUGO)为以下值的位或:

S_IFDIR           - 目录        \
S_IFREG           - 普通文件     |
S_IFLNK           - 软链接       |
S_IFBLK           - 块设备       > TT (S_IFMT)
S_IFCHR           - 字符设备     |    //用了6个二进制位来代表7个文件类型
S_IFSOCK          - Unix域套接字 |    //它们是000000/000001/000010/000100/001000/010000/100000
S_IFIFO           - 有名管道    /
--------------------------------
S_ISUID           - 设置用户ID  \
S_ISGID           - 设置组ID     > S
S_ISVTX           - 粘滞        /
--------------------------------
S_IRUSR(S_IREAD)  - 属主可读    \
S_IWUSR(S_IWRITE) - 属主可写     > U (S_IRWXU)
S_IXUSR(S_IEXEC)  - 属主可执行  /
--------------------------------
S_IRGRP           - 属组可读    \
S_IWGRP           - 属组可写     > G (S_IRWXG)
S_IXGRP           - 属组可执行  /
--------------------------------
S_IROTH           - 其它可读    \
S_IWOTH           - 其它可写     > O (S_IRWXO)
S_IXOTH           - 其它可执行  /

1. 有关S_ISUID/S_ISGID/S_ISVTX的说明

1) 具有S_ISUID/S_ISGID位的可执行文件,
   其有效用户ID/有效组ID,
   并不取自由其父进程(比如登录shell)所决定的,
   实际用户ID/实际组ID,
   而是取自该可执行文件的属主ID/属组ID。
   如:/usr/bin/passwd

2) 具有S_ISUID位的目录,
   其中的文件或目录除root外,
   只有其属主可以删除。

3) 具有S_ISGID位的目录,
   在该目录下所创建的文件,继承该目录的属组ID,
   而非其创建者进程的有效组ID。

4) 具有S_ISVTX位的可执行文件,
   在其首次执行并结束后,
   其代码区将被连续地保存在磁盘交换区中,
   而一般磁盘文件中的数据块是离散存放的。
   因此,下次执行该程序可以获得较快的载入速度。
   现代Unix系统大都采用快速文件系统,
   已不再需要这种技术。

5) 具有S_ISVTX位的目录,
   只有对该目录具有写权限的用户,
   在满足下列条件之一的情况下,
   才能删除或更名该目录下的文件或目录:

   A. 拥有此文件;
   B. 拥有此目录;
   C. 是超级用户。

   如:/tmp

   任何用户都可在该目录下创建文件,
   任何用户对该目录都享有读/写/执行权限,
   但除root以外的任何用户在目录下,
   都只能删除或更名属于自己的文件。

2. 常用以下宏辅助分析st_mode

S_ISDIR()  - 是否目录
S_ISREG()  - 是否普通文件
S_ISLNK()  - 是否软链接
S_ISBLK()  - 是否块设备
S_ISCHR()  - 是否字符设备
S_ISSOCK() - 是否Unix域套接字
S_ISFIFO() - 是否有名管道

范例:stat.c
/*
 * 用函数stat()获得文件信息*/
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

const char* mtos (mode_t m) {
	static char s[11];

	if (S_ISDIR (m))
		strcpy (s, "d");
	else
	if (S_ISLNK (m))
		strcpy (s, "l");
	else
	if (S_ISBLK (m))
		strcpy (s, "b");
	else
	if (S_ISCHR (m))
		strcpy (s, "c");
	else
	if (S_ISSOCK (m))
		strcpy (s, "s");
	else
	if (S_ISFIFO (m))
		strcpy (s, "p");
	else
		strcpy (s, "-");

	strcat (s, m & S_IRUSR ? "r" : "-");
	strcat (s, m & S_IWUSR ? "w" : "-");
	strcat (s, m & S_IXUSR ? "x" : "-");
	strcat (s, m & S_IRGRP ? "r" : "-");
	strcat (s, m & S_IWGRP ? "w" : "-");
	strcat (s, m & S_IXGRP ? "x" : "-");
	strcat (s, m & S_IROTH ? "r" : "-");
	strcat (s, m & S_IWOTH ? "w" : "-");
	strcat (s, m & S_IXOTH ? "x" : "-");

	if (m & S_ISUID)
		s[3] = (s[3] == 'x' ? 's' : 'S');
	if (m & S_ISGID)
		s[6] = (s[6] == 'x' ? 's' : 'S');
	if (m & S_ISVTX)
		s[9] = (s[9] == 'x' ? 't' : 'T');

	return s;
}

const char* ttos (time_t t) {
	static char s[20];

	struct tm* lt = localtime (&t);
	sprintf (s, "%04d-%02d-%02d %02d:%02d:%02d",
		lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
		lt->tm_hour, lt->tm_min, lt->tm_sec);

	return s;
}

int main (int argc, char* argv[]) {
	if (argc < 2)
		goto usage;

	struct stat st;

	if (argc < 3) {
		if (stat (argv[1], &st) == -1) {
			perror ("stat");
			return -1;
		}
	}
	else if (! strcmp (argv[2], "-l")) {
		if (lstat (argv[1], &st) == -1) {
			perror ("lstat");
			return -1;
		}
	}
	else
		goto usage;

	printf ("           设备ID:%u\n", st.st_dev);
	printf ("          i节点号:%u\n", st.st_ino);
	//printf ("             模式:%#o\n", st.st_mode);
	printf ("             模式:%s\n", mtos (st.st_mode));
	printf ("         硬链接数:%u\n", st.st_nlink);
	printf ("           属主ID:%u\n", st.st_uid);
	printf ("           属组ID:%u\n", st.st_gid);
	printf ("       特殊设备ID:%u\n", st.st_rdev);
	printf ("         总字节数:%u\n", st.st_size);
	printf ("      I/O块字节数:%u\n", st.st_blksize);
	printf ("占用块(512字节)数:%u\n", st.st_blocks);
	printf ("     最后访问时间:%s\n", ttos (st.st_atime));
	printf ("     最后修改时间:%s\n", ttos (st.st_mtime));
	printf (" 最后状态改变时间:%s\n", ttos (st.st_ctime));

	return 0;

usage:
	fprintf (stderr, "用法:%s <文件> [-l]\n", argv[0]);
	return -1;
}



四、access
----------

#include <unistd.h>

int access (
    const char* pathname, // 文件路径
    int         mode      // 访问模式
);

1. 按实际用户ID和实际组ID(而非有效用户ID和有效组ID),
   进行访问模式测试。

2. 成功返回0,失败返回-1。

3. mode取R_OK/W_OK/X_OK的位或,
   测试调用进程对该文件,
   是否可读/可写/可执行,
   或者取F_OK,测试该文件是否存在。

范例:access.c
/*
 * access()函数练习
 * 按照进程实际用户ID和实际组ID进行访问模式测试
 * */
#include <stdio.h>
#include <unistd.h>

int main (int argc, char* argv[]) {
	if (argc < 2) {
		fprintf (stderr, "用法:%s <文件>\n", argv[0]);
		return -1;
	}

	printf ("文件%s", argv[1]);
	if (access (argv[1], F_OK) == -1)
		printf ("不存在(%m)。\n");
	else {
		if (access (argv[1], R_OK) == -1)
			printf ("不可读(%m),");
		else
			printf ("可读,");

		if (access (argv[1], W_OK) == -1)
			printf ("不可写(%m),");
		else
			printf ("可写,");

		if (access (argv[1], X_OK) == -1)
			printf ("不可执行(%m)。\n");
		else
			printf ("可执行。\n");
	}

	return 0;
}



五、umask
---------

可以用umask命令查看/修改当前shell的文件权限屏蔽字:

# umask
0022

# umask 0033
# umask
0033

#include <sys/stat.h>

mode_t umask (
    mode_t cmask // 屏蔽字
);

1. 为进程设置文件权限屏蔽字,并返回以前的值,
   此函数永远成功。

2. cmask由9个权限宏位或组成(直接写八进制整数形式亦可,
   如022 - 屏蔽属组和其它用户的写权限):

S_IRUSR(S_IREAD)  - 属主可读
S_IWUSR(S_IWRITE) - 属主可写
S_IXUSR(S_IEXEC)  - 属主可执行
------------------------------
S_IRGRP           - 属组可读
S_IWGRP           - 属组可写
S_IXGRP           - 属组可执行
------------------------------
S_IROTH           - 其它可读
S_IWOTH           - 其它可写
S_IXOTH           - 其它可执行

3. 设上屏蔽字以后,此进程所创建的文件,
   都不会有屏蔽字所包含的权限。

范例:umask.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

int main (void) {
//	mode_t old = umask (0333);
	mode_t old = umask (
		S_IWUSR | S_IXUSR |
		S_IWGRP | S_IXGRP |
		S_IWOTH | S_IXOTH);

	int fd = open ("umask.txt", O_RDWR | O_CREAT | O_TRUNC, 0777);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	close (fd);
	umask (old);

	return 0;
}



六、chmod/fchmod
----------------

修改文件的权限。

#include <sys/stat.h>

int chmod (
    const char* path, // 文件路径
    mode_t      mode  // 文件权限
);

int fchmod (
    int    fd,  // 文件路径
    mode_t mode // 文件权限
);

成功返回0,失败返回-1。

mode为以下值的位或(直接写八进制整数形式亦可,如07654 - rwSr-sr-T):

S_ISUID           - 设置用户ID
S_ISGID           - 设置组ID
S_ISVTX           - 粘滞
------------------------------
S_IRUSR(S_IREAD)  - 属主可读
S_IWUSR(S_IWRITE) - 属主可写
S_IXUSR(S_IEXEC)  - 属主可执行
------------------------------
S_IRGRP           - 属组可读
S_IWGRP           - 属组可写
S_IXGRP           - 属组可执行
------------------------------
S_IROTH           - 其它可读
S_IWOTH           - 其它可写
S_IXOTH           - 其它可执行

范例:chmod.c
/*
 * chmod()函数练习
 * */
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

int main (void) {
	int fd = open ("chmod.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

//	if (fchmod (fd, 07654) == -1) {
	if (fchmod (fd,
		S_ISUID | S_ISGID | S_ISVTX |
		S_IRUSR | S_IWUSR |
		S_IRGRP | S_IXGRP |
		S_IROTH) == -1) {
		perror ("fchmod");
		return -1;
	}

	close (fd);

	return 0;
}



七、chown/fchown/lchown
-----------------------

# chown <uid>:<gid> <file>

修改文件的属主和属组。

#include <unistd.h>

int chown (
    const char* path,  // 文件路径
    uid_t       owner, // 属主ID
    gid_t       group  // 属组ID
);

int fchown (
    int   fildes, // 文件描述符
    uid_t owner,  // 属主ID
    gid_t group   // 属组ID
);

int lchown (
    const char* path,  // 文件路径(不跟踪软链接)
    uid_t       owner, // 属主ID
    gid_t       group  // 属组ID
);

成功返回0,失败返回-1。

注意:

1. 属主和属组ID取-1表示不修改。

2. 超级用户进程可以修改文件的属主和属组,
   普通进程必须拥有该文件才可以修改其属主和属组。

八、truncate/ftruncate
----------------------

修改文件的长度,截短丢弃,加长添零。

#include <unistd.h>

int truncate (
    const char* path,  // 文件路径
    off_t       length // 文件长度
);

int ftruncate (
    int   fd,    // 文件描述符
    off_t length // 文件长度
);

成功返回0,失败返回-1。

范例:trunc.c、mmap.c
/*文件名: trunc.c
 * truncate()练习
 *
 * mmap()/mumap()练习   虚拟内存映射到磁盘文件
 * */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>

int main (void) {
	const char* text = "Hello, World !";
	size_t size = (strlen (text) + 1) * sizeof (text[0]);

	int fd = open ("trunc.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	if (ftruncate (fd, size) == -1) {
		perror ("ftruncate");
		return -1;
	}

	void* map = mmap (NULL, size, PROT_READ | PROT_WRITE, 
		MAP_SHARED/*MAP_PRIVATE*/, fd, 0);
	if (map == MAP_FAILED) {
		perror ("mmap");
		return -1;
	}

	memcpy (map, text, size);
	printf ("%s\n", map);

	munmap (map, size);
	close (fd);

	return 0;
}

/*mmap() mumap()练习
 * 虚拟内存映射-映射到磁盘文件
 * */
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main (void) {
	int fd = open ("trunc.txt", O_RDONLY);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	struct stat st;
	if (fstat (fd, &st) == -1) {
		perror ("fstat");
		return -1;
	}

	void* map = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if (map == MAP_FAILED) {
		perror ("mmap");
		return -1;
	}

	printf ("%s\n", map);

	munmap (map, st.st_size);
	close (fd);

	return 0;
}



注意:对于虚拟内存到磁盘文件的映射,私有映射(MAP_PRIVATE)将数据写到缓冲区而非文件中,只有自己可以访问。
而对于虚拟内存到物理内存的映射,私有(MAP_PRIVATE)和公有(MAP_SHARED)没有区别,都是仅自己可以访问。

九、link/unlink/remove/rename
-----------------------------

link: 创建文件的硬链接(目录条目)。

unlink: 删除文件的硬链接(目录条目)。
只有当文件的硬链接数降为0时,文件才会真正被删除。
若该文件正在被某个进程打开,
其内容直到该文件被关闭才会被真正删除。

remove: 对文件同unlink,
对目录同rmdir (不能删非空目录)。

rename: 修改文件/目录名。

#include <unistd.h>

int link (
    const char* path1, // 文件路径
    const char* path2  // 链接路径
);

int unlink (
    const char* path // 链接路径
);

#include <stdio.h>

int remove (
    const char* pathname // 文件/目录路径
);

int rename (
    const char* old, // 原路径名
    const char* new  // 新路径名
);

成功返回0,失败返回-1。

注意:硬链接只是一个文件名,即目录中的一个条目。
软链接则是一个独立的文件,
其内容是另一个文件的路径信息。

十、symlink/readlink
--------------------

symlink: 创建软链接。目标文件可以不存在,
也可以位于另一个文件系统中。

readlink: 获取软链接文件本身(而非其目标)的内容。
open不能打开软链接文件本身。

#include <unistd.h>

int symlink (
    const char* oldpath, // 文件路径(可以不存在)
    const char* newpath  // 链接路径
);

成功返回0,失败返回-1。

ssize_t readlink (
    const char* restrict path,   // 软链接文件路径
    char* restrict       buf,    // 缓冲区
    size_t               bufsize // 缓冲区大小
);

成功返回实际拷入缓冲区buf中软链接文件内容的字节数,
失败返回-1。

范例:slink.c
/*
 * syslink()练习
 * */
#include <stdio.h>
#include <limits.h>

int main (int argc, char* argv[]) {
	if (argc < 3) {
		fprintf (stderr, "用法:%s <文件> <软链接>\n", argv[0]);
		return -1;
	}

	if (symlink (argv[1], argv[2]) == -1) {
		perror ("symlink");
		return -1;
	}

	char slink[PATH_MAX+1] = {0};

	if (readlink (argv[2], slink,
		sizeof (slink) - sizeof (slink[0])) == -1) {
		perror ("readlink");
		return -1;
	}

	printf ("%s是%s的软链接。\n", argv[2], slink);

	return 0;
}



十一、mkdir/rmdir
-----------------

mkdir: 创建一个空目录。

rmdir: 删除一个空目录。

#include <sys/stat.h>

int mkdir (
    const char* path, // 目录路径
    mode_t      mode  // 访问权限,
                      // 目录的执行权限(x)表示可进入
);

#include <unistd.h>

int rmdir (
    const char* path // 目录路径
);

成功返回0,失败返回-1。

十二、chdir/fchdir/getcwd
-------------------------

chdir/fchdir: 更改当前工作目录。
工作目录是进程的属性,只影响调用进程本身。

getcwd: 获取当前工作目录。

#include <unistd.h>

int chdir (
    const char* path // 工作目录路径
);

int fchdir (
    int fildes // 工作目录描述符(由open函数返回)
);

成功返回0,失败返回-1。

char* getcwd (
    char*  buf, // 缓冲区
    size_t size // 缓冲区大小
);

成功返回当前工作目录字符串指针,失败返回NULL。

范例:dir.c
/*
 * getcwd();chdir();mkdir();练习
 * */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>

int main (void) {
	char cwd[PATH_MAX+1];
	if (! getcwd (cwd, sizeof (cwd))) {
		perror ("getcwd");
		return -1;
	}

	printf ("当前工作目录:%s\n", cwd);

	if (mkdir ("work", 0755) == -1) {
		perror ("mkdir");
		return -1;
	}

	if (chdir ("work") == -1) {
		perror ("chdir");
		return -1;
	}

	if (! getcwd (cwd, sizeof (cwd))) {
		perror ("getcwd");
		return -1;
	}

	printf ("当前工作目录:%s\n", cwd);

	if (mkdir ("empty", 0755) == -1) {
		perror ("mkdir");
		return -1;
	}

	if (rmdir ("empty") == -1) {
		perror ("rmdir");
		return -1;
	}

	if (rmdir ("../work") == -1) {
		perror ("rmdir");
		return -1;
	}

	return 0;
}



十三、opendir/fdopendir/closedir/readdir/rewinddir/telldir/seekdir
------------------------------------------------------------------

opendir/fdopendir: 打开目录流。

closedir: 关闭目录流。

readdir: 读取目录流。

rewinddir: 复位目录流。

telldir: 获取目录流当前位置。

seekdir: 设置目录流当前位置。

#include <sys/types.h>
#include <dirent.h>

DIR* opendir (
    const char* name // 目录路径
);

DIR* fdopendir (
    int fd // 目录描述符(由open函数返回)
);

成功返回目录流指针,失败返回NULL。

int closedir (
    DIR* dirp // 目录流指针
);

成功返回0,失败返回-1。

struct dirent* readdir (
    DIR* dirp // 目录流指针
);

成功返回下一个目录条目结构体的指针,到达目录尾(不置errno)或失败(设置errno)返回NULL。

struct dirent {
    ino_t          d_ino;       // i节点号
    off_t          d_off;       // 下一条目的偏移量
                                // 注意是磁盘偏移量
                                // 而非内存地址偏移
    unsigned short d_reclen;    // 记录长度
    unsigned char  d_type;      // 文件类型
    char           d_name[256]; // 文件名
};

d_type取值:

DT_DIR     - 目录
DT_REG     - 普通文件
DT_LNK     - 软链接
DT_BLK     - 块设备
DT_CHR     - 字符设备
DT_SOCK    - Unix域套接字
DT_FIFO    - 有名管道
DT_UNKNOWN - 未知

范例:list.c
/*
 * readdir()练习
 *
 * 查看目录文件内容*/
#include <stdio.h>
#include <dirent.h>
#include <errno.h>

int main (int argc, char* argv[]) {
	if (argc < 2) {
		fprintf (stderr, "用法:%s <目录>\n", argv[0]);
		return -1;
	}

	DIR* dp = opendir (argv[1]);
	if (! dp) {
		perror ("opendir");
		return -1;
	}

	errno = 0;
	struct dirent* de;

	for (de = readdir (dp); de; de = readdir (dp)) {
		switch (de -> d_type) {
			case DT_DIR:
				printf ("        目录:");
				break;
			case DT_REG:
				printf ("    普通文件:");
				break;
			case DT_LNK:
				printf ("      软链接:");
				break;
			case DT_BLK:
				printf ("      块设备:");
				break;
			case DT_CHR:
				printf ("    字符设备:");
				break;
			case DT_SOCK:
				printf ("Unix域套接字:");
				break;
			case DT_FIFO:
				printf ("    有名管道:");
				break;
			default:
				printf ("        未知:");
				break;
		}

		printf ("%s\n", de -> d_name);
	}

	if (errno) {
		perror ("readdir");
		return -1;
	}

	closedir (dp);

	return 0;
}





代码:tree.c
/* 文件名:tree.c
 * 打印指定目录的目录树
 * */
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
/*
#include <limits.h>
*/
int tree (const char* dir, size_t depth) {
	DIR* dp = opendir (dir);
	if (! dp) {
		perror ("opendir");
		return -1;
	}

	if (chdir (dir) == -1) {
		perror ("chdir");
		return -1;
	}

	errno = 0;
	struct dirent* de;

	for (de = readdir (dp); de; de = readdir (dp))
		if (de -> d_type != DT_DIR)
			printf ("%*s%s\n", depth * 2, "", de -> d_name);
		else if (strcmp (de -> d_name, ".") &&
			strcmp (de -> d_name, "..")) {
			printf ("%*s%s/\n", depth * 2, "", de -> d_name);
			/*
			char subdir[PATH_MAX+1];
			sprintf (subdir, "%s/%s", dir, de -> d_name);

			if (tree (subdir, depth + 1) == -1)
				return -1;
			*/
			if (tree (de -> d_name, depth + 1) == -1)
				return -1;
		}

	if (errno) {
		perror ("readdir");
		return -1;
	}

	if (chdir ("..") == -1) {
		perror ("chdir");
		return -1;
	}

	closedir (dp);

	return 0;
}

int main (int argc, char* argv[]) {
	if (argc < 2) {
		fprintf (stderr, "用法:%s <目录>\n", argv[0]);
		return -1;
	}

	return tree (argv[1], 0);
}



void rewinddir (
    DIR* dirp // 目录流指针
);

long telldir (
    DIR* dirp // 目录流指针
);

成功返回目录流的当前位置,失败返回-1。

void seekdir (
    DIR* dirp,  // 目录流指针
    long offset // 位置偏移量
);

目录流是磁盘上目录文件里各个条目的整体,每个条目都是一个具有下图结构:
(p.s.文件流是磁盘上文件里各字节数据的整体。)

            +-----------------------+           +-----------------------+
            |                       v           |                       v
+-------+---|---+-----+-------+     +-------+---|---+-----+-------+     +-------
| d_ino | d_off | ... | a.txt | ... | d_ino | d_off | ... | b.txt | ... | d_ino
+-------+-------+-----+-------+     +-------+-------+-----+-------+     +-------
^                                   ^
|          -- readdir() ->          |

范例:seek.c
/*文件名:seek.c
 * seekdir()/rewinddir()/telldir()练习
 */
#include <stdio.h>
#include <dirent.h>
#include <errno.h>

int main (int argc, char* argv[]) {
	if (argc < 2) {
		fprintf (stderr, "用法:%s <目录>\n", argv[0]);
		return -1;
	}

	DIR* dp = opendir (argv[1]);
	if (! dp) {
		perror ("opendir");
		return -1;
	}
	/*
	seekdir (dp, 6373414);
	rewinddir (dp);
	*/
	long offset = telldir (dp);
	if (offset == -1) {
		perror ("telldir");
		return -1;
	}

	errno = 0;
	struct dirent* de;

	for (de = readdir (dp); de; de = readdir (dp)) {
		printf ("[%010d %010d] ", offset, de -> d_off);

		switch (de -> d_type) {
			case DT_DIR:
				printf ("        目录:");
				break;
			case DT_REG:
				printf ("    普通文件:");
				break;
			case DT_LNK:
				printf ("      软链接:");
				break;
			case DT_BLK:
				printf ("      块设备:");
				break;
			case DT_CHR:
				printf ("    字符设备:");
				break;
			case DT_SOCK:
				printf ("Unix域套接字:");
				break;
			case DT_FIFO:
				printf ("    有名管道:");
				break;
			default:
				printf ("        未知:");
				break;
		}

		printf ("%s\n", de -> d_name);

		if ((offset = telldir (dp)) == -1) {
			perror ("telldir");
			return -1;
		}
	}

	if (errno) {
		perror ("readdir");
		return -1;
	}

	closedir (dp);

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值