Input-Output System Calls in C: Complete Guide
Input-Output System Calls in C: A Comprehensive Tutorial
System calls are the interface through which a program requests services from the operating system kernel, especially for operations that require privileged access. In C, many input and output operations ultimately rely on system calls, where the program interacts with the kernel to access devices such as the keyboard, monitor, and other I/O resources.
This tutorial provides an in-depth exploration of I/O system calls in C: file descriptors, creat, open, close, read, write, practical applications, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What are System Calls?
System calls are functions that request services from the operating system kernel. They provide the interface between user-space programs and the kernel for operations that require privileged access, such as file I/O, process management, and device communication.
What is a File Descriptor?
A file descriptor is an integer that uniquely identifies an opened file for a process. It is stored in the file descriptor table, where each element points to a file table entry. Each process has its own file descriptor table.
Standard file descriptors:
| File Descriptor | Name | Description |
|---|---|---|
| 0 | stdin | Standard input (keyboard) |
| 1 | stdout | Standard output (screen) |
| 2 | stderr | Standard error (screen) |
I/O System Calls Overview
| System Call | Function | Description |
|---|---|---|
creat | creat() | Create a new empty file |
open | open() | Open a file for reading/writing |
close | close() | Close a file descriptor |
read | read() | Read data from a file |
write | write() | Write data to a file |
Required Headers
c
#include <fcntl.h> // File control options (O_RDONLY, O_WRONLY, etc.) #include <unistd.h> // System calls (open, close, read, write) #include <stdio.h> // perror, printf #include <stdlib.h> // exit, malloc #include <errno.h> // errno
Syntax & Core Mechanics
1. creat() — Create a File
Creates a new empty file.
Syntax:
c
int creat(const char *filename, mode_t mode);
Parameters:
filename: Name of the file to createmode: Permissions (e.g.,0644for rw-r–r–)
Return value:
- File descriptor on success (usually 3)
-1on error
Example:
c
#include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(void) { int fd = creat("newfile.txt", 0644); if (fd == -1) { perror("Error creating file"); return 1; } printf("File created with descriptor: %d\n", fd); close(fd); return 0; } Expected output:
text
File created with descriptor: 3
2. open() — Open a File
Opens a file for reading, writing, or both.
Syntax:
c
int open(const char *path, int flags); int open(const char *path, int flags, mode_t mode);
Common flags:
| Flag | Description |
|---|---|
O_RDONLY | Read-only |
O_WRONLY | Write-only |
O_RDWR | Read and write |
O_CREAT | Create if doesn’t exist |
O_EXCL | Prevent creation if exists |
O_APPEND | Append to end |
O_TRUNC | Truncate to zero length |
Example:
c
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> int main(void) { int fd = open("foo.txt", O_RDONLY | O_CREAT, 0644); if (fd == -1) { printf("Error Number: %d\n", errno); perror("Program"); return 1; } printf("File descriptor: %d\n", fd); close(fd); return 0; } Expected output:
text
File descriptor: 3
3. close() — Close a File
Closes the file associated with the file descriptor.
Syntax:
c
int close(int fd);
Return value:
0on success-1on error
Example:
c
#include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(void) { int fd = open("foo.txt", O_RDONLY); if (fd < 0) { perror("Error opening file"); return 1; } printf("Opened fd = %d\n", fd); if (close(fd) < 0) { perror("Error closing file"); return 1; } printf("Closed fd.\n"); return 0; } Expected output:
text
Opened fd = 3 Closed fd.
4. read() — Read from a File
Reads bytes from a file into a buffer.
Syntax:
c
ssize_t read(int fd, void *buf, size_t count);
Parameters:
fd: File descriptorbuf: Buffer to store read datacount: Number of bytes to read
Return value:
- Number of bytes read on success
0on end of file-1on error
Example:
c
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { int fd = open("foo.txt", O_RDONLY); if (fd < 0) { perror("Error opening file"); return 1; } char *buffer = calloc(100, sizeof(char)); if (buffer == NULL) { perror("Memory allocation failed"); close(fd); return 1; } ssize_t bytesRead = read(fd, buffer, 10); if (bytesRead < 0) { perror("Error reading file"); free(buffer); close(fd); return 1; } buffer[bytesRead] = '\0'; printf("Read %zd bytes: %s\n", bytesRead, buffer); free(buffer); close(fd); return 0; } Expected output (depends on file content):
text
Read 10 bytes: Hello Worl
5. write() — Write to a File
Writes bytes from a buffer to a file.
Syntax:
c
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
Return value:
- Number of bytes written on success
-1on error
Example:
c
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(void) { int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("Error opening file"); return 1; } const char *message = "Hello, System Calls!\n"; ssize_t bytesWritten = write(fd, message, strlen(message)); if (bytesWritten < 0) { perror("Error writing to file"); close(fd); return 1; } printf("Wrote %zd bytes.\n", bytesWritten); close(fd); return 0; } Expected output:
text
Wrote 23 bytes.
Full Practical Code Example with Explanation
This program demonstrates all five I/O system calls in a realistic scenario: a file copy utility.
Program: System Call File Copier
c
/** * syscall_file_copy.c * Demonstrates I/O system calls: creat, open, close, read, write. * Implements a file copy utility using low-level system calls. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #define BUFFER_SIZE 4096 #define MAX_FILENAME_LEN 256 /* Function prototypes */ int copyFile(const char *source, const char *destination); int demonstrateCreat(void); int demonstrateOpenClose(void); int demonstrateReadWrite(void); void printFileContent(const char *filename); int main(void) { int choice; char buffer[MAX_FILENAME_LEN]; char source[MAX_FILENAME_LEN]; char destination[MAX_FILENAME_LEN]; printf("=== System Call I/O Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate creat()\n"); printf("2. Demonstrate open() and close()\n"); printf("3. Demonstrate read() and write()\n"); printf("4. Copy File (All System Calls)\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateCreat(); break; case 2: demonstrateOpenClose(); break; case 3: demonstrateReadWrite(); break; case 4: printf("Enter source file: "); fflush(stdout); if (fgets(source, sizeof(source), stdin) != NULL) { source[strcspn(source, "\n")] = '\0'; printf("Enter destination file: "); fflush(stdout); if (fgets(destination, sizeof(destination), stdin) != NULL) { destination[strcspn(destination, "\n")] = '\0'; if (copyFile(source, destination) == 0) { printf("File copied successfully.\n"); printf("Destination content:\n"); printFileContent(destination); } } } break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Demonstrates creat() */ int demonstrateCreat(void) { printf("\n=== creat() System Call ===\n"); const char *filename = "created_file.txt"; int fd = creat(filename, 0644); if (fd == -1) { perror("Error creating file"); return -1; } printf("File '%s' created with descriptor %d.\n", filename, fd); close(fd); return 0; } /* Demonstrates open() and close() */ int demonstrateOpenClose(void) { printf("\n=== open() and close() System Calls ===\n"); const char *filename = "test_open.txt"; /* Create a file first */ int createFd = creat(filename, 0644); if (createFd == -1) { perror("Error creating file"); return -1; } close(createFd); /* Open with different flags */ int fd1 = open(filename, O_RDONLY); if (fd1 == -1) { perror("Error opening read-only"); return -1; } printf("Opened read-only: fd = %d\n", fd1); int fd2 = open(filename, O_WRONLY | O_APPEND); if (fd2 == -1) { perror("Error opening write-append"); close(fd1); return -1; } printf("Opened write-append: fd = %d\n", fd2); /* Close both */ if (close(fd1) == -1) { perror("Error closing fd1"); } if (close(fd2) == -1) { perror("Error closing fd2"); } printf("Both file descriptors closed.\n"); return 0; } /* Demonstrates read() and write() */ int demonstrateReadWrite(void) { printf("\n=== read() and write() System Calls ===\n"); const char *filename = "test_rw.txt"; /* Create and write to file */ int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("Error opening for writing"); return -1; } const char *message = "Hello, System Calls!\n"; ssize_t bytesWritten = write(fd, message, strlen(message)); if (bytesWritten < 0) { perror("Error writing"); close(fd); return -1; } printf("Wrote %zd bytes to '%s'.\n", bytesWritten, filename); close(fd); /* Read from file */ fd = open(filename, O_RDONLY); if (fd == -1) { perror("Error opening for reading"); return -1; } char buffer[100]; ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1); if (bytesRead < 0) { perror("Error reading"); close(fd); return -1; } buffer[bytesRead] = '\0'; printf("Read %zd bytes: %s", bytesRead, buffer); close(fd); return 0; } /* Copies a file using system calls */ int copyFile(const char *source, const char *destination) { int srcFd = open(source, O_RDONLY); if (srcFd == -1) { fprintf(stderr, "Error opening source '%s': %s\n", source, strerror(errno)); return -1; } int dstFd = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dstFd == -1) { fprintf(stderr, "Error opening destination '%s': %s\n", destination, strerror(errno)); close(srcFd); return -1; } char buffer[BUFFER_SIZE]; ssize_t bytesRead, bytesWritten; while ((bytesRead = read(srcFd, buffer, sizeof(buffer))) > 0) { bytesWritten = write(dstFd, buffer, bytesRead); if (bytesWritten != bytesRead) { fprintf(stderr, "Error writing: %s\n", strerror(errno)); close(srcFd); close(dstFd); return -1; } } if (bytesRead == -1) { fprintf(stderr, "Error reading: %s\n", strerror(errno)); close(srcFd); close(dstFd); return -1; } if (close(srcFd) == -1) { fprintf(stderr, "Error closing source: %s\n", strerror(errno)); } if (close(dstFd) == -1) { fprintf(stderr, "Error closing destination: %s\n", strerror(errno)); } return 0; } /* Prints file content using system calls */ void printFileContent(const char *filename) { int fd = open(filename, O_RDONLY); if (fd == -1) { perror("Error opening file"); return; } char buffer[BUFFER_SIZE]; ssize_t bytesRead; while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) { write(STDOUT_FILENO, buffer, bytesRead); } close(fd); } Explanation of System Call Usage
creat(): Creates new files with permissions.open(): Opens files with various flags.close(): Releases file descriptors.read(): Reads data from files.write(): Writes data to files.- Error handling: Checks return values, uses
perrorandstrerror.
Expected Output (sample run)
text
=== System Call I/O Demo === --- Menu --- 1. Demonstrate creat() 2. Demonstrate open() and close() 3. Demonstrate read() and write() 4. Copy File (All System Calls) 5. Exit Enter choice: 1 === creat() System Call === File 'created_file.txt' created with descriptor 3. --- Menu --- ... Enter choice: 3 === read() and write() System Calls === Wrote 23 bytes to 'test_rw.txt'. Read 23 bytes: Hello, System Calls! --- Menu --- ... Enter choice: 4 Enter source file: test_rw.txt Enter destination file: copy.txt File copied successfully. Destination content: Hello, System Calls!
Common Pitfalls, Edge Cases & Memory Considerations
1. Not Checking Return Values
System calls can fail; always check return values.
c
int fd = open("file.txt", O_RDONLY); read(fd, buffer, 100); // BUG: fd may be -1 Solution: Always check if (fd == -1) after open.
2. Forgetting to Close File Descriptors
Leaving file descriptors open leaks resources.
c
int fd = open("file.txt", O_RDONLY); /* Missing close(fd); */ Solution: Always close file descriptors.
3. Buffer Overflow in read
Reading into a buffer without checking size.
c
char buffer[10]; read(fd, buffer, 100); // BUG: overflow
Solution: Ensure buffer is large enough: read(fd, buffer, sizeof(buffer)).
4. Ignoring Partial Reads/Writes
read may return fewer bytes than requested; write may write partially.
Solution: Loop until all data is read/written, or handle partial operations.
5. Using O_CREAT Without Mode
open with O_CREAT requires a third argument (mode).
c
open("file.txt", O_RDONLY | O_CREAT); // Undefined behavior Solution: Always provide mode with O_CREAT:
c
open("file.txt", O_RDONLY | O_CREAT, 0644); 6. Not Handling EINTR
System calls can be interrupted by signals.
c
ssize_t result = read(fd, buffer, size); if (result == -1 && errno == EINTR) { /* Retry */ } Solution: Retry on EINTR or use sigaction with SA_RESTART.
7. File Descriptor Reuse
After closing a file descriptor, the next open may reuse the same number.
Solution: Do not use closed file descriptors.
8. Wrong File Permissions
Using incorrect permissions in creat/open may cause access issues.
Solution: Use appropriate permissions (e.g., 0644, 0600).
9. read on Write-Only File
Reading from a write-only file descriptor fails.
Solution: Open with correct mode (O_RDONLY for reading).
10. Memory Considerations
readandwriteuse buffers; ensure buffers are large enough.- Large reads/writes may be more efficient.
- File descriptors consume kernel resources; close them.
- System calls are slower than standard I/O (buffered).
Summary & Best Practices
System calls provide direct access to the operating system’s I/O capabilities. Understanding creat, open, close, read, and write is essential for low-level file operations in C.
Key Takeaways
- File descriptors identify open files.
- Standard descriptors: 0 (stdin), 1 (stdout), 2 (stderr).
creat()creates files.open()opens files with flags.close()releases file descriptors.read()reads data;write()writes data.- Always check return values and handle errors.
Best Practices
- Always check return values of system calls.
- Close file descriptors after use.
- Use appropriate flags for open operations.
- Provide mode when using
O_CREAT. - Handle partial reads/writes in loops.
- Use adequately sized buffers to prevent overflow.
- Handle
EINTRby retrying interrupted calls. - Use
perrororstrerrorfor error reporting. - Check for
NULLbuffers before use. - Test with edge cases: empty files, large files, missing files.
By mastering I/O system calls, you’ll write C programs that interact directly with the operating system, enabling efficient and low-level file operations.












