0%

Linux系统编程-文件和目录管理

  • toc
    {:toc}

专注于用户空间的系统级编程–即内核之上的所有内容。

获得文件metadata的Stat家族

#include <sys/types.h>  
#include <sys/stat.h>  
#include <unistd.h>  

struct stat{  
    dev_t st_dev; /*包含文件的设备号*/  
    ino_t st_ino; /*文件inode序列号 */  
    mode_t st_mode;/*文件的mode*/  
    nlink_t st_nlink; /*硬链接的数量*/  
    uid_t st_uid; /*文件的用户id*/  
    gid_t st_gid; /*文件的goup id*/  
    off_t st_size;/*文件大小*/  

    time_t st_atime;/*最后一次访问时间*/  
    time_t st_mtime;/*最后一次修改时间*/  
    time_t st_ctime;/*最后一次状态改变时间*/  
}  

int lstat(const char * restrict path, struct stat * restrict buf);  
int stat(const char *restrict path, struct stat * restrict buf);  
int fstat(int fd,struct stat *buf);  

对于软链来说:

  • lstat返回软链本身的状态
  • stat返回软链所指文件的状态

例子:

#include <stdio.h>  
#include <time.h>  
#include <sys/stat.h>  

int isdirectory(char *path) {  
    struct stat statbuf;  

    if (stat(path, &statbuf) == -1)  
        return 0;  
    else  
        return S_ISDIR(statbuf.st_mode);  
}  

文件权限

设置文件权限的系统调用:

#include <sys/types.h>  
#include <sys/stat.h>  

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

成功返回0,失败返回-1,并设置errno:

  • EACCESS:没有搜索path的权限
  • EBADF:fd不合法的文件描述符(仅fchmod)
  • EFAULT:path不合法的指针(仅chmod)
  • EIO:文件系统内部I/O错误
  • ELOOP:由于symbolic link导致解析path死循环
  • ENAMETOOLONG:path太长(仅chmod)
  • ENOENT:path不存在
  • ENOME:内存不足
  • ENOTDIR:不是一个目录(仅chmod)
  • EPERM:进程不是文件的owner或者缺少CAP_FOWNER能力。
  • EROFS:文件在只读文件系统中。

文件的所有者

stat结构中st_uid和st_gid提供了文件的所有者和group,以下系统调用可以改变所有者:

#include <sys/types.h>  
#include <unistd.h>  

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

chown和lchown区别:chown会follow simbolic link,改变link的目标文件,lchmod改变符号链接本身。
成功过返回0,失败返回-1,并设置errno。

struct group *gr;  
int ret;  

gr = getgrnam("officers");  
if(! gr){  
    perror("getgrnam");  
    return 1;  
}  

ret = chmod("manifest.txt",-1,gr->gr_gid);  
if(ret){  
    perror("chmod");  
    return 1;  
}  

设置为root拥有者:

int make_root_owner(int fd){  
    int ret;  

    ret = fchown(fd,0,0);  
    if(ret)  
        perror("fchown");  
    return ret;  
}  

进程需要有CAP_CHOWN能力,也就是必须是root为所有。

文件系统Navigation

改变当前的工作目录

#include <unistd.h>  

int chdir(const char *path);  
int fchdir(int fd);  

将当前的工作目录指定为path:

char *dir = "/tmp";  
if(chdir(dir) == -1)  
    perror("Failed to change current working directory to /tmp");  

获得当前工作目录

#include <unistd.h>  

char *getcwd(char *buf, size_t size);  

例子: 

#include <limits.h>  
#include <stdio.h>  
#include <unistd.h>  
#ifndef PATH_MAX  
#define PATH_MAX 255  
#endif  

int main(void)
{  
    char mycwd[PATH_MAX];  
    if(getcwd(mycwd,PATH_MAX) == NULL){  
        perror("Failed to get current working directory");  
        return 1;  
    }  
    printf("Current working directory: %s\n",mycwd);  
    return 0;  
}  

path conf

#include <unistd.h>  

long fpathconf(int fd, int name);  
long pathconf(const char *path,int name);  
sysconf(int name);  

创建目录

#include <sys/stat.h>  
#include <sys/types.h>  

int mkdir(const char *path, mode_t mode);  

成功返回0,失败返回-1,并设置好errno。没有可以递归删除与rm -r等价的系统调用。
如果目录非空,失败设置errno为ENOTEMPTY。

int ret;  

ret = rmdir("/home/fuliang/test");  
if(ret)  
    perror("rmdir");  

删除目录

#include <unistd.h>  

int rmdir(const char *path);  

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

文件夹访问

opendir closedir, readdir

#include <dirent.h>  
DIR *opendir(const char *dirname);  
struct dirent *readdir(DIR *dirp);  
int closedir(DIR *dirp);  
void rewinddir(DIR *dirp);  

例子:显示pathname下的文件:

#include <dirent.h>  
#include <errno.h>  
#include <stdio.h>  

