文件属性

获取文件信息: stat

#include <sys/stat.h>

struct stat
  {
    __dev_t st_dev;        /* Device.  */
#ifndef __x86_64__
    unsigned short int __pad1;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __ino_t st_ino;        /* File serial number.    */
#else
    __ino_t __st_ino;            /* 32bit file serial number.    */
#endif
#ifndef __x86_64__
    __mode_t st_mode;            /* File mode.  */
    __nlink_t st_nlink;            /* Link count.  */
#else
    __nlink_t st_nlink;        /* Link count.  */
    __mode_t st_mode;        /* File mode.  */
#endif
    __uid_t st_uid;        /* User ID of the file's owner.    */
    __gid_t st_gid;        /* Group ID of the file's group.*/
#ifdef __x86_64__
    int __pad0;
#endif
    __dev_t st_rdev;        /* Device number, if device.  */
#ifndef __x86_64__
    unsigned short int __pad2;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
    __off_t st_size;            /* Size of file, in bytes.  */
#else
    __off64_t st_size;            /* Size of file, in bytes.  */
#endif
    __blksize_t st_blksize;    /* Optimal block size for I/O.  */
#if defined __x86_64__  || !defined __USE_FILE_OFFSET64
    __blkcnt_t st_blocks;        /* Number 512-byte blocks allocated. */
#else
    __blkcnt64_t st_blocks;        /* Number 512-byte blocks allocated. */
#endif
#ifdef __USE_XOPEN2K8
    /* Nanosecond resolution timestamps are stored in a format
       equivalent to 'struct timespec'.  This is the type used
       whenever possible but the Unix namespace rules do not allow the
       identifier 'timespec' to appear in the <sys/stat.h> header.
       Therefore we have to handle the use of this header in strictly
       standard-compliant sources special.  */
    struct timespec st_atim;        /* Time of last access.  */
    struct timespec st_mtim;        /* Time of last modification.  */
    struct timespec st_ctim;        /* Time of last status change.  */
# define st_atime st_atim.tv_sec    /* Backward compatibility.  */
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec
#else
    __time_t st_atime;            /* Time of last access.  */
    __syscall_ulong_t st_atimensec;    /* Nscecs of last access.  */
    __time_t st_mtime;            /* Time of last modification.  */
    __syscall_ulong_t st_mtimensec;    /* Nsecs of last modification.  */
    __time_t st_ctime;            /* Time of last status change.  */
    __syscall_ulong_t st_ctimensec;    /* Nsecs of last status change.  */
#endif
#ifdef __x86_64__
    __syscall_slong_t __glibc_reserved[3];
#else
# ifndef __USE_FILE_OFFSET64
    unsigned long int __glibc_reserved4;
    unsigned long int __glibc_reserved5;
# else
    __ino64_t st_ino;            /* File serial number.    */
# endif
#endif
  };

int stat(const char* pathname, struct stat *statbuf);
int lstat(const char* pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
  • stat: 返回命名文件的相关信息
  • lstat: 如果文件属于符号链接,那么返回链接自身的信息
  • fstat: 返回由某个打开文件描述符所指代文件的相关信息
#include <sys/sysmacros.h>
#if defined(_AIX)
#define _BSD
#endif
#if defined(__sgi) || defined(__sun) /* Some systems need this */
#include <sys/mkdev.h>               /* To get major() and minor() */
#endif
#if defined(__hpux) /* Other systems need this */
#include <sys/mknod.h>
#endif
#include <sys/stat.h>
#include <time.h>
#include "file_perms.h"
#include "tlpi_hdr.h"

static void displayStatInfo(const struct stat *sb)
{
    printf("File type:                ");

    switch (sb->st_mode & S_IFMT)
    {
    case S_IFREG:
        printf("regular file\n");
        break;
    case S_IFDIR:
        printf("directory\n");
        break;
    case S_IFCHR:
        printf("character device\n");
        break;
    case S_IFBLK:
        printf("block device\n");
        break;
    case S_IFLNK:
        printf("symbolic (soft) link\n");
        break;
    case S_IFIFO:
        printf("FIFO or pipe\n");
        break;
    case S_IFSOCK:
        printf("socket\n");
        break;
    default:
        printf("unknown file type?\n");
        break;
    }

    printf("Device containing i-node: major=%ld   minor=%ld\n",
           (long)major(sb->st_dev), (long)minor(sb->st_dev));

    printf("I-node number:            %ld\n", (long)sb->st_ino);

    printf("Mode:                     %lo (%s)\n",
           (unsigned long)sb->st_mode, filePermStr(sb->st_mode, 0));

    if (sb->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
        printf("    special bits set:     %s%s%s\n",
               (sb->st_mode & S_ISUID) ? "set-UID " : "",
               (sb->st_mode & S_ISGID) ? "set-GID " : "",
               (sb->st_mode & S_ISVTX) ? "sticky " : "");

    printf("Number of (hard) links:   %ld\n", (long)sb->st_nlink);

    printf("Ownership:                UID=%ld   GID=%ld\n",
           (long)sb->st_uid, (long)sb->st_gid);

    if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode))
        printf("Device number (st_rdev):  major=%ld; minor=%ld\n",
               (long)major(sb->st_rdev), (long)minor(sb->st_rdev));

    printf("File size:                %lld bytes\n", (long long)sb->st_size);
    printf("Optimal I/O block size:   %ld bytes\n", (long)sb->st_blksize);
    printf("512B blocks allocated:    %lld\n", (long long)sb->st_blocks);

    printf("Last file access:         %s", ctime(&sb->st_atime));
    printf("Last file modification:   %s", ctime(&sb->st_mtime));
    printf("Last status change:       %s", ctime(&sb->st_ctime));
}

