CoreTechED

Loading

Learn C I/O system calls: creat, open, close, read, and write. Master file descriptors and low-level file operations with examples.

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 DescriptorNameDescription
0stdinStandard input (keyboard)
1stdoutStandard output (screen)
2stderrStandard error (screen)

I/O System Calls Overview

System CallFunctionDescription
creatcreat()Create a new empty file
openopen()Open a file for reading/writing
closeclose()Close a file descriptor
readread()Read data from a file
writewrite()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 create
  • mode: Permissions (e.g., 0644 for rw-r–r–)

Return value:

  • File descriptor on success (usually 3)
  • -1 on 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:

FlagDescription
O_RDONLYRead-only
O_WRONLYWrite-only
O_RDWRRead and write
O_CREATCreate if doesn’t exist
O_EXCLPrevent creation if exists
O_APPENDAppend to end
O_TRUNCTruncate 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:

  • 0 on success
  • -1 on 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 descriptor
  • buf: Buffer to store read data
  • count: Number of bytes to read

Return value:

  • Number of bytes read on success
  • 0 on end of file
  • -1 on 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 descriptor
  • buf: Buffer containing data to write
  • count: Number of bytes to write

Return value:

  • Number of bytes written on success
  • -1 on 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

  1. creat(): Creates new files with permissions.
  2. open(): Opens files with various flags.
  3. close(): Releases file descriptors.
  4. read(): Reads data from files.
  5. write(): Writes data to files.
  6. Error handling: Checks return values, uses perror and strerror.

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

  • read and write use 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

  1. Always check return values of system calls.
  2. Close file descriptors after use.
  3. Use appropriate flags for open operations.
  4. Provide mode when using O_CREAT.
  5. Handle partial reads/writes in loops.
  6. Use adequately sized buffers to prevent overflow.
  7. Handle EINTR by retrying interrupted calls.
  8. Use perror or strerror for error reporting.
  9. Check for NULL buffers before use.
  10. 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.