Linux provides two distinct approaches for file I/O operations: library functions (buffered/standard I/O) and system calls (unbuffered I/O). Understanding the differences between these approaches is essential for writing efficeint file handling code.
Buffered I/O (Standard Library Functions)
Buffered I/O functions are part of the C standard library and provide user-space buffering, which reduces the number of system calls and improves performance.
Opening Files: fopen
Header: #include <stdio.h>
Function prototypes:
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
Parameters:
pathname: Path to the filefd: File descriptor (for fdopen)mode: File open mode (r, w, a, r+, w+, a+)- Returns: FILE pointer on success, NULL on failure
Reading Files: fread
Header: #include <stdio.h>
size_t fread(void *buffer, size_t element_size, size_t count, FILE *stream);
Parameters:
buffer: Memory buffer to store read dataelement_size: Size of each element in bytescount: Number of elements to readstream: File stream pointer- Returns: Number of elements successfully read (may be less than count on error/EOF)
Writing Files: fwrite
Header: #include <stdio.h>
size_t fwrite(const void *buffer, size_t element_size, size_t count, FILE *stream);
Parameters:
buffer: Memory buffer containing data to writeelement_size: Size of each element in bytescount: Number of elements to writestream: File stream pointer- Returns: Number of elements successfully written
Closing Files: fclose
Header: #include <stdio.h>
int fclose(FILE *stream);
Parameters:
stream: File stream pointer to close- Returns: 0 on success, EOF on error
Example: File Copy Using Buffered I/O
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int option;
char src_path[256] = {0}, dst_path[256] = {0};
FILE *src_file = NULL, *dst_file = NULL;
while ((option = getopt(argc, argv, "s:d:")) != -1) {
switch (option) {
case 's':
strncpy(src_path, optarg, sizeof(src_path) - 1);
break;
case 'd':
strncpy(dst_path, optarg, sizeof(dst_path) - 1);
break;
default:
fprintf(stderr, "Usage: %s -s source_file -d destination_file\n", argv[0]);
return 1;
}
}
if (strlen(src_path) == 0 || strlen(dst_path) == 0) {
fprintf(stderr, "Usage: %s -s source_file -d destination_file\n", argv[0]);
return 1;
}
src_file = fopen(src_path, "r");
if (src_file == NULL) {
perror("fopen source");
return 1;
}
dst_file = fopen(dst_path, "w");
if (dst_file == NULL) {
perror("fopen destination");
fclose(src_file);
return 1;
}
char data_buffer[4096] = {0};
size_t bytes_read;
while ((bytes_read = fread(data_buffer, 1, sizeof(data_buffer), src_file)) > 0) {
if (fwrite(data_buffer, 1, bytes_read, dst_file) != bytes_read) {
perror("fwrite error");
break;
}
}
fclose(src_file);
fclose(dst_file);
return 0;
}
Unbuffered I/O (System Calls)
System calls provide direct interface to the kernel without user-space buffering. These operations interact directly with the operating system.
Opening Files: open
Headers:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
Function prototypes:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Parameters:
pathname: Path to the fileflags: Bitwise OR of access mode and optional flags:O_RDONLY: Read-onlyO_WRONLY: Write-onlyO_RDWR: Read-writeO_CREAT: Create file if it doesn't exist (requires mode parameter)O_EXCL: Fail if file exists (used with O_CREAT)O_TRUNC: Truncate file to zero lengthO_APPEND: Append to end of file
mode: File permissions (e.g., 0644, S_IRUSR|S_IWUSR)- Returns: File descriptor on success, -1 on failure
Reading Files: read
Header: #include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
Parameters:
fd: File descriptorbuf: Buffer to store read datacount: Number of bytes to read- Returns: Number of bytes read, 0 on EOF, -1 on error
Writing Files: write
Header: #include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
Parameters:
fd: File descriptorbuf: Buffer containing data to writecount: Number of bytes to write- Returns: Number of bytes written, -1 on errer
Closing Files: cloce
Header: #include <unistd.h>
int close(int fd);
Parameters:
fd: File descriptor to close- Returns: 0 on success, -1 on error
Important notes:
- close() releases the file descriptor association with the file
- When a process terminates, the kernel automatically closes all open file descriptors
- The returned file descriptor is always the lowest numbered unused descriptor
Creating Files: creat
Headers:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
Parameters:
pathname: Path to the filemode: File permissions- Returns: File descriptor on success, -1 on failure
Example: Reading File Contents
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
int file_descriptor = open(argv[1], O_RDONLY);
if (file_descriptor == -1) {
perror("open failed");
fprintf(stderr, "Error code: %d\n", errno);
return 1;
}
char read_buffer[1024] = {0};
ssize_t bytes_received;
while ((bytes_received = read(file_descriptor, read_buffer, sizeof(read_buffer))) > 0) {
if (write(STDOUT_FILENO, read_buffer, bytes_received) != bytes_received) {
perror("write failed");
break;
}
}
if (bytes_received == -1) {
perror("read failed");
}
close(file_descriptor);
return 0;
}
Example: File Copy Using System Calls
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[]) {
int input_fd, output_fd;
if (argc < 3) {
fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}
input_fd = open(argv[1], O_RDONLY);
if (input_fd == -1) {
perror("open input");
return 1;
}
output_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (output_fd == -1) {
perror("open output");
close(input_fd);
return 1;
}
char transfer_buffer[2048] = {0};
ssize_t bytes_in, bytes_out;
while ((bytes_in = read(input_fd, transfer_buffer, sizeof(transfer_buffer))) > 0) {
bytes_out = write(output_fd, transfer_buffer, bytes_in);
if (bytes_out != bytes_in) {
perror("write error");
break;
}
}
if (bytes_in == -1) {
perror("read error");
}
close(input_fd);
close(output_fd);
return 0;
}
Additional File Operations
File Descriptor Limits
- View limits:
ulimit -ashows open files limit (default 1024) - Modify limit:
ulimit -n 4096increases limit
Obtaining File Information: stat
The stat structure provides comprehensive file metadata:
struct stat {
dev_t st_dev; /* Device ID containing file */
ino_t st_ino; /* Inode number (unique file identifier) */
mode_t st_mode; /* File type and permissions */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* Owner user ID */
gid_t st_gid; /* Owner group ID */
off_t st_size; /* File size in bytes */
blksize_t st_blksize; /* Preferred block size for I/O */
blkcnt_t st_blocks; /* Number of 512-byte blocks allocated */
struct timespec st_atim; /* Last access time */
struct timespec st_mtim; /* Last modification time */
struct timespec st_ctim; /* Last status change time */
};
Example: Displaying File Information
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
const char *target_path = (argc > 1) ? argv[1] : ".";
struct stat file_info;
if (lstat(target_path, &file_info) == -1) {
perror("lstat");
return 1;
}
/* Method 1: Using S_IS* macros */
if (S_ISREG(file_info.st_mode)) {
printf("Regular file\n");
} else if (S_ISDIR(file_info.st_mode)) {
printf("Directory\n");
} else if (S_ISLNK(file_info.st_mode)) {
printf("Symbolic link\n");
} else if (S_ISFIFO(file_info.st_mode)) {
printf("FIFO/pipe\n");
}
/* Method 2: Using bitwise AND with S_IFMT */
switch (file_info.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("Symbolic link\n"); break;
case S_IFREG: printf("Regular file\n"); break;
case S_IFSOCK: printf("Socket\n"); break;
default: printf("Unknown type\n"); break;
}
printf("Inode: %lu\n", (unsigned long)file_info.st_ino);
printf("Size: %ld bytes\n", (long)file_info.st_size);
printf("Hard links: %lu\n", (unsigned long)file_info.st_nlink);
return 0;
}
Modifying File Properties: fcntl
The fcntl function modifies properties of already open files without reopening them.
Headers:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int command, ... /* arg */);
Example: Setting and Getting File Flags
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
/* Set non-blocking mode */
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0) {
perror("fcntl F_GETFL");
close(fd);
return 1;
}
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
/* Retrieve and display access mode */
flags = fcntl(fd, F_GETFL);
switch (flags & O_ACCMODE) {
case O_RDONLY: printf("Read-only\n"); break;
case O_WRONLY: printf("Write-only\n"); break;
case O_RDWR: printf("Read-write\n"); break;
}
if (flags & O_NONBLOCK) {
printf("Non-blocking mode enabled\n");
}
/* Remove non-blocking flag */
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
close(fd);
return 0;
}
File Positioning: lseek
Headers:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
Parameters:
fd: File descriptoroffset: Offset from reference positionwhence: Reference position (SEEK_SET, SEEK_CUR, SEEK_END)- Returns: New file position on success, -1 on error
Example: File Positioning Operations
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[]) {
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open");
fprintf(stderr, "errno = %d\n", errno);
return 1;
}
/* Get current position */
printf("Initial position: %ld\n", lseek(fd, 0, SEEK_CUR));
/* Read two bytes and advance position */
char ch;
read(fd, &ch, 1);
read(fd, &ch, 1);
printf("After reading 2 bytes: %ld\n", lseek(fd, 0, SEEK_CUR));
/* Skip 3 bytes forward */
lseek(fd, 3, SEEK_CUR);
printf("After skipping 3 bytes: %ld\n", lseek(fd, 0, SEEK_CUR));
/* Get file size */
off_t file_size = lseek(fd, 0, SEEK_END);
printf("File size: %ld bytes\n", (long)file_size);
/* Extend file by 100 bytes */
off_t new_size = lseek(fd, 100, SEEK_CUR);
printf("New file size after extension: %ld bytes\n", (long)new_size);
/* Write to trigger actual allocation */
if (write(fd, "x", 1) != 1) {
perror("write");
}
close(fd);
return 0;
}
Creating Hard Links: link
int link(const char *existing_path, const char *new_path);
- Creates a new directory entry (hard link) for an existing file
- Returns 0 on success, -1 on failure
- Note: The mv command only modifies directory entries, not the file itself
Removing Links: unlink
int unlink(const char *pathname);
- Removes a directory entry for a file
- The file is actually deleted when the link count reaches zero
- Returns 0 on success, -1 on failure
Linux removes files by decrementing the link count. When the count reaches zero and no process has the file open, the operating system reclaims the disk space.
Checking File Access: access
int access(const char *pathname, int mode);
mode values:
R_OK: Check read permissionW_OK: Check write permissionX_OK: Check execute permissionF_OK: Check file existence
Returns: 0 if permission exists or file exists, -1 otherwise
Changing Permissions: chmod
int chmod(const char *pathname, mode_t mode);
- Modifies file access permissions
- Returns 0 on success, -1 on failure
Truncating Files: truncate
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
- Sets file size to specified length
- Can extend or truncate files
- Useful for extending files without writing data
Duplicating File Descriptors: dup/dup2
int dup(int oldfd);
int dup2(int oldfd, int newfd);
Example: Basic dup Usage
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
int original = open(argv[1], O_RDONLY); /* Typically fd 3 */
int duplicate = dup(original); /* Gets next available fd */
printf("Duplicated fd: %d\n", duplicate);
close(original);
close(duplicate);
return 0;
}
Example: dup2 for Redirection
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[]) {
int file1 = open(argv[1], O_RDWR);
int file2 = open(argv[2], O_RDWR);
printf("Original fds: file1=%d, file2=%d\n", file1, file2);
/* Redirect file2 to file1 */
int result = dup2(file1, file2);
printf("dup2 returned: %d\n", result);
const char *msg = "written via redirected fd\n";
write(file2, msg, strlen(msg));
close(file1);
close(file2);
return 0;
}
Using fcntl for Duplication
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
int original = open(argv[1], O_RDWR);
printf("Original fd: %d\n", original);
/* Duplicate to lowest available fd */
int copy1 = fcntl(original, F_DUPFD, 0);
printf("Copy1 (lowest available): %d\n", copy1);
/* Duplicate to specific fd (100) */
int copy2 = fcntl(original, F_DUPFD, 100);
printf("Copy2 (fd 100): %d\n", copy2);
close(original);
close(copy1);
close(copy2);
return 0;
}