int main(int argc, char *argv[])
{
    struct stat sb;
    Boolean statLink; /* True if "-l" specified (i.e., use lstat) */
    int fname;        /* Location of filename argument in argv[] */

    statLink = (argc > 1) && strcmp(argv[1], "-l") == 0;
    /* Simple parsing for "-l" */
    fname = statLink ? 2 : 1;

    if (fname >= argc || (argc > 1 && strcmp(argv[1], "--help") == 0))
        usageErr("%s [-l] file\n"
                 "        -l = use lstat() instead of stat()\n",
                 argv[0]);

    if (statLink)
    {
        if (lstat(argv[fname], &sb) == -1)
            errExit("lstat");
    }
    else
    {
        if (stat(argv[fname], &sb) == -1)
            errExit("stat");
    }

    displayStatInfo(&sb);

    exit(EXIT_SUCCESS);
}

文件时间戳

#include <utime.h>
#include <sys/time.h>

struct utimbuf {
    time_t actime;
    time_t modtime;
}

int utime(const char* pathname, const struct utimbuf *buf);
int utimes(const char* pathname, const struct timeval tv[2]);
int futimes(int fd, const struct timeval tv[2]);
int lutimes(const char* pathname, const struct timeval tv[2]);
  • utime 可以显式改变存储于文件 i 节点中的文件上次访问时间戳和上次修改时间戳【buf 为 NULL,修改为当前时间;不为 NULL,修改为 buf 指定的时间】
  • utimes 可以以微秒精度来指定时间值
  • futimes -> fd
  • lutimes -> 针对链接
struct stat st;
struct utimbuf utb;

if (stat(pathname, &st) == -1) {
    errExit("stat");
}
utb.actime = st.st_atime;
utb.modtime = st.st_atime;
if (utime(pathname, &utb) == -1) {
    errExit("utime");
}

文件属主

每个文件都有一个与之关联的 UID 和 GID。

文件创建时,其 UID “取自”进程的有效 UID,GID “取自”进程的有效 GID。

#include <unistd.h>

int chown(const char* pathname, uid_t owner, gid_t group);

#define _XOPEN_SOURCE 500

int lchown(const char* pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
  • chown 改变 pathname 的所有权
  • lchown 针对链接
  • f 针对 fd

如果文件组的属主或属组发生了变化,那么 set-user-ID 和 set-group-ID 权限位也会随之关闭。

#include <pwd.h>
#include <grp.h>
#include "ugid_functions.h" /* Declarations of userIdFromName()
                                           and groupIdFromName() */
#include "tlpi_hdr.h"

int main(int argc, char *argv[])
{
    uid_t uid;
    gid_t gid;
    int j;
    boolean errFnd;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s owner group [file...]\n"
                 "        owner or group can be '-', "
                 "meaning leave unchanged\n",
                 argv[0]);

    if (strcmp(argv[1], "-") == 0)
    { /* "-" ==> don't change owner */
        uid = -1;
    }
    else
    { /* Turn user name into UID */
        uid = user_id_from_name(argv[1]);
        if (uid == -1)
            fatal("No such user (%s)", argv[1]);
    }

    if (strcmp(argv[2], "-") == 0)
    { /* "-" ==> don't change group */
        gid = -1;
    }
    else
    { /* Turn group name into GID */
        gid = group_id_from_name(argv[2]);
        if (gid == -1)
            fatal("No group user (%s)", argv[2]);
    }

    /* Change ownership of all files named in remaining arguments */

    errFnd = FALSE;
    for (j = 3; j < argc; j++)
    {
        if (chown(argv[j], uid, gid) == -1)
        {
            errMsg("chown: %s", argv[j]);
            errFnd = TRUE;
        }
    }

    exit(errFnd ? EXIT_FAILURE : EXIT_SUCCESS);
}

文件权限

st_mode 字段的低 12 位定义了文件权限。其中前三位【UGT】分别是 set-user-ID 位、set-group-ID 位和 sticky 位。

  • 4 【set-user-ID / read】
  • 2 【set-group-ID / write】
  • 1 【sticky / execute】

