OSDN Git Service

Add switches for compressor
[android-x86/system-extras.git] / f2fs_utils / f2fs_sparseblock.c
1 #define _LARGEFILE64_SOURCE
2
3 #define LOG_TAG "f2fs_sparseblock"
4
5
6 #include <cutils/log.h>
7 #include <fcntl.h>
8 #include <f2fs_fs.h>
9 #include <linux/types.h>
10 #include <sys/stat.h>
11 #include "f2fs_sparseblock.h"
12
13
14 #define D_DISP_u32(ptr, member)           \
15   do {                \
16     SLOGD("%-30s" "\t\t[0x%#08x : %u]\n",    \
17       #member, le32_to_cpu((ptr)->member), le32_to_cpu((ptr)->member) );  \
18   } while (0);
19
20 #define D_DISP_u64(ptr, member)           \
21   do {                \
22     SLOGD("%-30s" "\t\t[0x%#016llx : %llu]\n",    \
23       #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member) );  \
24   } while (0);
25
26 #define segno_in_journal(sum, i)    (sum->sit_j.entries[i].segno)
27
28 #define sit_in_journal(sum, i)      (sum->sit_j.entries[i].se)
29
30 static void dbg_print_raw_sb_info(struct f2fs_super_block *sb)
31 {
32     SLOGD("\n");
33     SLOGD("+--------------------------------------------------------+\n");
34     SLOGD("| Super block                                            |\n");
35     SLOGD("+--------------------------------------------------------+\n");
36
37     D_DISP_u32(sb, magic);
38     D_DISP_u32(sb, major_ver);
39     D_DISP_u32(sb, minor_ver);
40     D_DISP_u32(sb, log_sectorsize);
41     D_DISP_u32(sb, log_sectors_per_block);
42
43     D_DISP_u32(sb, log_blocksize);
44     D_DISP_u32(sb, log_blocks_per_seg);
45     D_DISP_u32(sb, segs_per_sec);
46     D_DISP_u32(sb, secs_per_zone);
47     D_DISP_u32(sb, checksum_offset);
48     D_DISP_u64(sb, block_count);
49
50     D_DISP_u32(sb, section_count);
51     D_DISP_u32(sb, segment_count);
52     D_DISP_u32(sb, segment_count_ckpt);
53     D_DISP_u32(sb, segment_count_sit);
54     D_DISP_u32(sb, segment_count_nat);
55
56     D_DISP_u32(sb, segment_count_ssa);
57     D_DISP_u32(sb, segment_count_main);
58     D_DISP_u32(sb, segment0_blkaddr);
59
60     D_DISP_u32(sb, cp_blkaddr);
61     D_DISP_u32(sb, sit_blkaddr);
62     D_DISP_u32(sb, nat_blkaddr);
63     D_DISP_u32(sb, ssa_blkaddr);
64     D_DISP_u32(sb, main_blkaddr);
65
66     D_DISP_u32(sb, root_ino);
67     D_DISP_u32(sb, node_ino);
68     D_DISP_u32(sb, meta_ino);
69     D_DISP_u32(sb, cp_payload);
70     SLOGD("\n");
71 }
72 static void dbg_print_raw_ckpt_struct(struct f2fs_checkpoint *cp)
73 {
74     SLOGD("\n");
75     SLOGD("+--------------------------------------------------------+\n");
76     SLOGD("| Checkpoint                                             |\n");
77     SLOGD("+--------------------------------------------------------+\n");
78
79     D_DISP_u64(cp, checkpoint_ver);
80     D_DISP_u64(cp, user_block_count);
81     D_DISP_u64(cp, valid_block_count);
82     D_DISP_u32(cp, rsvd_segment_count);
83     D_DISP_u32(cp, overprov_segment_count);
84     D_DISP_u32(cp, free_segment_count);
85
86     D_DISP_u32(cp, alloc_type[CURSEG_HOT_NODE]);
87     D_DISP_u32(cp, alloc_type[CURSEG_WARM_NODE]);
88     D_DISP_u32(cp, alloc_type[CURSEG_COLD_NODE]);
89     D_DISP_u32(cp, cur_node_segno[0]);
90     D_DISP_u32(cp, cur_node_segno[1]);
91     D_DISP_u32(cp, cur_node_segno[2]);
92
93     D_DISP_u32(cp, cur_node_blkoff[0]);
94     D_DISP_u32(cp, cur_node_blkoff[1]);
95     D_DISP_u32(cp, cur_node_blkoff[2]);
96
97
98     D_DISP_u32(cp, alloc_type[CURSEG_HOT_DATA]);
99     D_DISP_u32(cp, alloc_type[CURSEG_WARM_DATA]);
100     D_DISP_u32(cp, alloc_type[CURSEG_COLD_DATA]);
101     D_DISP_u32(cp, cur_data_segno[0]);
102     D_DISP_u32(cp, cur_data_segno[1]);
103     D_DISP_u32(cp, cur_data_segno[2]);
104
105     D_DISP_u32(cp, cur_data_blkoff[0]);
106     D_DISP_u32(cp, cur_data_blkoff[1]);
107     D_DISP_u32(cp, cur_data_blkoff[2]);
108
109     D_DISP_u32(cp, ckpt_flags);
110     D_DISP_u32(cp, cp_pack_total_block_count);
111     D_DISP_u32(cp, cp_pack_start_sum);
112     D_DISP_u32(cp, valid_node_count);
113     D_DISP_u32(cp, valid_inode_count);
114     D_DISP_u32(cp, next_free_nid);
115     D_DISP_u32(cp, sit_ver_bitmap_bytesize);
116     D_DISP_u32(cp, nat_ver_bitmap_bytesize);
117     D_DISP_u32(cp, checksum_offset);
118     D_DISP_u64(cp, elapsed_time);
119
120     D_DISP_u32(cp, sit_nat_version_bitmap[0]);
121     SLOGD("\n\n");
122 }
123
124 static void dbg_print_info_struct(struct f2fs_info *info)
125 {
126     SLOGD("\n");
127     SLOGD("+--------------------------------------------------------+\n");
128     SLOGD("| F2FS_INFO                                              |\n");
129     SLOGD("+--------------------------------------------------------+\n");
130     SLOGD("blocks_per_segment: %"PRIu64, info->blocks_per_segment);
131     SLOGD("block_size: %d", info->block_size);
132     SLOGD("sit_bmp loc: %p", info->sit_bmp);
133     SLOGD("sit_bmp_size: %d", info->sit_bmp_size);
134     SLOGD("blocks_per_sit: %"PRIu64, info->blocks_per_sit);
135     SLOGD("sit_blocks loc: %p", info->sit_blocks);
136     SLOGD("sit_sums loc: %p", info->sit_sums);
137     SLOGD("sit_sums num: %d", le16_to_cpu(info->sit_sums->n_sits));
138     unsigned int i;
139     for(i = 0; i < (le16_to_cpu(info->sit_sums->n_sits)); i++) {
140         SLOGD("entry %d in journal entries is for segment %d",i, le32_to_cpu(segno_in_journal(info->sit_sums, i)));
141     }
142
143     SLOGD("cp_blkaddr: %"PRIu64, info->cp_blkaddr);
144     SLOGD("cp_valid_cp_blkaddr: %"PRIu64, info->cp_valid_cp_blkaddr);
145     SLOGD("sit_blkaddr: %"PRIu64, info->sit_blkaddr);
146     SLOGD("nat_blkaddr: %"PRIu64, info->nat_blkaddr);
147     SLOGD("ssa_blkaddr: %"PRIu64, info->ssa_blkaddr);
148     SLOGD("main_blkaddr: %"PRIu64, info->main_blkaddr);
149     SLOGD("total_user_used: %"PRIu64, info->total_user_used);
150     SLOGD("total_blocks: %"PRIu64, info->total_blocks);
151     SLOGD("\n\n");
152 }
153
154
155 /* read blocks */
156 static int read_structure(int fd, unsigned long long start, void *buf, ssize_t len)
157 {
158     off64_t ret;
159
160     ret = lseek64(fd, start, SEEK_SET);
161     if (ret < 0) {
162         SLOGE("failed to seek\n");
163         return ret;
164     }
165
166     ret = read(fd, buf, len);
167     if (ret < 0) {
168         SLOGE("failed to read\n");
169         return ret;
170     }
171     if (ret != len) {
172         SLOGE("failed to read all\n");
173         return -1;
174     }
175     return 0;
176 }
177
178 static int read_structure_blk(int fd, unsigned long long start_blk, void *buf, size_t len)
179 {
180     return read_structure(fd, F2FS_BLKSIZE*start_blk, buf, F2FS_BLKSIZE * len);
181 }
182
183 static int read_f2fs_sb(int fd, struct f2fs_super_block *sb)
184 {
185     int rc;
186     rc = read_structure(fd, F2FS_SUPER_OFFSET, sb, sizeof(*sb));
187     if (le32_to_cpu(sb->magic) != F2FS_SUPER_MAGIC) {
188         SLOGE("Not a valid F2FS super block. Magic:%#08x != %#08x",
189                                   le32_to_cpu(sb->magic), F2FS_SUPER_MAGIC);
190         return -1;
191     }
192     return 0;
193 }
194
195 unsigned int get_f2fs_filesystem_size_sec(char *dev)
196 {
197     int fd;
198     if ((fd = open(dev, O_RDONLY)) < 0) {
199         SLOGE("Cannot open device to get filesystem size ");
200         return 0;
201     }
202     struct f2fs_super_block sb;
203     if(read_f2fs_sb(fd, &sb))
204         return 0;
205     return (unsigned int)(le64_to_cpu(sb.block_count)*F2FS_BLKSIZE/DEFAULT_SECTOR_SIZE);
206 }
207
208 static struct f2fs_checkpoint *validate_checkpoint(block_t cp_addr,
209                                                    unsigned long long *version, int fd)
210 {
211     unsigned char *cp_block_1, *cp_block_2;
212     struct f2fs_checkpoint *cp_block, *cp_ret;
213     u64 cp1_version = 0, cp2_version = 0;
214
215     cp_block_1 = malloc(F2FS_BLKSIZE);
216     if (!cp_block_1)
217         return NULL;
218
219     /* Read the 1st cp block in this CP pack */
220     if (read_structure_blk(fd, cp_addr, cp_block_1, 1))
221         goto invalid_cp1;
222
223     /* get the version number */
224     cp_block = (struct f2fs_checkpoint *)cp_block_1;
225
226     cp1_version = le64_to_cpu(cp_block->checkpoint_ver);
227
228     cp_block_2 = malloc(F2FS_BLKSIZE);
229     if (!cp_block_2) {
230         goto invalid_cp1;
231     }
232     /* Read the 2nd cp block in this CP pack */
233     cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1;
234     if (read_structure_blk(fd, cp_addr, cp_block_2, 1)) {
235         goto invalid_cp2;
236     }
237
238     cp_block = (struct f2fs_checkpoint *)cp_block_2;
239
240     cp2_version = le64_to_cpu(cp_block->checkpoint_ver);
241
242     if (cp2_version == cp1_version) {
243         *version = cp2_version;
244         free(cp_block_2);
245         return (struct f2fs_checkpoint *)cp_block_1;
246     }
247
248     /* There must be something wrong with this checkpoint */
249 invalid_cp2:
250     free(cp_block_2);
251 invalid_cp1:
252     free(cp_block_1);
253     return NULL;
254 }
255
256 int get_valid_checkpoint_info(int fd, struct f2fs_super_block *sb, struct f2fs_checkpoint **cp,  struct f2fs_info *info)
257 {
258     struct f2fs_checkpoint *cp_block;
259
260     struct f2fs_checkpoint *cp1, *cp2, *cur_cp;
261     int cur_cp_no;
262     unsigned long blk_size;// = 1<<le32_to_cpu(info->sb->log_blocksize);
263     unsigned long long cp1_version = 0, cp2_version = 0;
264     unsigned long long cp1_start_blk_no;
265     unsigned long long cp2_start_blk_no;
266     u32 bmp_size;
267
268     blk_size = 1U<<le32_to_cpu(sb->log_blocksize);
269
270     /*
271      * Find valid cp by reading both packs and finding most recent one.
272      */
273     cp1_start_blk_no = le32_to_cpu(sb->cp_blkaddr);
274     cp1 = validate_checkpoint(cp1_start_blk_no, &cp1_version, fd);
275
276     /* The second checkpoint pack should start at the next segment */
277     cp2_start_blk_no = cp1_start_blk_no + (1 << le32_to_cpu(sb->log_blocks_per_seg));
278     cp2 = validate_checkpoint(cp2_start_blk_no, &cp2_version, fd);
279
280     if (cp1 && cp2) {
281         if (ver_after(cp2_version, cp1_version)) {
282             cur_cp = cp2;
283             info->cp_valid_cp_blkaddr = cp2_start_blk_no;
284             free(cp1);
285         } else {
286             cur_cp = cp1;
287             info->cp_valid_cp_blkaddr = cp1_start_blk_no;
288             free(cp2);
289         }
290     } else if (cp1) {
291         cur_cp = cp1;
292         info->cp_valid_cp_blkaddr = cp1_start_blk_no;
293     } else if (cp2) {
294         cur_cp = cp2;
295         info->cp_valid_cp_blkaddr = cp2_start_blk_no;
296     } else {
297         goto fail_no_cp;
298     }
299
300     *cp = cur_cp;
301
302     return 0;
303
304 fail_no_cp:
305     SLOGE("Valid Checkpoint not found!!");
306     return -EINVAL;
307 }
308
309 static int gather_sit_info(int fd, struct f2fs_info *info)
310 {
311     u64 num_segments = (info->total_blocks - info->main_blkaddr
312             + info->blocks_per_segment - 1) / info->blocks_per_segment;
313     u64 num_sit_blocks = (num_segments + SIT_ENTRY_PER_BLOCK - 1) / SIT_ENTRY_PER_BLOCK;
314     u64 sit_block;
315
316     info->sit_blocks = malloc(num_sit_blocks * sizeof(struct f2fs_sit_block));
317     if (!info->sit_blocks)
318         return -1;
319
320     for(sit_block = 0; sit_block<num_sit_blocks; sit_block++) {
321         off64_t address = info->sit_blkaddr + sit_block;
322
323         if (f2fs_test_bit(sit_block, info->sit_bmp))
324             address += info->blocks_per_sit;
325
326         SLOGD("Reading cache block starting at block %"PRIu64, address);
327         if (read_structure(fd, address * F2FS_BLKSIZE, &info->sit_blocks[sit_block], sizeof(struct f2fs_sit_block))) {
328             SLOGE("Could not read sit block at block %"PRIu64, address);
329             free(info->sit_blocks);
330             return -1;
331         }
332     }
333     return 0;
334 }
335
336 static inline int is_set_ckpt_flags(struct f2fs_checkpoint *cp, unsigned int f)
337 {
338     unsigned int ckpt_flags = le32_to_cpu(cp->ckpt_flags);
339     return !!(ckpt_flags & f);
340 }
341
342 static inline u64 sum_blk_addr(struct f2fs_checkpoint *cp, struct f2fs_info *info, int base, int type)
343 {
344     return info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_total_block_count)
345                 - (base + 1) + type;
346 }
347
348 static int get_sit_summary(int fd, struct f2fs_info *info, struct f2fs_checkpoint *cp)
349 {
350     char buffer[F2FS_BLKSIZE];
351
352     info->sit_sums = calloc(1, sizeof(struct f2fs_summary_block));
353     if (!info->sit_sums)
354         return -1;
355
356     /* CURSEG_COLD_DATA where the journaled SIT entries are. */
357     if (is_set_ckpt_flags(cp, CP_COMPACT_SUM_FLAG)) {
358         if (read_structure_blk(fd, info->cp_valid_cp_blkaddr + le32_to_cpu(cp->cp_pack_start_sum), buffer, 1))
359             return -1;
360         memcpy(&info->sit_sums->n_sits, &buffer[SUM_JOURNAL_SIZE], SUM_JOURNAL_SIZE);
361     } else {
362         u64 blk_addr;
363         if (is_set_ckpt_flags(cp, CP_UMOUNT_FLAG))
364             blk_addr = sum_blk_addr(cp, info, NR_CURSEG_TYPE, CURSEG_COLD_DATA);
365         else
366             blk_addr = sum_blk_addr(cp, info, NR_CURSEG_DATA_TYPE, CURSEG_COLD_DATA);
367
368         if (read_structure_blk(fd, blk_addr, buffer, 1))
369             return -1;
370
371         memcpy(info->sit_sums, buffer, sizeof(struct f2fs_summary_block));
372     }
373     return 0;
374 }
375
376 struct f2fs_info *generate_f2fs_info(int fd)
377 {
378     struct f2fs_super_block *sb = NULL;
379     struct f2fs_checkpoint *cp = NULL;
380     struct f2fs_info *info;
381
382     info = calloc(1, sizeof(*info));
383     if (!info) {
384         SLOGE("Out of memory!");
385         return NULL;
386     }
387
388     sb = malloc(sizeof(*sb));
389     if(!sb) {
390         SLOGE("Out of memory!");
391         free(info);
392         return NULL;
393     }
394     if (read_f2fs_sb(fd, sb)) {
395         SLOGE("Failed to read superblock");
396         free(info);
397         free(sb);
398         return NULL;
399     }
400     dbg_print_raw_sb_info(sb);
401
402     info->cp_blkaddr = le32_to_cpu(sb->cp_blkaddr);
403     info->sit_blkaddr = le32_to_cpu(sb->sit_blkaddr);
404     info->nat_blkaddr = le32_to_cpu(sb->nat_blkaddr);
405     info->ssa_blkaddr = le32_to_cpu(sb->ssa_blkaddr);
406     info->main_blkaddr = le32_to_cpu(sb->main_blkaddr);
407     info->block_size = F2FS_BLKSIZE;
408     info->total_blocks = sb->block_count;
409     info->blocks_per_sit = (le32_to_cpu(sb->segment_count_sit) >> 1) << le32_to_cpu(sb->log_blocks_per_seg);
410     info->blocks_per_segment = 1U << le32_to_cpu(sb->log_blocks_per_seg);
411
412     if (get_valid_checkpoint_info(fd, sb, &cp, info))
413         goto error;
414     dbg_print_raw_ckpt_struct(cp);
415
416     info->total_user_used = le32_to_cpu(cp->valid_block_count);
417
418     u32 bmp_size = le32_to_cpu(cp->sit_ver_bitmap_bytesize);
419
420     /* get sit validity bitmap */
421     info->sit_bmp = malloc(bmp_size);
422     if(!info->sit_bmp) {
423         SLOGE("Out of memory!");
424         goto error;
425     }
426
427     info->sit_bmp_size = bmp_size;
428     if (read_structure(fd, info->cp_valid_cp_blkaddr * F2FS_BLKSIZE
429                    + offsetof(struct f2fs_checkpoint, sit_nat_version_bitmap),
430                    info->sit_bmp, bmp_size)) {
431         SLOGE("Error getting SIT validity bitmap");
432         goto error;
433     }
434
435     if (gather_sit_info(fd , info)) {
436         SLOGE("Error getting SIT information");
437         goto error;
438     }
439     if (get_sit_summary(fd, info, cp)) {
440         SLOGE("Error getting SIT entries in summary area");
441         goto error;
442     }
443     dbg_print_info_struct(info);
444     return info;
445 error:
446     free(sb);
447     free(cp);
448     free_f2fs_info(info);
449     return NULL;
450 }
451
452 void free_f2fs_info(struct f2fs_info *info)
453 {
454     if (info) {
455         free(info->sit_blocks);
456         info->sit_blocks = NULL;
457
458         free(info->sit_bmp);
459         info->sit_bmp = NULL;
460
461         free(info->sit_sums);
462         info->sit_sums = NULL;
463     }
464     free(info);
465 }
466
467 u64 get_num_blocks_used(struct f2fs_info *info)
468 {
469     return info->main_blkaddr + info->total_user_used;
470 }
471
472 int f2fs_test_bit(unsigned int nr, const char *p)
473 {
474     int mask;
475     char *addr = (char *)p;
476
477     addr += (nr >> 3);
478     mask = 1 << (7 - (nr & 0x07));
479     return (mask & *addr) != 0;
480 }
481
482 int run_on_used_blocks(u64 startblock, struct f2fs_info *info, int (*func)(u64 pos, void *data), void *data) {
483     struct f2fs_sit_block sit_block_cache;
484     struct f2fs_sit_entry * sit_entry;
485     u64 sit_block_num_cur = 0, segnum = 0, block_offset;
486     u64 block;
487     unsigned int used, found, started = 0, i;
488
489     for (block=startblock; block<info->total_blocks; block++) {
490         /* TODO: Save only relevant portions of metadata */
491         if (block < info->main_blkaddr) {
492             if (func(block, data)) {
493                 SLOGI("func error");
494                 return -1;
495             }
496         } else {
497             /* Main Section */
498             segnum = (block - info->main_blkaddr)/info->blocks_per_segment;
499
500             /* check the SIT entries in the journal */
501             found = 0;
502             for(i = 0; i < le16_to_cpu(info->sit_sums->n_sits); i++) {
503                 if (le32_to_cpu(segno_in_journal(info->sit_sums, i)) == segnum) {
504                     sit_entry = &sit_in_journal(info->sit_sums, i);
505                     found = 1;
506                     break;
507                 }
508             }
509
510             /* get SIT entry from SIT section */
511             if (!found) {
512                 sit_block_num_cur = segnum/SIT_ENTRY_PER_BLOCK;
513                 sit_entry = &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
514             }
515
516             block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
517
518             used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map);
519             if(used)
520                 if (func(block, data))
521                     return -1;
522         }
523     }
524     return 0;
525 }
526
527 struct privdata
528 {
529     int count;
530     int infd;
531     int outfd;
532     char* buf;
533     char *zbuf;
534     int done;
535     struct f2fs_info *info;
536 };
537
538
539 /*
540  * This is a simple test program. It performs a block to block copy of a
541  * filesystem, replacing blocks identified as unused with 0's.
542  */
543
544 int copy_used(u64 pos, void *data)
545 {
546     struct privdata *d = data;
547     char *buf;
548     int pdone = (pos*100)/d->info->total_blocks;
549     if (pdone > d->done) {
550         d->done = pdone;
551         printf("Done with %d percent\n", d->done);
552     }
553
554     d->count++;
555     buf = d->buf;
556     if(read_structure_blk(d->infd, (unsigned long long)pos, d->buf, 1)) {
557         printf("Error reading!!!\n");
558         return -1;
559     }
560
561     off64_t ret;
562     ret = lseek64(d->outfd, pos*F2FS_BLKSIZE, SEEK_SET);
563     if (ret < 0) {
564         SLOGE("failed to seek\n");
565         return ret;
566     }
567
568     ret = write(d->outfd, d->buf, F2FS_BLKSIZE);
569     if (ret < 0) {
570         SLOGE("failed to write\n");
571         return ret;
572     }
573     if (ret != F2FS_BLKSIZE) {
574         SLOGE("failed to read all\n");
575         return -1;
576     }
577     return 0;
578 }
579
580 int main(int argc, char **argv)
581 {
582     if (argc != 3)
583         printf("Usage: %s fs_file_in fs_file_out\n", argv[0]);
584     char *in = argv[1];
585     char *out = argv[2];
586     int infd, outfd;
587
588     if ((infd = open(in, O_RDONLY)) < 0) {
589         SLOGE("Cannot open device");
590         return 0;
591     }
592     if ((outfd = open(out, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
593         SLOGE("Cannot open output");
594         return 0;
595     }
596
597     struct privdata d;
598     d.infd = infd;
599     d.outfd = outfd;
600     d.count = 0;
601     struct f2fs_info *info = generate_f2fs_info(infd);
602     if (!info) {
603         printf("Failed to generate info!");
604         return -1;
605     }
606     char *buf = malloc(F2FS_BLKSIZE);
607     char *zbuf = calloc(1, F2FS_BLKSIZE);
608     d.buf = buf;
609     d.zbuf = zbuf;
610     d.done = 0;
611     d.info = info;
612     int expected_count = get_num_blocks_used(info);
613     run_on_used_blocks(0, info, &copy_used, &d);
614     printf("Copied %d blocks. Expected to copy %d\n", d.count, expected_count);
615     ftruncate64(outfd, info->total_blocks * F2FS_BLKSIZE);
616     free_f2fs_info(info);
617     free(buf);
618     free(zbuf);
619     close(infd);
620     close(outfd);
621     return 0;
622 }