int main(int argc, char *argv[])
{  
    struct dirent *direntp;  
    DIR *dirp;  

    if(argc != 2){  
        fprintf(stderr,"Usage %s directory_name\n", argv[0]);  
    }  

    if((dirp = opendir(argv[1])) == NULL){  
        perror("Failed to open directory");  
        return 1;  
    }  

    while((direntp = readdir(dirp)) != NULL)  
        printf("%s\n", direntp->d_name);  
    while((closedir(dirp) == -1) && (errno == EINTR)) ;  
    return 0;   
}  

dirent结构:

struct dirent{  
    ino_t d_ino; /* inode number */  
    off_t d_off; /* offset to the next dirent */  
    unsigned short d_reclen; /* length of this record */  
    unsigned char d_type; /* type of file */  
    char d_name[256]; /*filename*/    
};  

POSIX仅需要d_name字段,其他的都是可选的或者是linux特有的。
可移植性的程序应该只访问d_name字段。

从Dir可以获得文件描述符:

#define _BSD_SOURCE  
#include <sys/types.h>  
#include <dirent.h>  

int dirfd(DIR *dir);  

这个是BSD的一个扩展,并不是POSIX标准。

访问文件的状态信息

lstat、stat

硬链接和软链接

硬链接:两个path指向同一个inode.
软链接:单独的一个文件,里面存储了链接文件的路径。

创建和删除一个硬链接

#include <unistd.h>  

int link(const char *path1, const char *path2);  
int unlink(const char *path);  

为有path1指定的文件创建一个新的目录项。

#include <stdio.h>  
#include <unistd.h>  

if (link("/dirA/name1","dirB/name2") == -1)  
    perror("Failed to make a new link in /dirB");  

创建和删除一个硬链接

#include <unistd.h>  

int symlink(const char *path1, const char *path2);  

拷贝或者移动文件

拷贝和移动文件时两个最基本的文件操作任务,可以使用shell命令cp和mv来完成。

拷贝文件

Unix没有提供此操作的系统调用或者库来完成拷贝文件和目录,但可以使用cp命令来手工执行这个任务。
拷贝一个文件src到目标dst,要执行的操作步骤:

  1. 打开src
  2. 打开dst,创建如果不存在,清空如果存在。
  3. 将src读取到内存
  4. 将从src读取到的内容写入dst
  5. 继续指导所有内容从src写到dst
  6. 关闭dst
  7. 关闭src

如果拷贝一个目录,递归拷贝目录本身及其子目录通过mkdir及其采用上面步骤拷贝文件。

移动

Unix提供了移动文件的系统调用。ANSI C使用这个调用来移动文件,POSIX标准将其用于文件和目录。

#include <stdio.h>  

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

将路径名称从oldpath改成newpath,内容和inode没有变化。newpath需要在同一文件系统中。
成功返回0,失败返回-1,并设置errno。

创建临时文件

ISO C标准定义了两个标准I/O函数来创建临时文件。

#include <stdio.h>  

char *tmpnam(char *ptr); /*返回执行路径的指针*/  
FILE *tmpfile(void); /*返回文件指针,错误则为NULL*/  

tmpnam每次产生一个不同的合法的路径名字。如果ptr是NULL,生成一个存储在静态区域的名字,
并返回指向它的指针,所以如果连续调用两次会覆盖前一次的tmpname。
如果指定了ptr,则它被假设指向一个数组,并且长度不小于L_tmpnam(<stdio.h>中定义)。
例子:

#include <stdio.h>  

int main(void){  
    char name[L_tmpnam],line[MAXLINE];  
    FILE *fp;  

    printf("%s\n",tmpnam(NULL));  
    tmpname(name);  
    printf("%s\n",name);  

    if((fp = tmpfile()) == NULL){  
        perror("tmpfile");  
        return 1;  
    }  

    fputs("one line of output\n",fp);  
    rewind(fp);  

    if(fgets(line,sizeof(line),fp) == NULL){  
        perror("fgets");  
        return 1;  
    }  
    fputs(line,stdout);  

    return 0;  
}  

XSI扩展定义了两个另外的方法来操作临时文件:

#include <stdio.h>  

char *tempnam(const char *directory, const char *prefix);  
int mkstemp(char *template);  

tempnam和tmpnam一样,但是可以指定目录和前缀来生成临时文件名。

  1. 如果环境变量定义了TMPDIR,则使用它作为目录。
  2. 如果directory不为NULL,则使用它。
  3. 在<stdio.h>中的P_tmpdir作为目录。
  4. 本地的目录,通常/tmp被作为临时目录。

前缀prefix如果不为NULL,至少5个字节。
生成的名字是通过malloc动态申请的内存,所以要使用free释放。

#include <stdio.h>  

int main(int argc, char *argv[]){  
    if(argc != 3){  
        pintf("usage: %s <directory> <prefix>",argv[0]);  
        return 1;  
    }  

    printf("%s\n",tempnam(argv[1][0] != ' ' ? argv[1] : NULL, argv[2][0] != ' ' ? argv[2] : NULL));  

    return 0;  
}  

mkstemp和tmpfile类似,只是返回了文件描述符,并且mkstemp生成的临时文件不会自动清除。
path的后六个字符要设置为XXXXXX,通过替换它来生成不同的名字。