unix 文件属性

在unix下提到文件属性,不得不提的一个结构就是stat,stat结构一般定义如下:

struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};

每个字段什么意思,注释写的很详细。通过下面的函数可以获取这个结构:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.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.其中需要注意的是lstat函数不跟随符号链接,当path是符号链接时获取符号链接本身的属性而不是符号链接所指的文件属性。下面围绕各个字段展开:

文件类型和访问权限

文件的类型和操作权限在st_mode中给出,文件的类型可以通过下面的宏获取:

S_ISREG(st_mode) //是否是普通文件

S_ISDIR(st_mode) //是否是目录文件

S_ISBLK(st_mode) //是否是块文件

S_ISCHR(st_mode) //是否是字符文件

S_ISSOCK(st_mode) //是否是socket文件

S_ISFIFO(st_mode) //是否是FIFO

S_ISLNK(st_mode)  //是否是符号链接文件

文件访问模式定义如下:

  用户 其他
S_IRUSR S_IRGRP S_IROTH
S_IWUSR S_IWGRP S_IWOTH
执行 S_IXUSR S_IXGRP S_IXOTH
综合 S_RWXU S_RWXO S_RWXO

下面是一个判断文件类型和文件访问模式的小程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> int main(int argc,char* argv[])
{
int ret ;
struct stat buf;
char* ptr = NULL; for(int i=;i<argc;i++)
{
printf("%s:",argv[i]); ret = lstat(argv[i],&buf);
if(ret == -)
{
printf("lstat failed\n");
return -;
} if(S_ISREG(buf.st_mode))
{
ptr = "reguler file"; } if(S_ISDIR(buf.st_mode))
{
ptr = "dir file";
} if(S_ISBLK(buf.st_mode))
{
ptr = "block file";
} if(S_ISCHR(buf.st_mode))
{
ptr = "char file";
} if(S_ISSOCK(buf.st_mode))
{
ptr = "socket file";
} if(S_ISFIFO(buf.st_mode))
{
ptr = "fifo file";
} if(S_ISLNK(buf.st_mode))
{
ptr = "link file";
} printf("%s;privilege:",ptr); if(buf.st_mode & S_IRWXU)
ptr = "user all";
if(buf.st_mode & S_IRUSR)
ptr = "user read";
if(buf.st_mode & S_IWUSR)
ptr = "user write";
if(buf.st_mode & S_IXUSR)
ptr = "user excute";
if(buf.st_mode & S_IRWXG)
ptr = "group all";
if(buf.st_mode & S_IRGRP)
ptr = "group read";
if(buf.st_mode & S_IWGRP)
ptr = "group write";
if(buf.st_mode & S_IXGRP)
ptr = "group excute";
if(buf.st_mode & S_IRWXO)
ptr = "other all";
if(buf.st_mode & S_IROTH)
ptr = "other read";
if(buf.st_mode & S_IWOTH)
ptr = "other write";
if(buf.st_mode & S_IXOTH)
ptr = "other excute";
if(buf.st_mode & S_ISUID)
ptr = "set uid";
if(buf.st_mode & S_ISGID)
ptr = "set gid";
if(buf.st_mode & S_ISVTX)
ptr = "sticky bit on";
printf("%s\n",ptr); }
}

下面是执行结果:

$ ./test /etc/passwd /etc /dev/log  /dev/tty /dev/sr0 /dev/cdrom
/etc/passwd:reguler file;privilege:other read
/etc:dir file;privilege:other excute
/dev/log:socket file;privilege:other write
/dev/tty:char file;privilege:other write
/dev/sr0:block file;privilege:group write
/dev/cdrom:link file;privilege:other excute

调用create函数或者open创建新的文件需要指定的访问模式就是其中的一种或者几种的组合。

文件访问权限检查

每个文件都有一个所有者(stat结构中st_uid表示)以及一个所属组(stat结构中st_gid表示),而每个进程都有下面接个ID:

  • 真实用户ID 真实组ID
  • 有效用户ID 有效组ID 附加组ID
  • 保存的设置用户ID 保存的设置组ID

其中保存的设置用户ID 和保存的设置组ID由 exec函数从有效用户ID和有效组ID拷贝而来.

真实用户ID和真实组ID 表明了我们是谁,由账户文件/etc/password中读取而来.中间的三个ID用来检查进程是否可以访问文件。

一般来说进程的有效用户ID等于实际用户ID,有效组ID等于实际组ID。不过执行设置用户ID位或者设置组ID位的程序就不一样了,这种情况下有效用户ID等于程序文件所有者ID或者有效组ID等于程序文件所有组ID,典型的文件就是passwd文件。

