ftruncate系统调用及示例

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

函数介绍

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

函数原型

1
2
3
4
5
#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);

功能

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

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

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

参数

  • int fd: 文件描述符

  • off_t length: 目标文件长度(字节)

返回值

  • 成功时返回0

失败时返回-1,并设置errno:

  • EBADF: 文件描述符无效或不是为写入而打开

  • EFBIG: 长度参数过大

  • EINVAL: 文件描述符不支持截断

  • EIO: I/O错误

  • EPERM: 操作不被允许

  • EROFS: 文件在只读文件系统上

相似函数

  • truncate(): 通过文件路径截断文件

  • open()配合O_TRUNC标志

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#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&#91;200];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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&#91;i] >= 32 && buffer&#91;i] <= 126) {
putchar(buffer&#91;i]);
} else if (buffer&#91;i] == '\n') {
putchar('\n');
} else {
printf("&#91;%02x]", (unsigned char)buffer&#91;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&#91;] = {10, 25, 40, 70};
for (int i = 0; i < 4; i++) {
printf("截断到%ld字节: ", lengths&#91;i]);
if (ftruncate(fd, lengths&#91;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&#91;100];
ssize_t bytes_read = read(fd, read_buffer, stat_buf.st_size);
if (bytes_read > 0) {
read_buffer&#91;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&#91;2];
if (pipe(pipefd) == 0) {
if (ftruncate(pipefd&#91;0], 100) == -1) {
printf("对管道操作: %s\n", strerror(errno));
}
close(pipefd&#91;0]);
close(pipefd&#91;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&#91;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&#91;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&#91;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&#91;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;
}
data-ad-format="auto" data-full-width-responsive="true">