1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "journal-authenticate.h"
32 #include "journal-verify.h"
39 * - evolve key even if nothing happened in regular intervals
45 static int journal_file_object_verify(JournalFile *f, Object *o) {
51 /* This does various superficial tests about the length an
52 * possible field values. It does not follow any references to
55 if ((o->object.flags & OBJECT_COMPRESSED) &&
56 o->object.type != OBJECT_DATA)
59 switch (o->object.type) {
64 if (le64toh(o->data.entry_offset) <= 0 ||
65 le64toh(o->data.n_entries) <= 0)
68 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
71 h1 = le64toh(o->data.hash);
73 if (o->object.flags & OBJECT_COMPRESSED) {
75 uint64_t alloc = 0, b_size;
77 if (!uncompress_blob(o->data.payload,
78 le64toh(o->object.size) - offsetof(Object, data.payload),
82 h2 = hash64(b, b_size);
85 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
90 if (!VALID64(o->data.next_hash_offset) ||
91 !VALID64(o->data.next_field_offset) ||
92 !VALID64(o->data.entry_offset) ||
93 !VALID64(o->data.entry_array_offset))
100 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
103 if (!VALID64(o->field.next_hash_offset) ||
104 !VALID64(o->field.head_data_offset))
109 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
112 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
115 if (le64toh(o->entry.seqnum) <= 0 ||
116 !VALID_REALTIME(le64toh(o->entry.realtime)) ||
117 !VALID_MONOTONIC(le64toh(o->entry.monotonic)))
120 for (i = 0; i < journal_file_entry_n_items(o); i++) {
121 if (o->entry.items[i].object_offset == 0 ||
122 !VALID64(o->entry.items[i].object_offset))
128 case OBJECT_DATA_HASH_TABLE:
129 case OBJECT_FIELD_HASH_TABLE:
130 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
133 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0)
136 for (i = 0; i < journal_file_hash_table_n_items(o); i++) {
137 if (o->hash_table.items[i].head_hash_offset != 0 &&
138 !VALID64(le64toh(o->hash_table.items[i].head_hash_offset)))
140 if (o->hash_table.items[i].tail_hash_offset != 0 &&
141 !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset)))
144 if ((o->hash_table.items[i].head_hash_offset != 0) !=
145 (o->hash_table.items[i].tail_hash_offset != 0))
151 case OBJECT_ENTRY_ARRAY:
152 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
155 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0)
158 if (!VALID64(o->entry_array.next_entry_array_offset))
161 for (i = 0; i < journal_file_entry_array_n_items(o); i++)
162 if (o->entry_array.items[i] != 0 &&
163 !VALID64(o->entry_array.items[i]))
169 if (le64toh(o->object.size) != sizeof(TagObject))
172 if (!VALID_EPOCH(o->tag.epoch))
181 static void draw_progress(uint64_t p, usec_t *last_usec) {
185 if (!isatty(STDOUT_FILENO))
188 z = now(CLOCK_MONOTONIC);
191 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
196 n = (3 * columns()) / 4;
197 j = (n * (unsigned) p) / 65535ULL;
200 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
202 for (i = 0; i < j; i++)
203 fputs("\xe2\x96\x88", stdout);
205 fputs(ANSI_HIGHLIGHT_OFF, stdout);
207 for (i = 0; i < k; i++)
208 fputs("\xe2\x96\x91", stdout);
210 printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
212 fputs("\r\x1B[?25h", stdout);
216 static void flush_progress(void) {
219 if (!isatty(STDOUT_FILENO))
222 n = (3 * columns()) / 4;
226 for (i = 0; i < n + 5; i++)
233 static int write_uint64(int fd, uint64_t p) {
236 k = write(fd, &p, sizeof(p));
245 static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
260 r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, c * sizeof(uint64_t), sizeof(uint64_t), (void **) &z);
280 static int entry_points_to_data(
293 assert(entry_fd >= 0);
295 if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) {
296 log_error("Data object references invalid entry at %llu", (unsigned long long) data_p);
300 r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
304 n = journal_file_entry_n_items(o);
305 for (i = 0; i < n; i++)
306 if (le64toh(o->entry.items[i].object_offset) == data_p) {
312 log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p);
316 /* Check if this entry is also in main entry array. Since the
317 * main entry array has already been verified we can rely on
320 n = le64toh(f->header->n_entries);
321 a = le64toh(f->header->entry_array_offset);
327 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
331 m = journal_file_entry_array_n_items(o);
332 for (j = 0; i < n && j < m; i++, j++)
333 if (le64toh(o->entry_array.items[j]) == entry_p)
336 a = le64toh(o->entry_array.next_entry_array_offset);
342 static int verify_data(
344 Object *o, uint64_t p,
345 int entry_fd, uint64_t n_entries,
346 int entry_array_fd, uint64_t n_entry_arrays) {
348 uint64_t i, n, a, last, q;
353 assert(entry_fd >= 0);
354 assert(entry_array_fd >= 0);
356 n = le64toh(o->data.n_entries);
357 a = le64toh(o->data.entry_array_offset);
359 /* We already checked this earlier */
362 last = q = le64toh(o->data.entry_offset);
363 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
372 log_error("Array chain too short at %llu", (unsigned long long) p);
376 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
377 log_error("Invalid array at %llu", (unsigned long long) p);
381 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
385 next = le64toh(o->entry_array.next_entry_array_offset);
386 if (next != 0 && next <= a) {
387 log_error("Array chain has cycle at %llu", (unsigned long long) p);
391 m = journal_file_entry_array_n_items(o);
392 for (j = 0; i < n && j < m; i++, j++) {
394 q = le64toh(o->entry_array.items[j]);
396 log_error("Data object's entry array not sorted at %llu", (unsigned long long) p);
401 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
405 /* Pointer might have moved, reposition */
406 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
417 static int verify_hash_table(
419 int data_fd, uint64_t n_data,
420 int entry_fd, uint64_t n_entries,
421 int entry_array_fd, uint64_t n_entry_arrays,
423 bool show_progress) {
429 assert(data_fd >= 0);
430 assert(entry_fd >= 0);
431 assert(entry_array_fd >= 0);
434 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
435 for (i = 0; i < n; i++) {
436 uint64_t last = 0, p;
439 draw_progress(0xC000 + (0x3FFF * i / n), last_usec);
441 p = le64toh(f->data_hash_table[i].head_hash_offset);
446 if (!contains_uint64(f->mmap, data_fd, n_data, p)) {
447 log_error("Invalid data object at hash entry %llu of %llu",
448 (unsigned long long) i, (unsigned long long) n);
452 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
456 next = le64toh(o->data.next_hash_offset);
457 if (next != 0 && next <= p) {
458 log_error("Hash chain has a cycle in hash entry %llu of %llu",
459 (unsigned long long) i, (unsigned long long) n);
463 if (le64toh(o->data.hash) % n != i) {
464 log_error("Hash value mismatch in hash entry %llu of %llu",
465 (unsigned long long) i, (unsigned long long) n);
469 r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays);
477 if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
478 log_error("Tail hash pointer mismatch in hash table");
486 static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
491 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
494 q = le64toh(f->data_hash_table[h].head_hash_offset);
501 r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
505 q = le64toh(o->data.next_hash_offset);
511 static int verify_entry(
513 Object *o, uint64_t p,
514 int data_fd, uint64_t n_data) {
521 assert(data_fd >= 0);
523 n = journal_file_entry_n_items(o);
524 for (i = 0; i < n; i++) {
528 q = le64toh(o->entry.items[i].object_offset);
529 h = le64toh(o->entry.items[i].hash);
531 if (!contains_uint64(f->mmap, data_fd, n_data, q)) {
532 log_error("Invalid data object at entry %llu",
533 (unsigned long long) p);
537 r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
541 if (le64toh(u->data.hash) != h) {
542 log_error("Hash mismatch for data object at entry %llu",
543 (unsigned long long) p);
547 r = data_object_in_hash_table(f, h, q);
551 log_error("Data object missing from hash at entry %llu",
552 (unsigned long long) p);
560 static int verify_entry_array(
562 int data_fd, uint64_t n_data,
563 int entry_fd, uint64_t n_entries,
564 int entry_array_fd, uint64_t n_entry_arrays,
566 bool show_progress) {
568 uint64_t i = 0, a, n, last = 0;
572 assert(data_fd >= 0);
573 assert(entry_fd >= 0);
574 assert(entry_array_fd >= 0);
577 n = le64toh(f->header->n_entries);
578 a = le64toh(f->header->entry_array_offset);
584 draw_progress(0x8000 + (0x3FFF * i / n), last_usec);
587 log_error("Array chain too short at %llu of %llu",
588 (unsigned long long) i, (unsigned long long) n);
592 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
593 log_error("Invalid array at %llu of %llu",
594 (unsigned long long) i, (unsigned long long) n);
598 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
602 next = le64toh(o->entry_array.next_entry_array_offset);
603 if (next != 0 && next <= a) {
604 log_error("Array chain has cycle at %llu of %llu",
605 (unsigned long long) i, (unsigned long long) n);
609 m = journal_file_entry_array_n_items(o);
610 for (j = 0; i < n && j < m; i++, j++) {
613 p = le64toh(o->entry_array.items[j]);
615 log_error("Entry array not sorted at %llu of %llu",
616 (unsigned long long) i, (unsigned long long) n);
621 if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
622 log_error("Invalid array entry at %llu of %llu",
623 (unsigned long long) i, (unsigned long long) n);
627 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
631 r = verify_entry(f, o, p, data_fd, n_data);
635 /* Pointer might have moved, reposition */
636 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
647 int journal_file_verify(
650 usec_t *first_validated, usec_t *last_validated, usec_t *last_contained,
651 bool show_progress) {
654 uint64_t p = 0, last_tag = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0;
655 uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
656 sd_id128_t entry_boot_id;
657 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
658 uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0;
659 usec_t last_usec = 0;
660 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
661 char data_path[] = "/var/tmp/journal-data-XXXXXX",
662 entry_path[] = "/var/tmp/journal-entry-XXXXXX",
663 entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
671 r = journal_file_parse_verification_key(f, key);
673 log_error("Failed to parse seed.");
682 data_fd = mkostemp(data_path, O_CLOEXEC);
684 log_error("Failed to create data file: %m");
690 entry_fd = mkostemp(entry_path, O_CLOEXEC);
692 log_error("Failed to create entry file: %m");
698 entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
699 if (entry_array_fd < 0) {
700 log_error("Failed to create entry array file: %m");
704 unlink(entry_array_path);
707 if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) != 0)
709 if (f->header->compatible_flags != 0)
712 log_error("Cannot verify file with unknown extensions.");
717 for (i = 0; i < sizeof(f->header->reserved); i++)
718 if (f->header->reserved[i] != 0) {
719 log_error("Reserved field in non-zero.");
724 /* First iteration: we go through all objects, verify the
725 * superficial structure, headers, hashes. */
727 p = le64toh(f->header->header_size);
730 draw_progress(0x7FFF * p / le64toh(f->header->tail_object_offset), &last_usec);
732 r = journal_file_move_to_object(f, -1, p, &o);
734 log_error("Invalid object at %llu", (unsigned long long) p);
738 if (p > le64toh(f->header->tail_object_offset)) {
739 log_error("Invalid tail object pointer");
744 if (p == le64toh(f->header->tail_object_offset))
749 r = journal_file_object_verify(f, o);
751 log_error("Invalid object contents at %llu", (unsigned long long) p);
755 if ((o->object.flags & OBJECT_COMPRESSED) && !JOURNAL_HEADER_COMPRESSED(f->header)) {
756 log_error("Compressed object in file without compression at %llu", (unsigned long long) p);
761 switch (o->object.type) {
764 r = write_uint64(data_fd, p);
776 if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) {
777 log_error("First entry before first tag at %llu", (unsigned long long) p);
782 r = write_uint64(entry_fd, p);
786 if (le64toh(o->entry.realtime) < last_tag_realtime) {
787 log_error("Older entry after newer tag at %llu", (unsigned long long) p);
792 if (!entry_seqnum_set &&
793 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
794 log_error("Head entry sequence number incorrect at %llu", (unsigned long long) p);
799 if (entry_seqnum_set &&
800 entry_seqnum >= le64toh(o->entry.seqnum)) {
801 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
806 entry_seqnum = le64toh(o->entry.seqnum);
807 entry_seqnum_set = true;
809 if (entry_monotonic_set &&
810 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
811 entry_monotonic > le64toh(o->entry.monotonic)) {
812 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
817 entry_monotonic = le64toh(o->entry.monotonic);
818 entry_boot_id = o->entry.boot_id;
819 entry_monotonic_set = true;
821 if (!entry_realtime_set &&
822 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
823 log_error("Head entry realtime timestamp incorrect");
828 entry_realtime = le64toh(o->entry.realtime);
829 entry_realtime_set = true;
834 case OBJECT_DATA_HASH_TABLE:
835 if (n_data_hash_tables > 1) {
836 log_error("More than one data hash table at %llu", (unsigned long long) p);
841 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
842 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
843 log_error("Header fields for data hash table invalid");
848 n_data_hash_tables++;
851 case OBJECT_FIELD_HASH_TABLE:
852 if (n_field_hash_tables > 1) {
853 log_error("More than one field hash table at %llu", (unsigned long long) p);
858 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
859 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
860 log_error("Header fields for field hash table invalid");
865 n_field_hash_tables++;
868 case OBJECT_ENTRY_ARRAY:
869 r = write_uint64(entry_array_fd, p);
873 if (p == le64toh(f->header->entry_array_offset)) {
874 if (found_main_entry_array) {
875 log_error("More than one main entry array at %llu", (unsigned long long) p);
880 found_main_entry_array = true;
887 if (!JOURNAL_HEADER_SEALED(f->header)) {
888 log_error("Tag object in file without sealing at %llu", (unsigned long long) p);
893 if (le64toh(o->tag.seqnum) != n_tags + 1) {
894 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
899 if (le64toh(o->tag.epoch) < last_epoch) {
900 log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p);
909 log_debug("Checking tag %llu..", (unsigned long long) le64toh(o->tag.seqnum));
911 rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec;
912 if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) {
913 log_error("Tag/entry realtime timestamp out of synchronization at %llu", (unsigned long long) p);
918 /* OK, now we know the epoch. So let's now set
919 * it, and calculate the HMAC for everything
920 * since the last tag. */
921 r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
925 r = journal_file_hmac_start(f);
930 r = journal_file_hmac_put_header(f);
934 q = le64toh(f->header->header_size);
939 r = journal_file_move_to_object(f, -1, q, &o);
943 r = journal_file_hmac_put_object(f, -1, q);
947 q = q + ALIGN64(le64toh(o->object.size));
950 /* Position might have changed, let's reposition things */
951 r = journal_file_move_to_object(f, -1, p, &o);
955 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
956 log_error("Tag failed verification at %llu", (unsigned long long) p);
961 f->hmac_running = false;
962 last_tag_realtime = rt;
963 last_sealed_realtime = entry_realtime;
967 last_tag = p + ALIGN64(le64toh(o->object.size));
968 last_epoch = le64toh(o->tag.epoch);
977 if (p == le64toh(f->header->tail_object_offset))
980 p = p + ALIGN64(le64toh(o->object.size));
984 log_error("Tail object pointer dead");
989 if (n_objects != le64toh(f->header->n_objects)) {
990 log_error("Object number mismatch");
995 if (n_entries != le64toh(f->header->n_entries)) {
996 log_error("Entry number mismatch");
1001 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
1002 n_data != le64toh(f->header->n_data)) {
1003 log_error("Data number mismatch");
1008 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
1009 n_fields != le64toh(f->header->n_fields)) {
1010 log_error("Field number mismatch");
1015 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
1016 n_tags != le64toh(f->header->n_tags)) {
1017 log_error("Tag number mismatch");
1022 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
1023 n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
1024 log_error("Entry array number mismatch");
1029 if (n_data_hash_tables != 1) {
1030 log_error("Missing data hash table");
1035 if (n_field_hash_tables != 1) {
1036 log_error("Missing field hash table");
1041 if (!found_main_entry_array) {
1042 log_error("Missing entry array");
1047 if (entry_seqnum_set &&
1048 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
1049 log_error("Invalid tail seqnum");
1054 if (entry_monotonic_set &&
1055 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
1056 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
1057 log_error("Invalid tail monotonic timestamp");
1062 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
1063 log_error("Invalid tail realtime timestamp");
1068 /* Second iteration: we follow all objects referenced from the
1069 * two entry points: the object hash table and the entry
1070 * array. We also check that everything referenced (directly
1071 * or indirectly) in the data hash table also exists in the
1072 * entry array, and vice versa. Note that we do not care for
1073 * unreferenced objects. We only care that everything that is
1074 * referenced is consistent. */
1076 r = verify_entry_array(f,
1078 entry_fd, n_entries,
1079 entry_array_fd, n_entry_arrays,
1085 r = verify_hash_table(f,
1087 entry_fd, n_entries,
1088 entry_array_fd, n_entry_arrays,
1097 mmap_cache_close_fd(f->mmap, data_fd);
1098 mmap_cache_close_fd(f->mmap, entry_fd);
1099 mmap_cache_close_fd(f->mmap, entry_array_fd);
1101 close_nointr_nofail(data_fd);
1102 close_nointr_nofail(entry_fd);
1103 close_nointr_nofail(entry_array_fd);
1105 if (first_validated)
1106 *first_validated = last_tag_realtime ? le64toh(f->header->head_entry_realtime) : 0;
1108 *last_validated = last_sealed_realtime;
1110 *last_contained = le64toh(f->header->tail_entry_realtime);
1118 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
1120 (unsigned long long) p,
1121 (unsigned long long) f->last_stat.st_size,
1122 (unsigned long long) (100 * p / f->last_stat.st_size));
1125 mmap_cache_close_fd(f->mmap, data_fd);
1126 close_nointr_nofail(data_fd);
1129 if (entry_fd >= 0) {
1130 mmap_cache_close_fd(f->mmap, entry_fd);
1131 close_nointr_nofail(entry_fd);
1134 if (entry_array_fd >= 0) {
1135 mmap_cache_close_fd(f->mmap, entry_array_fd);
1136 close_nointr_nofail(entry_array_fd);