当我们想访问某个目录下的某个文件的时候,我们必须有访问这些目录的权限。读一个目录的权限指可以获取目录下的目录项列表,写目录指能够在目录下建立文件,写文件,删除文件,执行权限指能够进入这个目录。

一般来讲一个进程是否能够访问某个文件,内核会按照以下步骤去检查:

  • 进程的有效ID是否等于文件的所有者ID,如果相等进程具有访问这个文件相应的权限(这里相应的权限指是否能够读写执行)
  • 进程的有效组ID或者附件组ID是否等于文件的所属组ID,如果相等进程具有访问这个文件的相应权限
  • 文件其他访问权限的检查

有时候也需要通过真实用户ID和真实组ID去访问文件,内核的检查顺序同上,只不过将有效用户ID和有效组ID换成了真实用户ID和真实组ID。对于通过真实用户ID和真实组ID访问文件有下面函数:

#include <unistd.h>
int access(const char *pathname, int mode);

成功返回0,失败返回-1.mode取值如下:F_OK,R_OK,W_OK,X_OK,分别是文件存在,文件读,文件写,文件执行。

下面是个说明两种访问方式区别的小程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> int main(int argc,char* argv[])
{
if(argc != )
{
printf("usage:./test path\n");
return -;
} if(access(argv[],R_OK) == -)
{
printf("access error for %s\n",argv[]); }else
{
printf("access OK for %s\n",argv[]);
} if(open(argv[],O_RDONLY) < )
{
printf("open error for %s\n",argv[]);
}else
{ printf("open for read OK for %s\n",argv[]);
} return ;
}

执行结果如下:

hero@powerPC:~/source$ ls -l test
-rwxrwxr-x hero hero 10月 : test
hero@powerPC:~/source$ ./test test
access OK for test
open for read OK for test
hero@powerPC:~/source$ ls -l a
-r-------- root root 10月 : a
hero@powerPC:~/source$ ./test a
access error for a
open error for a
hero@powerPC:~/source$ su
Password:
root@powerPC:/home/hero/source# chown root test
root@powerPC:/home/hero/source# chmod u+s test
root@powerPC:/home/hero/source# exit
exit
hero@powerPC:~/source$ ls -l test
-rwsrwxr-x root hero 10月 : test
hero@powerPC:~/source$ ./test a
access error for a
open for read OK for a
hero@powerPC:~/source$

改变文件权限和所有者

下面说说怎么改变文件的权限和所有者,在Unix下通过下面的函数改变文件的权限:

#include <sys/stat.h>

 int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);

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

改变文件权限的进程的有效用户ID必须等于文件所有者ID或者进程具有超级用户权限,此外如果进程的有效组ID或者附加组ID不等于文件的组ID,文件的设置组ID位自动关闭。在Linux上如果进程没有授权,那么写文件的时候文件的设置用户ID和设置组ID自动关闭。请看下面的小程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#define mode (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
int main(int argc,char* argv[])
{
int errno;
if(argc != )
{
printf("usage:./test %s",argv[]);
return -;
}
if(chmod(argv[],mode) < )
{
if(errno == EPERM)
{
printf("process euid not equal file uid or process not privilege\n");
return -;
}
printf("chmod failed\n");
}
return ;
}

下面是测试结果:

hero@powerPC:~/source$ ls -l test_file*
-rw-r--r-- root root 11月 : test_file1
-rw-rwSr-- hero root 11月 : test_file2
hero@powerPC:~/source$ ./test test_file1
process euid not equal file uid or process not privilege
hero@powerPC:~/source$ ./test test_file2
hero@powerPC:~/source$ ls -l test_file2
-rw-rw-rw- hero root 11月 : test_file2

通过下面的函数改变文件的归宿:

#include <unistd.h>

 int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
return - faild, OK

只有超级用户进程能够改变文件的用户ID,文件的所有者能够将文件组改为自己所属于的任意组。如果owner或者group等于-1,文件的归宿不变。一个可执行文件如果被一个未授权进程修改,那么文件的设置用户ID和设置组ID自动清除。

新建文件的用户ID等于进程的有效ID,新建文件的组ID可能等于创建文件目录的组ID或者等于进程的有效组ID。如果创建文件的目录设置了粘住位,那么新建文件的组ID等于创建文件的目录的组ID。一个设置了粘住位的目录意味着只有对目录具有写权限同时满足下面任何一个条件才可以删除目录下的文件或者改变它们的名称:

拥有文件,拥有目录,拥有超级用户权限

进程可以通过umask函数改变进程新建文件的访问权限:

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
//Allways return OK