目录权限

  • 读权限:可列出【ls】目录下的内容
  • 写权限:可在目录内创建、删除文件【删除时,无需对文件本身有任何权限】
  • 可执行权限:可访问目录中的文件【搜索权限】

权限检查算法

特权用户 -> 属主用户 -> 属组用户 -> 其他用户

检查对文件的访问权限

#include <unistd.h>

int access(const char* pathname, int mode);

系统调用 access 根据进程的真实用户 ID 和组 ID,去检查对 pathename 参数所指定文件的访问权限。

  • F_OK
  • R_OK
  • W_OK
  • X_OK

Sticky 权限位

Sticky 权限位起限制删除位的作用。为目录设置该位,则表明仅当非特权进程具有对目录的写权限,且为文件或目录的属主时,才能对目录下的文件进行删除【unlink, rmdir, rename】

进程的文件模式创建掩码

umask 是一种进程属性,当进程新建文件或目录时,该属性用于指明应屏蔽哪些权限位。

#include <sys/stat.h>

mode_t umask(mode_t mask);

系统调用 umask 将进程的 umask 改变为 mask 参数所指定的值【对 umask 的调用总会成功,并返回进程的前 umask】

#include <sys/stat.h>
#include <fcntl.h>
#include "file_perms.h"
#include "tlpi_hdr.h"

#define MYFILE "myfile"
#define MYDIR "mydir"
#define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define DIR_PERMS (S_IRWXU | S_IRWXG | S_IRWXO)
#define UMASK_SETTING (S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH)

int main(int argc, char *argv[])
{
    int fd;
    struct stat sb;
    mode_t u;

    umask(UMASK_SETTING);

    fd = open(MYFILE, O_RDWR | O_CREAT | O_EXCL, FILE_PERMS);
    if (fd == -1)
        errExit("open-%s", MYFILE);
    if (mkdir(MYDIR, DIR_PERMS) == -1)
        errExit("mkdir-%s", MYDIR);

    u = umask(0); /* Retrieves (and clears) umask value */

    if (stat(MYFILE, &sb) == -1)
        errExit("stat-%s", MYFILE);
    printf("Requested file perms: %s\n", filePermStr(FILE_PERMS, 0));
    printf("Process umask:        %s\n", filePermStr(u, 0));
    printf("Actual file perms:    %s\n\n", filePermStr(sb.st_mode, 0));

    if (stat(MYDIR, &sb) == -1)
        errExit("stat-%s", MYDIR);
    printf("Requested dir. perms: %s\n", filePermStr(DIR_PERMS, 0));
    printf("Process umask:        %s\n", filePermStr(u, 0));
    printf("Actual dir. perms:    %s\n", filePermStr(sb.st_mode, 0));

    if (unlink(MYFILE) == -1)
        errMsg("unlink-%s", MYFILE);
    if (rmdir(MYDIR) == -1)
        errMsg("rmdir-%s", MYDIR);
    exit(EXIT_SUCCESS);
}

更改文件权限

#include <sys/stat.h>

int chmod(const char* pathname, mode_t mode);

#define _XOPEN_SOURCE 500
int fchmod(int fd, mode_t mode);

系统调用 chmod/fchmod 更改参数文件的权限。

chmod u+w,o-r myfile

struct stat st;
mode_t mode;

if (stat("myfile", &st) == -1) {
    errExit("stat");
}
mode = (st.st_mode | S_IWUSR) & ~S_IROTH;
if (chmod("myfile", mode) == -1) {
    errExit("chmod");
}

i 节点标志

某些 Linux 文件系统允许为文件和目录设置各种各样的 i-node flags。【lsattr, chattr】可在程序中使用 ioctl 来获取并修改 i 节点标志。

常量chattr 选项用途
FS_APPENDFLa仅能在尾部追加【需要特权】
FS_COMPRc启用文件压缩
FS_DIRSYNC_FLD目录更新同步
FS_IMMUTABLE_FLi不可变更【需要特权】
FS_JOURNAL_DATA_FLj针对数据启用日志功能【需要特权】
FS_NOATIME——FLA不更新文件的上次访问时间
FS_NODUMP_FLd不转储
FS_NOTAIL_FLt禁用尾部打包
FS_SECRM_FLs安全删除【未实现】
FS_SYNC_FLS文件和目录同步更新
FS_TOPDIC_FLY以 Orlov 策略来处理顶层目录
FS_UNRM_FLu可恢复已删除的文件【未实现】
int attr;

if (ioctl(fd, FS_IOC_GETFLAGS, &attr) == -1) {
    errExit("ioctl");
}
attr |= FS_NOATIME_FL;
if (ioctl(fd, FS_IOC_SETFLAGS, &attr) == -1) {
    errExit("ioctl");
}