stat file from DIR *
Categories:
Understanding stat
and lstat
for File Information in C on Linux

Explore how to use the stat
and lstat
system calls in C to retrieve detailed file and directory metadata on Linux systems, including handling symbolic links.
When working with files and directories in C on Linux, understanding their metadata is crucial. The stat
and lstat
system calls provide a powerful way to retrieve this information, such as file type, permissions, size, ownership, and timestamps. This article will guide you through using these functions, explaining their differences, especially concerning symbolic links, and providing practical code examples.
The stat
and lstat
System Calls
Both stat
and lstat
are POSIX-compliant functions declared in <sys/stat.h>
. They take a file path as input and populate a struct stat
with various attributes of the file or directory. The primary difference lies in how they handle symbolic links:
stat()
: If the path refers to a symbolic link,stat()
dereferences it and returns information about the target file or directory.lstat()
: If the path refers to a symbolic link,lstat()
returns information about the symbolic link itself, not its target.
flowchart TD A[Input Path] --> B{Is Path a Symbolic Link?} B -- Yes --> C{Function Called?} B -- No --> D[Get Info for File/Directory] C -- stat() --> E[Dereference Link] E --> D C -- lstat() --> F[Get Info for Symbolic Link Itself] F --> G[Return Link Info] D --> G
Flowchart illustrating the difference between stat
and lstat
when encountering a symbolic link.
Understanding struct stat
The struct stat
is a comprehensive data structure that holds all the metadata about a file. Key members include:
st_mode
: File type and permissions (e.g.,S_ISREG()
,S_ISDIR()
,S_ISLNK()
for type;S_IRUSR
,S_IWGRP
for permissions).st_ino
: Inode number.st_dev
: ID of device containing file.st_nlink
: Number of hard links.st_uid
: User ID of owner.st_gid
: Group ID of owner.st_size
: Total size in bytes.st_blksize
: Block size for filesystem I/O.st_blocks
: Number of 512B blocks allocated.st_atime
: Time of last access.st_mtime
: Time of last modification.st_ctime
: Time of last status change.
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
void print_file_info(const char *path, const struct stat *sb) {
printf("\nFile: %s\n", path);
printf(" Type: ");
switch (sb->st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: printf("directory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: printf("regular file\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n"); break;
}
printf(" Inode: %ld\n", (long) sb->st_ino);
printf(" Device: %ld\n", (long) sb->st_dev);
printf(" Links: %ld\n", (long) sb->st_nlink);
struct passwd *pw = getpwuid(sb->st_uid);
struct group *gr = getgrgid(sb->st_gid);
printf(" Owner: %s (UID %ld)\n", pw ? pw->pw_name : "unknown", (long) sb->st_uid);
printf(" Group: %s (GID %ld)\n", gr ? gr->gr_name : "unknown", (long) sb->st_gid);
printf(" Size: %lld bytes\n", (long long) sb->st_size);
printf(" Blocks: %lld (%lld bytes)\n", (long long) sb->st_blocks, (long long) sb->st_blocks * 512);
printf(" Access: %s", ctime(&sb->st_atime));
printf(" Modify: %s", ctime(&sb->st_mtime));
printf(" Change: %s", ctime(&sb->st_ctime));
printf(" Permissions: ");
printf((S_ISDIR(sb->st_mode)) ? "d" : "-");
printf((sb->st_mode & S_IRUSR) ? "r" : "-");
printf((sb->st_mode & S_IWUSR) ? "w" : "-");
printf((sb->st_mode & S_IXUSR) ? "x" : "-");
printf((sb->st_mode & S_IRGRP) ? "r" : "-");
printf((sb->st_mode & S_IWGRP) ? "w" : "-");
printf((sb->st_mode & S_IXGRP) ? "x" : "-");
printf((sb->st_mode & S_IROTH) ? "r" : "-");
printf((sb->st_mode & S_IWOTH) ? "w" : "-");
printf((sb->st_mode & S_IXOTH) ? "x" : "-");
printf("\n");
}
int main(int argc, char *argv[]) {
struct stat sb;
if (argc != 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(EXIT_FAILURE);
}
// Using stat()
if (stat(argv[1], &sb) == -1) {
perror("stat");
// exit(EXIT_FAILURE); // Don't exit, try lstat next
} else {
printf("--- Using stat() ---");
print_file_info(argv[1], &sb);
}
// Using lstat()
if (lstat(argv[1], &sb) == -1) {
perror("lstat");
exit(EXIT_FAILURE);
} else {
printf("\n--- Using lstat() ---");
print_file_info(argv[1], &sb);
}
return 0;
}
C program demonstrating stat
and lstat
to retrieve and print file information.
To compile the example code, use gcc -o filestat filestat.c
. Then, you can test it with various files, directories, and symbolic links, for example:
touch myfile.txt
mkdir mydir
ln -s myfile.txt mylink.txt
./filestat myfile.txt
./filestat mydir
./filestat mylink.txt
Practical Use Cases and Considerations
The stat
and lstat
functions are fundamental for many system-level tasks:
- File Type Checking: Determine if a path refers to a regular file, directory, symbolic link, etc., using macros like
S_ISREG()
,S_ISDIR()
,S_ISLNK()
. - Permission Checks: Verify read, write, or execute permissions before attempting file operations.
- Disk Usage Analysis: Get file sizes and block usage.
- Backup Utilities: Identify modified files based on
st_mtime
. - Symbolic Link Handling:
lstat
is essential when you need to operate on the symbolic link itself (e.g., deleting the link, not its target) or determine if a path is a symbolic link.
Remember to always check the return value of stat
and lstat
for errors. A return value of -1
indicates an error, and errno
will be set accordingly (e.g., ENOENT
if the file does not exist, EACCES
for permission issues).
stat
vs. lstat
) can lead to unexpected behavior or security vulnerabilities if not handled carefully.