注意该函数不改变其他进程的umask值。请看小程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> #define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
#define UMASK (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
int main(int argc,char* argv[])
{
if(argc != )
{
printf("usage ./test %s",argv[]);
return -;
} umask(UMASK); if(creat(argv[],RWRWRW)< )
{
printf("creat file :%s failed\n",argv[]);
return -;
}
return ; }

执行结果如下:

hero@powerPC:~/source$ umask

hero@powerPC:~/source$ ./test tf
hero@powerPC:~/source$ ll
total
drwxrwxr-x hero hero 11月 : ./
drwxr-xr-x hero hero 11月 : ../
-rw-rw-r-- hero hero 11月 : chmod.c
-rwxrwxr-x hero hero 11月 : test*
-rw------- hero hero 11月 : tf
-rw-rw-r-- hero hero 11月 : umask.c
hero@powerPC:~/source$ umask

上面的程序并没有改变设立了的umask值,只影响了调用进程新建文件的权限。

文件长度

在stat结构中st_size表示文件的大小,只有普通文件,目录文件和符号链接文件有大小,普通文件的大小可以是0,但是目录文件和符号链接文件的大小绝不可能是0,因为目录文件至少有两个目录项,符号文件的大小就是它所指的文件的文件路径长度。使用下面函数可以从尾端截断普通文件。

#include <unistd.h>
#include <sys/types.h> int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
//On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

如果原来的文件比length要长,超过的部分将不能访问,如果原来的长度比length短,那么中间就是就是\0,注意文件的offset不会改变。如果文件大小发生改变,文件的mtime和ctime发生变化,设置用户ID和设置组ID可能清除。注意文件必须是可写的。

链接

对于每个文件有多个目录项指向它的i-node,通过下面函数可以建立一个指向已有文件的硬链接。

#include <unistd.h>

int link(const char *oldpath, const char *newpath);

// On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

如果newpath已经存在返回错误,只有最后的分量会被创建,之前的路径必须已经存在。

只有超级用户可以建立指向目录文件的硬链接。

删除一个目录项可以通过unlink函数:

#include <unistd.h>

 int unlink(const char *pathname);

// On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

如果指向该目录项的链接数变为0并且没有进程打开这个文件,那么文件被删除并且释放占有的空间,如果还有进程打开那么直到进程关闭它为止才会释放文件占用的空间,但是此时文件已经看不到了,请看小程序。如果pathname是一个符号链接,那么删除符号链接本身。如果是FIFO ,SOCK那么仅仅删除名字,文件仍然可以使用。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> int main(int argc,char* argv[])
{
if(argc != )
{
printf("usage ./test %s",argv[]);
return -;
} if(open(argv[],O_RDWR) < )
{
printf("open file failed\n");
return -;
}
if(unlink(argv[]) < )
{
printf("unlink file %s faild\n",argv[]);
} printf("file unlinked\n"); sleep(); printf("done\n"); return ;
}

下面是验证结果:

hero@powerPC:~/source$ clear
hero@powerPC:~/source$ df /home
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 % /
hero@powerPC:~/source$ ./test tmpfile &
[]
hero@powerPC:~/source$ file unlinked
ls tmpfile
ls: cannot access tmpfile: No such file or directory
hero@powerPC:~/source$ df /home
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 % /
hero@powerPC:~/source$ done
df /home
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 % /
[]+ Done ./test tmpfile

unlink一个目录应该使用rmdir,或者一个更好的函数,remove

#include <stdio.h>

 int remove(const char *pathname);
// On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

对于目录这个函数相当于rmdir对于普通文件相当于unlink,如果是符号链接文件,删除符号链接本身,如果是FIFO ,SOCK那么仅仅删除名字,文件仍然可以使用。

符号链接

创建符号链接并要求在同一文件系统也没有创建目录硬链接的限制,通过syslink创建符号链接:

#include <unistd.h>

int symlink(const char *oldpath, const char *newpath);
//On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

该函数并不要求odlpath已经存在。

由于open函数跟随符号链接,所以通过readlink读符号链接:

#include <unistd.h>

ssize_t readlink(const char *path, char *buf, size_t bufsiz);
// On success, readlink() returns the number of bytes placed in buf. On error, -1 is returned and errno is set to indicate the error.

注意bufsiz必须足够大否则有可能截断,同时返回的符号链接内容不包括\0。

重命名

文件和目录可以通过rename函数重命名:

#include <stdio.h>

 int rename(const char *oldpath, const char *newpath);
// On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

