《Unix/Linux编程实践教程》笔记(2)
Table of Contents
内容
“一切皆文件”,目录也是文件,opendir() 打开目录,readdir()从目录中读取内容,closedir()关闭目录。stat()函数可以得到文件的属性,在将st_mode(文件类型和许可权限)显示为rwx这种形式时,使用了掩码屏蔽不需要的字段,得到需要的字段。st_mode中有三个特殊的位,分别是执行时设置用户id,执行时设置组id,对于目录受限制删除标志。最后展示了几个相关的系统函数,chmod(),chown(),utime(),rename()。getpwnam(username)可以将username转换成uid,getpwuid(uid)可以将uid转换成username。
习题
3.1
man 3 readdir 可得到答案,大意是因为posix定义d_name为 char d_name[]数组。
3.3
usermode,useradd都无法使用户的ID相同,可以先新建两个用户,再修改etc/passwd 和/etc/group中的内容。实践证明,系统是以ID判别用户的,相同ID即为相同用户。
3.8
对于目录来说,执行权限就是切换和搜索目录的权限。关掉目录执行权限的话,可以查看目录里的内容,但是无法切换到此目录和搜索目录内容。
3.9
login源码在util-linux包中,修改终端设备文件所有者的代码是chown_tty。注销时,终端设备文件所有者改回成root的程序暂时不知道。
3.10 ls分列输出
man 4 tty_ioctl可得到获取终端大小的信息。如下代码实现了ls的分列输出。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <dirent.h> #include <termios.h> #include <sys/ioctl.h> #include <unistd.h> #include <string.h> char filename[128][128];//存储文件名 int lenoffilename[128];//存储文件名长度 int max[128];//存储每列文件名的最大长度 int getcol( int tmpnumfile ,int tmpwscol ) { int col = 0; //最终输出的列数 int sum = 0; //存储文件明长度之合 int k = 0; int i = 0; //得到初始的列数 for (i = 0; i < tmpnumfile; i++) { max[i] = lenoffilename[i]; sum += lenoffilename[i]+2; if (sum >= tmpwscol) { col = i; max[i] = 0; break; } } //所有文件不足一行,返回col if (i >= tmpnumfile) { col = i; return col; } memset(max, 0, 128*sizeof(int)); //数组置0 for (i = 0; i < col; i++) { //取得这一列的最大文件长度,存储到max数组 for (int j = 0; ; j++) { if ( (i+j*col) <= tmpnumfile ) { if (lenoffilename[i+j*col] > max[i]) max[i] = lenoffilename[i+j*col]; } else break; } /*判断每列最大文件名长度之和是否超出终端宽度 *如果是,则col减1, i设为-1(for循环i++,i就变成0),max数组置0, 跳出for k循环 *如果否,不做处理,继续 */ sum = 0; for (k = 0; k < col; k++) { sum += max[k]+2; if (sum >=tmpwscol) { col--; i = -1; memset(max, 0, 128*sizeof(int)); break; } } } return col; } /* 得到文件名,存储到filename数组 * 得到文件名长度,存储到lenoffilename数组 * 返回文件数目 */ int getfilename(char *dirname ) { DIR *dir_ptr; /* the directory */ struct dirent *direntp; /* each entry */ int numfile = 0; if ( ( dir_ptr = opendir( dirname ) ) == NULL ) fprintf(stderr,"ls1: cannot open %s\n", dirname); else { while ( ( direntp = readdir( dir_ptr ) ) != NULL ) { //printf("%s\n", direntp->d_name ); strcpy(filename[numfile], direntp->d_name); lenoffilename[numfile++] = strlen(direntp->d_name); } closedir(dir_ptr); } return numfile; } int main(int ac, char *av[]) { struct winsize size; int tmpnumfile, col; if (isatty(STDOUT_FILENO) == 0) exit; //得到终端的size if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) { perror("ioctl TIOCGWINSZ error"); exit(1); } if ( ac == 1 ) { tmpnumfile = getfilename( "." ); col = getcol( tmpnumfile, size.ws_col); do_ls(col, tmpnumfile); } else while ( --ac ) { printf("%s:\n", *++av ); tmpnumfile = getfilename( *av ); col = getcol( tmpnumfile, size.ws_col ); do_ls(col, tmpnumfile); } return 0; } void do_ls( int col, int tmpnumfile) { for (int i = 0; i < tmpnumfile; i++) { printf ("%s ", filename[i]); //输出空格补全 for( int j = 0; j < (max[i%col]-lenoffilename[i]); j++) printf (" "); if ( (i+1)%col == 0) printf ("\n"); } printf ("\n"); return; }
3.11 使ls接收目录参数
do_ls中传给dostat()的只是一个文件名,没有将路径一并传进去。为了让ls2能接收其他目录的作为参数,所以修改了do_ls函数,拼接好一个完整的文件路径,传给dostat函数。do_ls函数代码如下:
do_ls( char dirname[] ) /* * list files in directory called dirname */ { DIR *dir_ptr; /* the directory */ struct dirent *direntp; /* each entry */ char *tmpdirname; int len = strlen(dirname); char *sep = "/"; if ( ( dir_ptr = opendir( dirname ) ) == NULL ) fprintf(stderr,"ls2: cannot open %s\n", dirname); else { while ( ( direntp = readdir( dir_ptr ) ) != NULL ) { tmpdirname = (char *)malloc(strlen(direntp->d_name)+len+2); memset(tmpdirname, 0, strlen(direntp->d_name)+len+2); strcat (tmpdirname, dirname); if (tmpdirname[len-1] != '/') strcat(tmpdirname, sep); strcat(tmpdirname, direntp->d_name); dostat(tmpdirname); free(tmpdirname); tmpdirname = NULL; } closedir(dir_ptr); } }
3.12 ls显示特殊权限
man 2 stat可以得到特殊权限的掩码,输出显示时还需要注意,如果文件或者目录没有对应的x权限,对应位置上的s或者t应是大写的S或者T。实现代码如下:
permbits( permval, specpos, bit0, string ) char *string; char bit0; /* * convert bits in permval into chars rw and x */ { //printf ("bit0 is%c\n", bit0); if ( permval & 4 ) //这里是xxx & 0000000100 string[0] = 'r'; if ( permval & 2 ) string[1] = 'w'; if ( permval & 1 ) { if (specpos & 1) { if (bit0 == 'd') string[2] = 't'; else string[2] = 's'; } string[2] = 'x'; } else { if (specpos & 1) { if (bit0 == 'd') string[2] = 'T'; else string[2] = 'S'; } } }
3.13 使cp支持第二个参数是目录
代码如下:
main(int ac, char *av[]) { int in_fd, out_fd, n_chars, len; char buf[BUFFERSIZE]; struct stat info; char *pathtofile; char *sep = "/"; /* check args */ if ( ac != 3 ){ fprintf( stderr, "usage: %s source destination\n", *av); exit(1); } /* open files */ if ( (in_fd=open(av[1], O_RDONLY)) == -1 ) oops("Cannot open ", av[1]); //加判断,判断第二个参数是否是目录 if (stat(av[2], &info) == -1) perror( av[2]); if ( (info.st_mode & S_IFMT) == S_IFDIR ) { len = strlen(av[2]); pathtofile = (char *)malloc(strlen(av[1])+len+2); memset(pathtofile, 0, strlen(av[1]+len+2)); strcat(pathtofile, av[2]); if (pathtofile[len-1] != '/') strcat(pathtofile, sep); strcat(pathtofile, av[1]); if ( (out_fd=creat( pathtofile, COPYMODE)) == -1 ) oops( "Cannot creat", av[2]); free(pathtofile); pathtofile = NULL; } else { if ( (out_fd=creat( av[2], COPYMODE)) == -1 ) oops( "Cannot creat", av[2]); } /* copy files */ while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 ) if ( write( out_fd, buf, n_chars ) != n_chars ) oops("Write error to ", av[2]); if ( n_chars == -1 ) oops("Read error from ", av[1]); /* close files */ if ( close(in_fd) == -1 || close(out_fd) == -1 ) oops("Error closing files",""); }
3.14 使cp支持第一个参数使目录
修改cp1.c,使得可以将第一个目录下的所有文件拷贝到第二个目录下
include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> #include <stdlib.h> #include <dirent.h> #define BUFFERSIZE 4096 #define COPYMODE 0644 void oops(char *, char *); void cpfile(const char *pf, const char *todir); char *combpath(const char *dir, const char *pf); char *sep = "/"; char buf[BUFFERSIZE]; main(int ac, char *av[]) { int in_fd, out_fd, n_chars, len; char *pathtofile; struct stat info1, info2; DIR *dir_ptr; struct dirent *direntp; /* check args */ if ( ac != 3 ){ fprintf( stderr, "usage: %s source destination\n", *av); exit(1); } /* open files */ /* if ( (in_fd=open(av[1], O_RDONLY)) == -1 ) oops("Cannot open ", av[1]); */ //加判断,判断第二个参数是否是目录 if (stat(av[2], &info2) == -1) perror( av[2]); if ( (info2.st_mode & S_IFMT) == S_IFDIR ) { //如果第一个参数是目录,将这个目录下所有文件拷贝到第二个参数的目录下 if (stat(av[1], &info1) == -1) perror( av[1]); if ((info1.st_mode & S_IFMT) == S_IFDIR ) { //循环读取av[1]目录下文件,写到新的目录下 if (( dir_ptr = opendir( av[1] )) == NULL) fprintf (stderr, "opendir: cannot open %s\n", av[1]); char *tmppathtofile = NULL; while ( ( direntp = readdir( dir_ptr )) != NULL) { printf (" in %s is %s\n", av[1], direntp->d_name); if (strcmp(direntp->d_name, ".") == 0 || strcmp(direntp->d_name, "..") == 0) continue; pathtofile = combpath(av[1], direntp->d_name); tmppathtofile = combpath(av[2], direntp->d_name); cpfile(pathtofile, tmppathtofile); free(pathtofile); free(tmppathtofile); pathtofile = NULL; tmppathtofile = NULL; } return; } else { //如果av[1]是文件,av[2]是目录,拼接完整文件路径 pathtofile = combpath(av[2], av[1]); } } else { //如果av[2]是文件 cpfile(av[1], av[2]); return; } //av[1]是文件,av[2]是目录,将av[1]拷贝到av[2]目录下 cpfile(av[1], pathtofile); free(pathtofile); } void oops(char *s1, char *s2) { fprintf(stderr,"Error: %s ", s1); perror(s2); exit(1); } void cpfile(const char *inpf, const char *outpf) { int out_fd, in_fd, n_chars; if ( (out_fd=creat( outpf, COPYMODE)) == -1 ) oops( "Cannot creat", outpf); if ( (in_fd=open( inpf, COPYMODE)) == -1 ) oops( "Cannot open", inpf); //printf ("%s \n", inpf); while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 ) if ( write( out_fd, buf, n_chars ) != n_chars ) oops("Write error to ", outpf); if ( n_chars == -1 ) oops("Read error from ", inpf); if ( close(in_fd) == -1 || close(out_fd) == -1 ) oops("Error closing files",""); } char * combpath(const char *dir, const char *pf) { int len; len = strlen(dir); char *pathtofile = (char *)malloc(strlen(pf)+len+2); memset(pathtofile, 0, strlen(pf)+len+2); strcat(pathtofile, dir); if (pathtofile[len-1] != '/') strcat(pathtofile, sep); strcat(pathtofile, pf); return pathtofile; }
3.15 ls根据文件名排序输出
把文件名储存在数组里,再用快排排序。
#include <stdio.h> #include <sys/types.h> #include <dirent.h> #include <string.h> #include <stdlib.h> char dirlist[128][128]; int cmp(const void *a, const void *b); main(int ac, char *av[]) { if ( ac == 1 ) do_ls( "." ); else while ( --ac ){ printf("%s:\n", *++av ); do_ls( *av ); } } do_ls( char *dirname ) { DIR *dir_ptr; /* the directory */ struct dirent *direntp; /* each entry */ if ( ( dir_ptr = opendir( dirname ) ) == NULL ) fprintf(stderr,"ls1: cannot open %s\n", dirname); else { int i = 0; while ( ( direntp = readdir( dir_ptr ) ) != NULL ) { strcpy(dirlist[i++], direntp->d_name); } printf ("sizeof () is %d\n", sizeof(dirlist[0])); qsort(dirlist, i, sizeof(dirlist[0]), cmp); for (int j = 0; j<i; j++) printf ("%s\n", dirlist[j]); closedir(dir_ptr); } } int cmp(const void *ap, const void *bp) { return strcmp((char *)ap, (char *)bp); }
3.17 权限检查在open进行
用vim打开文件后,将文件的读权限去掉,依然可以正常读写文件。用open函数打开文件,读取一个字节并输出,然后sleep10秒,再读取一个字节输出,在这10秒间把文件的读权限去掉。发现依然能正常读取。 这说明在从文件描述符read的时候没有再去检察文件权限。
3.18 使ls支持递归
让ls1.c支持递规列出文件。观察系统ls命令,是根据目录一层一层的列出文件,所以用数组做了个栈,列完一层后,再去递归列出其中的目录。
#include <stdio.h> #include <sys/types.h> #include <dirent.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> char * combpath(const char *dir, const char *pf); int main(int ac, char *av[]) { if ( ac == 1 ) do_ls( "." ); else while ( --ac ){ printf("%s:\n", *++av ); do_ls( *av ); } return 0; } do_ls( char *dirname ) /* * list files in directory called dirname */ { DIR *dir_ptr; /* the directory */ struct stat info; struct dirent *direntp; /* each entry */ char tmpchar; char *pathtofile; char *stack[128]; //存储目录路径的栈 int num=0;//栈中的元素个数 if (stat(dirname, &info) == -1) perror(dirname); if ((info.st_mode & S_IFMT) == S_IFDIR) { printf("%s:\n", dirname ); if ( ( dir_ptr = opendir( dirname ) ) == NULL ) fprintf(stderr,"ls1: cannot open %s\n", dirname); else { while ( ( direntp = readdir( dir_ptr ) ) != NULL ) { //将目录名和文件名组成完整路径 pathtofile=combpath(dirname, direntp->d_name); tmpchar = direntp->d_name[0]; //忽略隐藏文件 if ( tmpchar == '.' ) continue; if (stat(pathtofile, &info) == -1) perror(pathtofile); //如果是目录,压栈 if ((info.st_mode & S_IFMT) == S_IFDIR) stack[num++] = pathtofile; printf("%s ", direntp->d_name ); } printf ("\n"); //处理栈中的目录 if (num-- >0) do_ls(stack[num]); closedir(dir_ptr); } } } char * combpath(const char *dir, const char *pf) { int len; len = strlen(dir); char *sep = "/"; char *pathtofile = (char *)malloc(strlen(pf)+len+2); memset(pathtofile, 0, strlen(pf)+len+2); strcat(pathtofile, dir); if (pathtofile[len-1] != '/') strcat(pathtofile, sep); strcat(pathtofile, pf); return pathtofile; }
3.20 用户名和用户id互相转换
getpwnam(username)可以将用户名转换成用户id,getpwuid(uid)可以将用户id转换成用户名,具体用法可以在man手册中查到。chown(path, owner, group)可以修改文件所有者和组。
3.21 utime修改文件最后修改时间和访问时间
系统调用utime可以修改文件最后修改时间和最后访问时间。代码如下:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <utime.h> #define BUFFERSIZE 4096 #define COPYMODE 0644 void oops(char *, char *); main(int ac, char *av[]) { int in_fd, out_fd, n_chars; char buf[BUFFERSIZE]; time_t nowtime; struct stat info; struct utimbuf timebuf; /* check args */ if ( ac != 3 ){ fprintf( stderr, "usage: %s source destination\n", *av); exit(1); } /* open files */ if ( (in_fd=open(av[1], O_RDONLY)) == -1 ) oops("Cannot open ", av[1]); if ( (out_fd=creat( av[2], COPYMODE)) == -1 ) oops( "Cannot creat", av[2]); /* copy files */ stat(av[1], &info); while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 ) if ( write( out_fd, buf, n_chars ) != n_chars ) oops("Write error to ", av[2]); if ( n_chars == -1 ) oops("Read error from ", av[1]); if ( close(in_fd) == -1 || close(out_fd) == -1 ) oops("Error closing files",""); timebuf.actime = info.st_atime; timebuf.modtime = info.st_mtime; utime(av[2], &timebuf); } void oops(char *s1, char *s2) { fprintf(stderr,"Error: %s ", s1); perror(s2); exit(1); }
项目
file命令
man file man magic 可以知道file命令的基本原理。
file命令会对文件进行filesystem tests,magic tests,and language tests,filesystem tests依靠stat()得到文件类型(比如 链接,sockets),magic tests按照系统上的magic file 去匹配可执行文件开头得到一个magic number,如果不是可执行文件,会测试它是否是文本文件,编码是什么。如果是文本类型,会进行language tests,去“猜测”文件内容的语言(比如C源码文件)。
结束
通过这一章的学习,知道了目录组成结构(目录也是文件,目录可以看成文件的列表),知道了如何获取文件详细信息和更改文件属性。