对于UNIX环境编程,工作中经常会用到相关知识,作为学习UNIX环境编程的经典书籍--UNIX环境高级编程,是每个UNIX编程人员必看的经典书籍之一,为了将相关知识重新进行学习,以系统的整合所学知识,遂以博文形式作为总结。
一、概述
本章将描述文件系统的其他特征和文件的性质。将从stat函数开始,诸葛说明stat结构的每一个成员以了解文件的所有属性。在此过程中我们将说明修改这些属性的各个函数(更改所有者、更改权限等),还将更详细的查看UNIX文件系统的结构以及符号链接。
二、stat、fstat和lstat函数
#include<sys/stat.h>
int stat(const char *restrict pathname,struct stat *restrict buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *restrict pathname,struct stat *restrict buf);
三个函数的返回值:若成功则返回0,若出错则返回-1
#include <stdio.h> //for printf
#include <stdlib.h> //for exit
#include <sys/stat.h> //for S_ISREG()/S_ISDIR() and so on
int main(int argc,char *argv[])
{
char *ptr;
struct stat buf;
int i;
for(i=1; i<argc; i++)
{
printf("argv[%d]:%s: ",i,argv[i]);
if(lstat(argv[i],&buf) < 0)
{
perror("lstat error");
continue;
}
if(S_ISREG(buf.st_mode))
ptr = "regular";
else if(S_ISDIR(buf.st_mode))
ptr = "directory";
else if(S_ISCHR(buf.st_mode))
ptr = "character special";
else if(S_ISBLK(buf.st_mode))
ptr = "block special";
else if(S_ISFIFO(buf.st_mode))
ptr = "fifo";
else if(S_ISLNK(buf.st_mode))
ptr = "symbolic link";
else if(S_ISSOCK(buf.st_mode))
ptr = "socket";
else
ptr = "***unknown mode***";
printf("%s\n",ptr);
}
exit(0);
}
#include<unistd.h>
int access(const char *pathname, int mode);
返回值:若成功则返回0,瑞出错则返回-1;
其中,mode是下表所列常量的按位或。
如下测试程序显示了access函数的使用方法。
#include <stdio.h> //for printf
#include <stdlib.h> //for exit
#include <unistd.h> //for access
#include <fcntl.h> //for open
int main(int argc,char *argv[])
{
if(argc != 2)
{
printf("usage:./a.out ");
exit(1);
}
if(access(argv[1],R_OK) < 0)
{
printf("access error for %s\n",argv[1]);
exit(1);
}
else
{
printf("read access OK\n");
}
if(open(argv[1],O_RDONLY) < 0)
{
printf("open error for %s\n",argv[1]);
exit(1);
}
else
{
printf("open for reading OK\n");
}
exit(0);
}
设置用户ID程序可以确定实际用户不能读某个指定文件,而open函数却能打开该文件。
八、umask函数
umask函数为进程设置文件模式创建屏蔽字,并返回以前的值。
#include<sys/stat.h>
mode_t umask(mode_t cmask);
返回值:以前的文件模式创建屏蔽字,该函数是没有出错返回的函数。
#include "apue.h"
#include<fcntl.h>
#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IROTH)
int main(void)
{
mode_t mask=umask(0);
if(creat("foo",RWRWRW)<0)
err_sys("create error for foo");
umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(creat("bar",RWRWRW)<0)
err_sys("creat error for bar");
exit(0);
}
执行结构为:
Single UNIX Specification要求shell支持符号形式的umask命令。与八进制格式不同,符号格式指定许可的权限而非拒绝的权限。下面比较了两种格式的命令。
九、chmod、fchmod函数
这两个函数使我们可以更改现有文件的访问权限
#include<sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int filedes, mode_t mode);
返回值:若成功则返回0,若出错则返回-1。
如下程序修改foo文件和bar文件模式
#include "apue.h"
int
main(void)
{
struct stat statbuf;
/* turn on set-group-ID and turn off group-execute */
if(stat("foo", &statbuf) < 0)
err_sys("stat error for foo");
if(chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("chmod error for foo");
/* set absolute mode to "rw-r--r--" */
if(chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
err_sys("chmod error for bar");
exit(0);
}
chmod函数更新的只是i节点最近一次被更改的时间,系统默认,ls -l列出的是最后文件内容的时间。
十、粘住位
下面几个chown函数可用于更改问价 的用户ID和组ID
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char *pathname , uid_t owner, gid_t group);
三个函数的返回值:若成功则返回0,若出错则返回-1
空洞是由所设置的偏移量超过文件尾端,并写了某些数据后造成的。对于没有写过的字节位置,read函数读到的字节是0.
如果使用实用程序(例如cat(1))复制这种文件,那么所有这些空洞都会被填满,其中所有实际数据字节皆填写为0.
有时我们需要在文件尾端截去一些数据为了缩短文件。将一个文件清空为0是一个特例,在打开文件时使用O_TRUNC标志可以做到这一点。
#include <unistd.h>
int truncate(const char* pathname, off_t length);
int ftruncate(int filedes, off_t length);
返回值:若成功返回0,失败则返回-1
这两个函数将把现有的文件长度截短为length字节。如果该文件以前的长度大于length,则超过length以外的数据将不能再访问。如果以前的长度短于length,则其效果与系统有关。若UNIX系统实现扩展了该文件,则在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。
十四、文件系统
目前,正在使用的UNIX文件系统有多种实现。例如,Solaris支持多种不同类型的磁盘文件系统:传统的基于BSD的UNIX文件系统(UFS,UNIX file system),读、写DOS格式化软盘的文件系统(称为PCFS),以及读CD的文件系统(HSFS)。
我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。(如下图:)
#include<unistd.h>
int link(const char *existingpath, const char *newpath);
返回值:若成功返回0,若出错返回-1
#include <unistd.h>
int unlink(const char *pathname);
返回值:若成功返回0,若出错返回-1
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
if (open("tmpfile",O_RDWR) < 0)
{
printf("open error\n");
}
if (unlink("tmpfile") < 0)
{
printf("unlink error\n");
}
sleep(15);
printf("done\n");
return 0;
}
运行该程序,其结果是
#include <stdio.h>
int remove(const char* pathname);
返回值:若成功则返回0,若出错则返回-1;
文件或者目录用rename函数更名。
#include<stdio.h>
int rename(const char *oldname, const char *newname);
返回值:若成功则返回0,若出错则返回-1;
引入符号链接可能在文件系统中引入循环。大多数查找路径名的函数在这种情况发生时都将返回值为ELOOP的errno。考虑下列命令序列:
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
返回值:若成功怎返回0,若出错则返回-1;
#include<unistd.h>
ssize_t readlink(const char* restrict pathname, char *restrict buf,size_t bufsize);
返回值:若成功则返回读到的字节数,若出错则返回-1;
此函数结合了open、read和close的所有操作。如果此函数成功执行,则他返回读入buf的字节数。在buf中返回的符号链接的内容不以null字符终止。
#include <sys/stat.h>
int futimes(int fd, const timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
返回值:若成功返回0,若出错返回-1
{
__time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds. */
};
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
函数返回值:若成功,返回0,若出错返回-1;
#include <fcntl.h>
#include <stdio.h>
#include <utime.h>
#include <sys/stat.h>
int main(int argc, char **argv)
{
int i,fd;
struct stat statbuf;
struct timespec times[2];
for (i = 1; i < argc; i++)
{
if (stat(argv[i], &statbuf) < 0) { /*得到当前时间*/
printf("%s:stat error", argv[i]);
continue;
}
if (fd = open(argv[i], O_RDWR | O_TRUNC) < 0) { /*截断处理*/
printf("%s:open error",argv[i]);
continue;
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
if (futimens(fd, times) < 0) { /*重置时间*/
printf("%s: futimens error",argv[i]);
}
close(fd);
}
exit(0);
}
#include<sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
返回值:如成功返回0,失败返回-1;
#include<unistd.h>
int rmdir(const char *pathname);
返回值:若成功,返回0,如出错,返回-1;
如果调用此函数使目录的链接计数称为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。如果在链接计数到达0时,有一个或者多个进程打开此目录,则在此函数返回前删除最后一个链接以及.和..项。另外,在此目录中不能在创建新文件。但是在最后一个进程关闭它之前并不释放次目录。(即使另一些进程打开该目录,他们在此目录下也不能执行其他操作。这样处理的原因是,为了使rmdir函数成功执行,该目录必须是空的。)
#include<dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
两个函数返回值:若成功返回指针,若出错返回NULL
struct dirent *readdir(DIR *dp);
返回值:若成功,返回指针;若在目录尾或者出错,返回NULL
void rewinddir(DIR *dp);
int closedir(DIR *dp);
返回值:若成功,返回0,若出错,返回-1
long telldir(DIR *dp);
返回值:与dp关联的目录中的当前位置
void seekdir(DIR *dp, long loc);
#include "apue.h"
#include "pathalloc.h"
#include <dirent.h>
#include <limits.h>
// function type that is called for each filename
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int main(int argc, char *argv[])
{
int ret;
if (argc != 2) {
err_quit("usage: ftw <starting-pathname>");
}
ret = myftw(argv[1], myfunc); // does it all
ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
if (ntot == 0) {
ntot = 1; // avoid divide by 0; print 0 for all counts
}
printf("regular files = %7ld, %5.2f %%\n", nreg, nreg * 100.0 / ntot);
printf("directories = %7ld, %5.2f %%\n", ndir, ndir * 100.0 / ntot);
printf("block special = %7ld, %5.2f %%\n", nblk, nblk * 100.0 / ntot);
printf("char special = %7ld, %5.2f %%\n", nchr, nchr * 100.0 / ntot);
printf("FIFLs = %7ld, %5.2f %%\n", nfifo, nfifo * 100.0 / ntot);
printf("symbolic links = %7ld, %5.2f %%\n", nslink, nslink * 100.0 / ntot);
printf("sockets = %7ld, %5.2f %%\n", nsock, nsock * 100.0 / ntot);
exit(ret);
}
/*
* Descend through the hierarchy starting at "pathname".
* The caller's func() is called for every file.
* */
#define FTW_F 1 // file other than directory
#define FTW_D 2 // directory
#define FTW_DNR 3 // directory thar can't be read
#define FTW_NS 4 // file that we can't stat
static char *fullpath; // contains full pathname for every file
static size_t pathlen;
static int myftw(char *pathname, Myfunc *func) // we return whatever func() returns
{
fullpath = path_alloc(&pathlen); // malloc PATH_MAX+1 bytes
if (pathlen <= strlen(pathname)) {
pathlen = strlen(pathname) * 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL) {
err_sys("realloc failed");
}
}
strcpy(fullpath, pathname);
return (dopath(func));
}
/*
* Descend through the hierarchy, starting at "fullpath".
* If "fullpath" is anything other than a directory, we lstat() it
* call func(), and return. For a directory, we call ourself
* recursively for each name in the directory.
* */
static int dopath(Myfunc *func) // we return whatever func() returns
{
struct stat statbuf;
struct dirent *dirp;
DIR * dp;
int ret, n;
if (lstat(fullpath, &statbuf) < 0) { // stat error
return (func(fullpath, &statbuf, FTW_NS));
}
if (S_ISDIR(statbuf.st_mode) == 0) { // not a directory
return (func(fullpath, &statbuf, FTW_F));
}
/*
* It's a directory. First call func() for the directory,
* then process each filename in the directory.
* */
if ((ret = func(fullpath, &statbuf, FTW_D)) != 0) {
return ret;
}
n = strlen(fullpath);
if (n + NAME_MAX +2 > pathlen) { // expand path buffer
pathlen *= 2;
if ((fullpath = realloc(fullpath, pathlen)) == NULL) {
err_sys("realloc failed");
}
}
fullpath[n++] = '/';
fullpath[n] = 0;
if ((dp = opendir(fullpath)) == NULL) { // can't read directory
return func(fullpath, &statbuf, FTW_DNR);
}
while ((dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0) {
continue; // ignore dot and dot-dot
}
strcpy(&fullpath[n], dirp->d_name); // append name agter "/"
if ((ret = dopath(func)) != 0) { // recursive
break; // time to leave
}
}
fullpath[n - 1] = 0; // erase everything from slash onward
if (closedir(dp) < 0) {
err_ret("can't close directory %s", fullpath);
}
return ret;
}
static int myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch (type) {
case FTW_F:
switch (statptr->st_mode & S_IFMT) {
case S_IFREG:
++nreg;
break;
case S_IFBLK:
++nblk;
break;
case S_IFCHR:
++nchr;
break;
case S_IFIFO:
++nfifo;
break;
case S_IFLNK:
++nslink;
break;
case S_IFSOCK:
++nsock;
break;
case S_IFDIR: // directories should have type = FTW_D
err_dump("for S_IFDIR for %s", pathname);
}
break;
case FTW_D:
++ndir;
break;
case FTW_DNR:
err_ret("can't read directory %s", pathname);
break;
case FTW_NS:
err_ret("stat error for %s", pathname);
break;
default:
err_dump("unknow type %d for pathname %s", type, pathname);
}
return 0;
}
二二、 函数chdir、fchdir和getcwd
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
两个函数返回值:若成功,返回0;若出错,返回-1;
因为当前工作目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程,则下例得不到我们可能希望的结果。
#include <stdio.h>
#include <unistd.h>
int main(void)
{
if (chdir("/tmp") < 0)
{
printf("chdir failed!\n");
}
printf("chdir to /tmp successed\n");
return 0;
}
#include <unistd.h>
char *getcwd(char *buf, size_t size);
返回值:若成功,返回buf,若出错,返回NULL
#include <unistd.h>
#include <stdio.h>
int main(void)
{
char buffer[1024];
size_t size;
if (chdir("/usr/spool/uucpppublic") < 0)
{
printf("chdir failed\n");
}
if (getcwd(buffer, sizeof(buffer) == NULL)
{
printf("getcwd failed\n");
}
printf("cwd is: %s\n", buffer);
exit(0);
}
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/sysmacros.h>
int main(int argc, char *argv[])
{
int i;
struct stat buf;
for(i=1; i<argc; i++)
{
printf("%s: ", argv[i]);
if(stat(argv[i], &buf) < 0)
{
err_ret("stat error");
continue;
}
printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev));
if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode))
{
printf(" (%s) rdev = %d/%d",
(S_ISCHR(buf.st_mode)) ? "character" : "block",
major(buf.st_rdev), minor(buf.st_rdev));
}
printf("\n");
}
exit(0);
}
运行该程序,如下: