Unix环境高级程序设计入门
----文件系统的相关编程(下)
在上文中,我们已经熟悉了文件描述符的相关知识,这里再介绍一下文件描述符的拷贝。Unix系统除了允许属于两个独立进程的不同文件描述符指向同一文件表项外,还允许同一进程的不同文件描述符指向同一个文件表项。这一特点被称为文件描述符的拷贝,可以通过使用dup函数或dup2函数来实现。它们的定义是:
#include <unistd.h>
int dup(int fd); //成功时返回新的文件描述符,失败则返回-1
int dup2(int fd1, int fd2); //成功时返回新的文件描述符,失败则返回-1
由dup函数返回的文件描述符一定是当前可用文件描述符中的最小数值,而dup2函数则可以利用参数fd2指定欲返回的文件描述符的数值。如果fd2已经打开,则先将其关闭;但如果fd2等于fd1,则dup2返回fd2,而不关闭它。使用dup函数返回的新文件描述符与参数fd共享同一个文件表项,而dup2函数返回的新文件描述符与fd1共享同一个文件表项。
[程序5]
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <string>
using namespace std;
int main()
{
int fd1, fd2;
string msg = "please enter a string: ";
write(1,msg.c_str(),msg.size()); //把msg的内容输出到屏幕上,c_str()函数用于string类型的转换
fd1 = dup(0); //fd1对应于cin
char* buf = new char[250];
memset(buf,0x00,250);
read(fd1,buf,250);
fd2 = dup2(1,7); //fd2=7,对应于cout
write(fd2,buf,strlen(buf));
delete [] buf;
return 0;
}
当此程序开始执行时,假定下一个可用的描述符是3,则fd1=3,且与文件描述符0共享同一文件表项。程序将提示用户输入一字符串,当从键盘输入一串字符后会在屏幕上打印出来。有关于描述符更多的应用,将在后续有关文章中进行介绍。
下面再来介绍一个新知识点,那就是如何在程序中获得文件的属性。这里需要用到三个函数,它们的定义是:
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char* pathname, struct stat* buf); //成功则返回0,失败返回-1
int fstat(int fd, struct stat* buf); //成功则返回0,失败返回-1
int lstat(const char* pathname, struct stat* buf); //成功则返回0,失败返回-1
stat()函数接收一个文件名,返回该文件的stat结构。fstat()接收一个已打开的文件描述符,返回该文件的stat结构。lstat()与stat()类似,但对于符号链接,它返回该符号链接的stat结构,而不是该符号链接所引用文件的stat结构。stat结构是一包含了文件所有属性信息的结构,它的定义如下:
struct stat{
mode_t st_mode; //file type & mode
ino_t st_ino; //i-node number
dev_t st_dev; //device number
dev_t st_rdev; //device number for special files
nlink_t st_nlink; //number of links
uid_t st_uid; //user ID of owner
gid_t st_gid; //group ID of owner
off_t st_size; //size in bytes,for regular files
time_t st_atime; //time of last access
time_t st_mtime; //time of last modification
time_t st_ctime; //time of last file status change
long st_blksize;//best I/O block size
long st_blocks; //number of 512 bytes blocks allocated
};
stat结构中最常用到的属性是st_mode(文件的类型及文件的访问权限)、st_nlink(硬链接数,表示有几个链接到该文件上)、st_uid、st_gid、st_size(以字节为单位的文件长度,只对普通文件、目录文件和符号连接有意义)、st_atime、st_mtime、st_ctime。
我们曾一再提到Unix系统中一切皆可视为文件,不过细而化之的话又可以分为多种类型,那么在程序中如何去对文件类型进行判别呢?这就需要用到下表中所示的一些宏:
宏 |
作用 |
S_ISREG() |
测试是否为普通文件 |
S_ISDIR() |
测试是否为目录文件 |
S_ISCHR() |
测试是否为字符特殊文件 |
S_ISBLK() |
测试是否为块特殊文件 |
S_ISFIFO() |
测试是否为FIFO文件 |
S_ISLNK() |
测试是否为链接文件 |
S_ISSOCK() |
测试是否为socket文件 |
S_ISUID() |
测试是否设置了“设置-用户-ID”位 |
S_ISGID() |
测试是否设置了“设置-组-ID”位 |
[程序6]
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int i;
struct stat buf;
char *ptr;
for(i=1; i< argc; i++)
{
printf("%s: ", argv[i]);
if(lstat(argv[i],&buf)<0)
{
printf("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 = "char special";
else if(S_ISBLK(buf.st_mode)) ptr = "block special";
else if(S_ISFIFO(buf.st_mode)) ptr = "fifo";
else ptr = "Others";
printf(" %s\n",ptr );
}
return 0;
}
编译程序后,我们先来使用命令mkfifo myfifo.pipe建一个管道文件,然后运行:
filetype myfifo.pipe . /etc/passwd
屏幕会显示:
myfifo.pipe : fifo
. : directory
/etc/passwd : regular
此外,st_mode属性还包含了可用于对文件的属主及权限进行判断的宏,如下表所示:
宏 |
意义 |
S_IRUSR |
所有者-读 |
S_IWUSR |
所有者-写 |
S_IXUSR |
所有者-执行 |
S_IRGRP |
组-读 |
S_IWGRP |
组-写 |
S_IXGRP |
组-执行 |
S_IROTH |
其他-读 |
S_IWOTH |
其他-写 |
S_IXOTH |
其他-执行 |
[程序7]
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
int main()
{
cout << " enter a file name: ";
char name[255];
cin >> name;
struct stat buf;
if(lstat(name,&buf)<0)
{
cout << " file not existing. " << endl;
return -1;
}
if(buf.st_mode & S_IRUSR) cout << " user can read|";
if(buf.st_mode & S_IWUSR) cout << " user can write|";
if(buf.st_mode & S_IXUSR) cout << " user can execute|";
if(buf.st_mode & S_IRGRP) cout << " group can read|";
if(buf.st_mode & S_IWGRP) cout << " group can write|";
if(buf.st_mode & S_IXGRP) cout << " group can execute|";
if(buf.st_mode & S_IROTH) cout << " other can read|";
if(buf.st_mode & S_IWOTH) cout << " other can write|";
if(buf.st_mode & S_IXOTH) cout << " ohter can execute|";
cout << endl;
return 0;
}
如果是想判断某用户对某一文件的访问权限,则可调用access()函数,它的定义是:
access(const char* path, int amode)
其中,amode对应的值有R_OK(可读)、W_OK(可写)、X_OK(可执行)、F_OK(文件存在)。
三、上下文知识点综合
笔者在参考一些资料的基础上,准备通过编写一个程序模拟“ls -l”命令的功能,来让大家回顾一下上面所介绍的各个知识点。
先来谈一下实现“ls -l”命令的思路:
我们知道使用ls –l命令,可以长列表显示目录的内容,即列出文件的类型、访问权限、拥有者、文件大小、修改时间及名称等详细信息。在程序中,首先使用opendir、readdir函数获得文件及目录名,然后再通过相应的方法来分别获得这些文件或目录的类型、权限、大小等详细信息。
[程序8]
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
//获得文件的类型,返回值可能是(-, d, c, b, p, l, s, ?)
char fileType(const struct stat & st);
//得到权限的表示方式,r表示可读,w表示可写,x表示可执行
void priv(const struct stat & st, char *privs, const int belong[3]);
//获得所有者的访问权限
void ownerPriv(const struct stat & st, char *privs);
//获得组的访问权限
void groupPriv(const struct stat & st, char *privs);
//获得其他人的访问权限
void otherPriv(const struct stat & st, char *privs);
//将默认的权限表示成"---"
void setDefault(char *privs);
//显示文件的名字、最后修改时间等属性
void trival(const struct stat & st, const char * name);
//显示权限
void dispPriv( void (*f)(const struct stat &, char *), const struct stat & st, char * privs);
int main(int argc, char* argv[])
{
DIR *dp;
struct dirent *dirp;
if (argc == 1)
dp = opendir( "." );
else
dp = opendir( argv[1] );
if(dp == NULL){
struct stat st;
if(lstat(argv[1], &st) < 0){
cout << "No such file or directory" << endl;
exit(1);
}else{
char privs[4];
cout << fileType(st);
dispPriv(ownerPriv, st, privs);
dispPriv(groupPriv, st, privs);
dispPriv(otherPriv, st, privs);
trival(st, argv[1]);
exit(0);
}
}
while ( (dirp = readdir(dp)) != NULL) {
struct stat st;
char fn[200];
if (argc == 1) strcpy(fn, dirp->d_name);
else sprintf(fn, "%s//%s", argv[1], dirp->d_name);
if( lstat(fn, &st) < 0) {
cout << "error" << endl;
continue;
}
cout << fileType(st);
char privs[4];
dispPriv(ownerPriv, st, privs);
dispPriv(groupPriv, st, privs);
dispPriv(otherPriv, st, privs);
trival(st, dirp->d_name);
}
closedir(dp); exit(0);
}
void setDefault(char * privs){
memset(privs, 0, sizeof(privs));
strcpy(privs, "---");
}
char fileType(const struct stat & st){
char type = '?';
if S_ISREG(st.st_mode) { type = '-';
}else if S_ISDIR(st.st_mode) { type = 'd';
}else if S_ISCHR(st.st_mode) { type = 'c';
}else if S_ISBLK(st.st_mode) { type = 'b';
}else if S_ISFIFO(st.st_mode) { type = 'p';
}else if S_ISLNK(st.st_mode) { type = 'l';
}else if S_ISSOCK(st.st_mode) { type = 's';
}
return type;
}
void ownerPriv(const struct stat & st, char * privs){
const int OWNER[3] = {S_IRUSR, S_IWUSR, S_IXUSR};
priv(st, privs, OWNER);
}
void groupPriv(const struct stat & st, char * privs){
const int GROUP[3] = {S_IRGRP, S_IWGRP, S_IXGRP};
priv(st, privs, GROUP);
}
void otherPriv(const struct stat & st, char * privs){
const int OTHER[3] = {S_IROTH, S_IWOTH, S_IXOTH};
priv(st, privs, OTHER);
}
void priv(const struct stat & st, char * privs, const int belong[3]){
if (st.st_mode & belong[0]) *privs = 'r';
++privs;
if (st.st_mode & belong[1]) *privs = 'w';
++privs;
if (st.st_mode & belong[2]) *privs = 'x';
}
void dispPriv( void (*f)(const struct stat &, char *), const struct stat & st, char * privs)
{
setDefault(privs);
f(st, privs);
cout << privs;
}
void trival(const struct stat & st, const char * name){
cout.width(8);
cout << " " << st.st_nlink;
struct passwd* pwd;
struct group* grp;
pwd = getpwuid(st.st_uid);
grp = getgrgid(st.st_gid);
cout << " " << pwd->pw_name << "\t" << grp->gr_name;
cout.width(16);
cout << st.st_size;
char * timeStr = ctime(&st.st_mtime);
timeStr[strlen(timeStr) - 1] = 0;
cout << " " << timeStr;
cout << " " << name << endl;
}
Unix环境中的文件系统相关编程知识暂时介绍至此,后有时间再整理其他方面的学习体会,感谢师友们的关注支持与帮助指正。