《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源码文件)。
结束
通过这一章的学习,知道了目录组成结构(目录也是文件,目录可以看成文件的列表),知道了如何获取文件详细信息和更改文件属性。