ftruncate系统调用及示例

ftruncate – 通过文件描述符截断文件

函数介绍

ftruncate系统调用用于将已打开的文件截断到指定长度。与truncate不同,ftruncate通过文件描述符而不是文件路径来操作文件,这在某些情况下更加高效和安全。

函数原型

#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);

功能

通过文件描述符将文件截断或扩展到指定长度。

ftruncate系统调用及示例-CSDN博客

参数

  • int fd: 文件描述符
  • off_t length: 目标文件长度(字节)

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效或不是为写入而打开
    • EFBIG: 长度参数过大
    • EINVAL: 文件描述符不支持截断
    • EIO: I/O错误
    • EPERM: 操作不被允许
    • EROFS: 文件在只读文件系统上

相似函数

  • truncate(): 通过文件路径截断文件
  • open()配合O_TRUNC标志

示例代码

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int main() {
    int fd;
    struct stat stat_buf;
    
    printf("=== Ftruncate函数示例 ===\n");
    
    // 示例1: 基本的文件截断操作
    printf("\n示例1: 基本的文件截断操作\n");
    
    // 创建测试文件并写入数据
    fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    
    // 写入测试数据
    const char *original_data = 
        "Line 1: This is the first line of data.\n"
        "Line 2: This is the second line of data.\n"
        "Line 3: This is the third line of data.\n"
        "Line 4: This is the fourth line of data.\n"
        "Line 5: This is the fifth line of data.\n";
    
    if (write(fd, original_data, strlen(original_data)) == -1) {
        perror("写入原始数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("写入原始数据,长度: %ld 字节\n", (long)strlen(original_data));
    
    // 获取原始文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取文件状态失败");
    } else {
        printf("原始文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 使用ftruncate截断文件到80字节
    printf("\n使用ftruncate将文件截断到80字节...\n");
    if (ftruncate(fd, 80) == -1) {
        perror("截断文件失败");
    } else {
        printf("文件截断成功\n");
    }
    
    // 检查截断后的文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取截断后文件状态失败");
    } else {
        printf("截断后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 读取截断后的数据验证
    lseek(fd, 0, SEEK_SET);  // 重置文件位置指针
    char buffer[200];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("截断后文件内容:\n%s", buffer);
    }
    
    // 示例2: 扩展文件
    printf("\n示例2: 扩展文件\n");
    
    // 使用ftruncate将文件扩展到150字节
    printf("使用ftruncate将文件扩展到150字节...\n");
    if (ftruncate(fd, 150) == -1) {
        perror("扩展文件失败");
    } else {
        printf("文件扩展成功\n");
    }
    
    // 检查扩展后的文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取扩展后文件状态失败");
    } else {
        printf("扩展后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 读取扩展后的数据
    lseek(fd, 0, SEEK_SET);  // 重置文件位置指针
    bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        printf("扩展后文件前%d字节内容:\n", (int)bytes_read);
        // 显示前100个字符
        for (int i = 0; i < bytes_read && i < 100; i++) {
            if (buffer[i] >= 32 && buffer[i] <= 126) {
                putchar(buffer[i]);
            } else if (buffer[i] == '\n') {
                putchar('\n');
            } else {
                printf("[%02x]", (unsigned char)buffer[i]);
            }
        }
        printf("\n");
    }
    
    // 示例3: 截断到0字节(清空文件)
    printf("\n示例3: 截断到0字节(清空文件)\n");
    
    printf("将文件截断到0字节...\n");
    if (ftruncate(fd, 0) == -1) {
        perror("清空文件失败");
    } else {
        printf("文件清空成功\n");
    }
    
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取清空后文件状态失败");
    } else {
        printf("清空后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 示例4: 与truncate的对比
    printf("\n示例4: ftruncate与truncate对比\n");
    
    // 重新写入数据
    const char *compare_data = "Data for comparison test: ftruncate vs truncate";
    if (write(fd, compare_data, strlen(compare_data)) == -1) {
        perror("写入对比数据失败");
    } else {
        printf("写入对比数据: %s\n", compare_data);
    }
    
    // 使用ftruncate截断
    clock_t ftruncate_start = clock();
    if (ftruncate(fd, 20) == -1) {
        perror("ftruncate失败");
    } else {
        clock_t ftruncate_time = clock() - ftruncate_start;
        printf("ftruncate成功,耗时: %f 秒\n", 
               ((double)ftruncate_time) / CLOCKS_PER_SEC);
    }
    
    // 重新创建文件测试truncate
    close(fd);
    unlink("test_ftruncate.txt");
    
    fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd != -1) {
        if (write(fd, compare_data, strlen(compare_data)) == -1) {
            perror("重新写入数据失败");
        }
        close(fd);
        
        // 使用truncate截断
        clock_t truncate_start = clock();
        if (truncate("test_ftruncate.txt", 20) == -1) {
            perror("truncate失败");
        } else {
            clock_t truncate_time = clock() - truncate_start;
            printf("truncate成功,耗时: %f 秒\n", 
                   ((double)truncate_time) / CLOCKS_PER_SEC);
        }
    }
    
    // 重新打开文件继续演示
    fd = open("test_ftruncate.txt", O_RDWR);
    if (fd == -1) {
        perror("重新打开文件失败");
        exit(EXIT_FAILURE);
    }
    
    // 示例5: 不同长度的截断演示
    printf("\n示例5: 不同长度的截断演示\n");
    
    // 重新写入测试数据
    const char *test_data = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    lseek(fd, 0, SEEK_SET);
    if (ftruncate(fd, 0) == -1) {  // 先清空
        perror("清空文件失败");
    }
    if (write(fd, test_data, strlen(test_data)) == -1) {
        perror("写入测试数据失败");
    }
    printf("重新写入测试数据: %s\n", test_data);
    
    // 演示不同长度的截断
    off_t lengths[] = {10, 25, 40, 70};
    for (int i = 0; i < 4; i++) {
        printf("截断到%ld字节: ", lengths[i]);
        if (ftruncate(fd, lengths[i]) == -1) {
            printf("失败 - %s\n", strerror(errno));
        } else {
            if (fstat(fd, &stat_buf) == -1) {
                printf("获取状态失败\n");
            } else {
                printf("成功,新大小: %ld字节\n", stat_buf.st_size);
                
                // 读取并显示内容
                lseek(fd, 0, SEEK_SET);
                char read_buffer[100];
                ssize_t bytes_read = read(fd, read_buffer, stat_buf.st_size);
                if (bytes_read > 0) {
                    read_buffer[bytes_read] = '\0';
                    printf("  内容: %s\n", read_buffer);
                }
            }
        }
    }
    
    // 示例6: 错误处理演示
    printf("\n示例6: 错误处理演示\n");
    
    // 尝试对无效文件描述符操作
    if (ftruncate(999, 100) == -1) {
        printf("对无效文件描述符操作: %s\n", strerror(errno));
    }
    
    // 尝试对只读文件描述符操作
    int readonly_fd = open("test_ftruncate.txt", O_RDONLY);
    if (readonly_fd != -1) {
        if (ftruncate(readonly_fd, 50) == -1) {
            printf("对只读文件描述符操作: %s\n", strerror(errno));
        }
        close(readonly_fd);
    }
    
    // 尝试使用负数长度
    if (ftruncate(fd, -10) == -1) {
        printf("使用负数长度: %s\n", strerror(errno));
    }
    
    // 尝试对管道或套接字操作(需要特殊文件描述符)
    int pipefd[2];
    if (pipe(pipefd) == 0) {
        if (ftruncate(pipefd[0], 100) == -1) {
            printf("对管道操作: %s\n", strerror(errno));
        }
        close(pipefd[0]);
        close(pipefd[1]);
    }
    
    // 示例7: 实际应用场景
    printf("\n示例7: 实际应用场景\n");
    
    // 场景1: 编辑器临时文件管理
    printf("场景1: 编辑器临时文件管理\n");
    int editor_fd = open("editor_temp.txt", O_CREAT | O_RDWR, 0644);
    if (editor_fd != -1) {
        // 模拟编辑器写入大量临时数据
        char temp_data[100];
        for (int i = 0; i < 50; i++) {
            sprintf(temp_data, "Temporary line %d for editor\n", i);
            write(editor_fd, temp_data, strlen(temp_data));
        }
        
        if (fstat(editor_fd, &stat_buf) == 0) {
            printf("临时文件大小: %ld 字节\n", stat_buf.st_size);
            
            // 当用户撤销操作时,可能需要截断文件
            off_t undo_position = stat_buf.st_size - 200;  // 模拟撤销到某个位置
            if (undo_position > 0) {
                printf("撤销操作,截断到位置: %ld\n", undo_position);
                if (ftruncate(editor_fd, undo_position) == -1) {
                    perror("撤销操作失败");
                } else {
                    printf("撤销操作成功\n");
                }
            }
        }
        
        close(editor_fd);
        unlink("editor_temp.txt");
    }
    
    // 场景2: 数据库文件维护
    printf("场景2: 数据库文件维护\n");
    int db_fd = open("database.dat", O_CREAT | O_RDWR, 0644);
    if (db_fd != -1) {
        // 模拟数据库文件增长
        char db_record[50];
        for (int i = 0; i < 100; i++) {
            sprintf(db_record, "Database record %d with some data\n", i);
            write(db_fd, db_record, strlen(db_record));
        }
        
        if (fstat(db_fd, &stat_buf) == 0) {
            printf("数据库文件原始大小: %ld 字节\n", stat_buf.st_size);
            
            // 模拟数据库压缩操作
            off_t compressed_size = stat_buf.st_size * 0.8;  // 压缩到80%
            printf("压缩数据库文件到: %ld 字节\n", compressed_size);
            if (ftruncate(db_fd, compressed_size) == -1) {
                perror("数据库压缩失败");
            } else {
                printf("数据库压缩成功\n");
            }
        }
        
        close(db_fd);
        unlink("database.dat");
    }
    
    // 场景3: 日志文件循环
    printf("场景3: 日志文件循环\n");
    int log_fd = open("circular_log.txt", O_CREAT | O_RDWR, 0644);
    if (log_fd != -1) {
        // 写入日志数据直到达到限制
        const char *log_entry = "LOG: System operation completed successfully\n";
        for (int i = 0; i < 20; i++) {
            char entry[100];
            sprintf(entry, "Entry %d: %s", i, log_entry);
            write(log_fd, entry, strlen(entry));
        }
        
        if (fstat(log_fd, &stat_buf) == 0) {
            printf("日志文件当前大小: %ld 字节\n", stat_buf.st_size);
            
            // 如果超过限制(比如1KB),保留最后512字节
            if (stat_buf.st_size > 1024) {
                printf("日志文件过大,保留最后512字节\n");
                
                // 读取最后512字节
                char last_data[512];
                off_t start_pos = stat_buf.st_size - 512;
                if (start_pos > 0) {
                    lseek(log_fd, start_pos, SEEK_SET);
                    ssize_t bytes_read = read(log_fd, last_data, 512);
                    if (bytes_read > 0) {
                        // 截断文件到0,然后写入保留的数据
                        ftruncate(log_fd, 0);
                        lseek(log_fd, 0, SEEK_SET);
                        write(log_fd, last_data, bytes_read);
                        printf("日志循环完成,新大小: %ld 字节\n", (long)bytes_read);
                    }
                }
            }
        }
        
        close(log_fd);
        unlink("circular_log.txt");
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    if (unlink("test_ftruncate.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注