如果oldpath是一个文件,那么newpath如果已经存在,那么它不能是目录,如果newpath不是目录那么先删除这个文件然后将oldpath重命名为newpath。

如果oldpath是一个目录,那么newpath如果已经存在,那么它不能是文件,如果newpath是目录且为空目录,那么先删除它然后将oldpath重命名为newpath。

同时newpath 不能包含oldpath.

如果oldpath或者newpath是符号链接,那么只处理符号链接本身。

如果oldpath 和newpath一样,那么函数什么都不做。

文件时间

通过futimens可以改变文件的访问时间和修改时间:

#include <sys/stat.h>
int futimens(int fd, const struct timespec times[]);
// On success, return 0. On error, -1 is returned and errno is set to indi cate the error.

times的第一个值表示访问时间,第二个值表示修改时间。timespec结构定义如下:

struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

随着times的取值不同函数的行为以及进程需要的权限也不一样:

1.如果times的值为NULL,那么现在的时间将被使用,进程的有效用户ID必须等于文件所有者ID,进程必须对文件具有写权限

2.如果2个值的任何一个值的tv_nsec值为 UTIME_NOW,那么对应的值设置为现在时间,进程的有效用户ID必须等于文件所有者ID,进程必须对文件具有写权限

3.如果2个值的任何一个值的tv_nsec值为 UTIME_OMIT,那么对应的值不变

4.如果2个值都不为空而且tv_nsec不是 UTIME_NOW或者UTIME_OMIT,那么设置相应的值。进程的有效用户ID必须等于文件所有者ID。

目录

通过mkdir函数可以建立目录,rmdir删除目录,在建立目录的时候一般加上可执行权限。

#include <sys/stat.h>
#include <unistd.h>
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
//Both return: 0 if OK, −1 on error

调用rmdir,pathname 必须是空目录。

目录可以被有权限的进程读,但是只有内核可以写目录。

#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
//Both return: pointer if OK, NULL on error
struct dirent *readdir(DIR *dp);
//Returns: pointer if OK, NULL at end of directory or error
void rewinddir(DIR *dp);
int closedir(DIR *dp);
//Returns: if OK, − on error
long telldir(DIR *dp);
//Returns: current location in directory associated with dp
void seekdir(DIR *dp, long loc);

每个进程都有一个当前工作目录,可以通过chdir改变进程当前工作目录,注意由于当前工作目录是进程的属性,调用此函数并不会改变运行程序的目录。

#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
//Both return: 0 if OK, −1 on error

下面是个使用这些函数的示例小程序:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string>
using std::string; #define BUF_SIZE 100 char* pwd()
{
char* curdir = NULL;
char buf[BUF_SIZE];
curdir = getcwd(buf,BUF_SIZE);
if(curdir)
{
printf("curdir %s\n",curdir);
} return curdir;
} int newdir(const char* path)
{
if(mkdir(path,S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH) < )
{
printf("create dir failed\n");
return -;
} return ; } int newfile(const char* path)
{
if(creat(path,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < )
{
printf("create file failed\n");
return -;
} return ;
} int main(int argc,char* argv[])
{
int savedfd;
DIR* dir= NULL;
struct dirent* entry = NULL;
struct stat finfo;
char buf[BUF_SIZE]; string full_path =getcwd(buf,BUF_SIZE);
full_path.append("/");
full_path.append("testdir");
string prefix = full_path;
if((savedfd = open(".",O_DIRECTORY)) < )
{
printf("save current dir faild\n");
}
if(!pwd())
{
return -;
} if(newdir("testdir"))
{
return -;
} if(chdir("testdir")< )
{
printf("change into testdir faild\n");
return -;
} if(!pwd())
{
return -;
} if(newdir("testdir1"))
{
return -;
} if(newfile("testfile"))
{
return -;
} if(fchdir(savedfd)<)
{
printf("change to ori dir failed\n");
return -;
} dir = opendir("testdir"); if(dir == NULL)
{
printf("open dir testdir failed\n");
return -;
} while((entry = readdir(dir))!= NULL )
{
printf("dir entry name:%s\n",entry->d_name);
full_path = prefix + "/"+ entry->d_name;
printf("full path:%s\n",full_path.c_str());
if(stat(full_path.c_str(),&finfo) < )
{
printf("stat file %s failed\n",entry->d_name);
return -; } if(S_ISDIR(finfo.st_mode))
{
printf("%s is dir\n",entry->d_name);
} if(S_ISREG(finfo.st_mode))
{
printf("%s is reguler \n",entry->d_name);
}
} return closedir(dir); }
上一篇:MySQL基础学习(一) 命令行命令


下一篇:Linux nginx 负载的几种方式