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
46 static int journal_file_object_verify(JournalFile *f, Object *o) {
52 /* This does various superficial tests about the length an
53 * possible field values. It does not follow any references to
56 if ((o->object.flags & OBJECT_COMPRESSED) &&
57 o->object.type != OBJECT_DATA)
60 switch (o->object.type) {
65 if (le64toh(o->data.entry_offset) <= 0 ||
66 le64toh(o->data.n_entries) <= 0)
69 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
72 h1 = le64toh(o->data.hash);
74 if (o->object.flags & OBJECT_COMPRESSED) {
76 uint64_t alloc = 0, b_size;
78 if (!uncompress_blob(o->data.payload,
79 le64toh(o->object.size) - offsetof(Object, data.payload),
83 h2 = hash64(b, b_size);
86 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
91 if (!VALID64(o->data.next_hash_offset) ||
92 !VALID64(o->data.next_field_offset) ||
93 !VALID64(o->data.entry_offset) ||
94 !VALID64(o->data.entry_array_offset))
101 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
104 if (!VALID64(o->field.next_hash_offset) ||
105 !VALID64(o->field.head_data_offset))
110 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
113 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
116 if (le64toh(o->entry.seqnum) <= 0 ||
117 !VALID_REALTIME(le64toh(o->entry.realtime)) ||
118 !VALID_MONOTONIC(le64toh(o->entry.monotonic)))
121 for (i = 0; i < journal_file_entry_n_items(o); i++) {
122 if (o->entry.items[i].object_offset == 0 ||
123 !VALID64(o->entry.items[i].object_offset))
129 case OBJECT_DATA_HASH_TABLE:
130 case OBJECT_FIELD_HASH_TABLE:
131 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
134 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0)
137 for (i = 0; i < journal_file_hash_table_n_items(o); i++) {
138 if (o->hash_table.items[i].head_hash_offset != 0 &&
139 !VALID64(le64toh(o->hash_table.items[i].head_hash_offset)))
141 if (o->hash_table.items[i].tail_hash_offset != 0 &&
142 !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset)))
145 if ((o->hash_table.items[i].head_hash_offset != 0) !=
146 (o->hash_table.items[i].tail_hash_offset != 0))
152 case OBJECT_ENTRY_ARRAY:
153 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
156 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0)
159 if (!VALID64(o->entry_array.next_entry_array_offset))
162 for (i = 0; i < journal_file_entry_array_n_items(o); i++)
163 if (o->entry_array.items[i] != 0 &&
164 !VALID64(o->entry_array.items[i]))
170 if (le64toh(o->object.size) != sizeof(TagObject))
173 if (!VALID_EPOCH(o->tag.epoch))
182 static void draw_progress(uint64_t p, usec_t *last_usec) {
186 if (!isatty(STDOUT_FILENO))
189 z = now(CLOCK_MONOTONIC);
192 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
197 n = (3 * columns()) / 4;
198 j = (n * (unsigned) p) / 65535ULL;
201 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
203 for (i = 0; i < j; i++)
204 fputs("\xe2\x96\x88", stdout);
206 fputs(ANSI_HIGHLIGHT_OFF, stdout);
208 for (i = 0; i < k; i++)
209 fputs("\xe2\x96\x91", stdout);
211 printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
213 fputs("\r\x1B[?25h", stdout);
217 static void flush_progress(void) {
220 if (!isatty(STDOUT_FILENO))
223 n = (3 * columns()) / 4;
227 for (i = 0; i < n + 5; i++)
234 static int write_uint64(int fd, uint64_t p) {
237 k = write(fd, &p, sizeof(p));
246 static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
261 r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, c * sizeof(uint64_t), sizeof(uint64_t), (void **) &z);
281 static int entry_points_to_data(
294 assert(entry_fd >= 0);
296 if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) {
297 log_error("Data object references invalid entry at %llu", (unsigned long long) data_p);
301 r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
305 n = journal_file_entry_n_items(o);
306 for (i = 0; i < n; i++)
307 if (le64toh(o->entry.items[i].object_offset) == data_p) {
313 log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p);
317 /* Check if this entry is also in main entry array. Since the
318 * main entry array has already been verified we can rely on
321 n = le64toh(f->header->n_entries);
322 a = le64toh(f->header->entry_array_offset);
328 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
332 m = journal_file_entry_array_n_items(o);
333 for (j = 0; i < n && j < m; i++, j++)
334 if (le64toh(o->entry_array.items[j]) == entry_p)
337 a = le64toh(o->entry_array.next_entry_array_offset);
343 static int verify_data(
345 Object *o, uint64_t p,
346 int entry_fd, uint64_t n_entries,
347 int entry_array_fd, uint64_t n_entry_arrays) {
349 uint64_t i, n, a, last, q;
354 assert(entry_fd >= 0);
355 assert(entry_array_fd >= 0);
357 n = le64toh(o->data.n_entries);
358 a = le64toh(o->data.entry_array_offset);
360 /* We already checked this earlier */
363 last = q = le64toh(o->data.entry_offset);
364 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
373 log_error("Array chain too short at %llu", (unsigned long long) p);
377 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
378 log_error("Invalid array at %llu", (unsigned long long) p);
382 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
386 next = le64toh(o->entry_array.next_entry_array_offset);
387 if (next != 0 && next <= a) {
388 log_error("Array chain has cycle at %llu", (unsigned long long) p);
392 m = journal_file_entry_array_n_items(o);
393 for (j = 0; i < n && j < m; i++, j++) {
395 q = le64toh(o->entry_array.items[j]);
397 log_error("Data object's entry array not sorted at %llu", (unsigned long long) p);
402 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
406 /* Pointer might have moved, reposition */
407 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
418 static int verify_hash_table(
420 int data_fd, uint64_t n_data,
421 int entry_fd, uint64_t n_entries,
422 int entry_array_fd, uint64_t n_entry_arrays,
424 bool show_progress) {
430 assert(data_fd >= 0);
431 assert(entry_fd >= 0);
432 assert(entry_array_fd >= 0);
435 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
436 for (i = 0; i < n; i++) {
437 uint64_t last = 0, p;
440 draw_progress(0xC000 + (0x3FFF * i / n), last_usec);
442 p = le64toh(f->data_hash_table[i].head_hash_offset);
447 if (!contains_uint64(f->mmap, data_fd, n_data, p)) {
448 log_error("Invalid data object at hash entry %llu of %llu",
449 (unsigned long long) i, (unsigned long long) n);
453 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
457 next = le64toh(o->data.next_hash_offset);
458 if (next != 0 && next <= p) {
459 log_error("Hash chain has a cycle in hash entry %llu of %llu",
460 (unsigned long long) i, (unsigned long long) n);
464 if (le64toh(o->data.hash) % n != i) {
465 log_error("Hash value mismatch in hash entry %llu of %llu",
466 (unsigned long long) i, (unsigned long long) n);
470 r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays);
478 if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
479 log_error("Tail hash pointer mismatch in hash table");
487 static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
492 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
495 q = le64toh(f->data_hash_table[h].head_hash_offset);
502 r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
506 q = le64toh(o->data.next_hash_offset);
512 static int verify_entry(
514 Object *o, uint64_t p,
515 int data_fd, uint64_t n_data) {
522 assert(data_fd >= 0);
524 n = journal_file_entry_n_items(o);
525 for (i = 0; i < n; i++) {
529 q = le64toh(o->entry.items[i].object_offset);
530 h = le64toh(o->entry.items[i].hash);
532 if (!contains_uint64(f->mmap, data_fd, n_data, q)) {
533 log_error("Invalid data object at entry %llu",
534 (unsigned long long) p);
538 r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
542 if (le64toh(u->data.hash) != h) {
543 log_error("Hash mismatch for data object at entry %llu",
544 (unsigned long long) p);
548 r = data_object_in_hash_table(f, h, q);
552 log_error("Data object missing from hash at entry %llu",
553 (unsigned long long) p);
561 static int verify_entry_array(
563 int data_fd, uint64_t n_data,
564 int entry_fd, uint64_t n_entries,
565 int entry_array_fd, uint64_t n_entry_arrays,
567 bool show_progress) {
569 uint64_t i = 0, a, n, last = 0;
573 assert(data_fd >= 0);
574 assert(entry_fd >= 0);
575 assert(entry_array_fd >= 0);
578 n = le64toh(f->header->n_entries);
579 a = le64toh(f->header->entry_array_offset);
585 draw_progress(0x8000 + (0x3FFF * i / n), last_usec);
588 log_error("Array chain too short at %llu of %llu",
589 (unsigned long long) i, (unsigned long long) n);
593 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
594 log_error("Invalid array at %llu of %llu",
595 (unsigned long long) i, (unsigned long long) n);
599 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
603 next = le64toh(o->entry_array.next_entry_array_offset);
604 if (next != 0 && next <= a) {
605 log_error("Array chain has cycle at %llu of %llu",
606 (unsigned long long) i, (unsigned long long) n);
610 m = journal_file_entry_array_n_items(o);
611 for (j = 0; i < n && j < m; i++, j++) {
614 p = le64toh(o->entry_array.items[j]);
616 log_error("Entry array not sorted at %llu of %llu",
617 (unsigned long long) i, (unsigned long long) n);
622 if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
623 log_error("Invalid array entry at %llu of %llu",
624 (unsigned long long) i, (unsigned long long) n);
628 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
632 r = verify_entry(f, o, p, data_fd, n_data);
636 /* Pointer might have moved, reposition */
637 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
648 int journal_file_verify(
651 usec_t *first_validated, usec_t *last_validated, usec_t *last_contained,
652 bool show_progress) {
655 uint64_t p = 0, last_tag = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0;
656 uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
657 sd_id128_t entry_boot_id;
658 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
659 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;
660 usec_t last_usec = 0;
661 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
662 char data_path[] = "/var/tmp/journal-data-XXXXXX",
663 entry_path[] = "/var/tmp/journal-entry-XXXXXX",
664 entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
672 r = journal_file_parse_verification_key(f, key);
674 log_error("Failed to parse seed.");
683 data_fd = mkostemp(data_path, O_CLOEXEC);
685 log_error("Failed to create data file: %m");
691 entry_fd = mkostemp(entry_path, O_CLOEXEC);
693 log_error("Failed to create entry file: %m");
699 entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
700 if (entry_array_fd < 0) {
701 log_error("Failed to create entry array file: %m");
705 unlink(entry_array_path);
708 if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) != 0)
710 if (f->header->compatible_flags != 0)
713 log_error("Cannot verify file with unknown extensions.");
718 for (i = 0; i < sizeof(f->header->reserved); i++)
719 if (f->header->reserved[i] != 0) {
720 log_error("Reserved field in non-zero.");
725 /* First iteration: we go through all objects, verify the
726 * superficial structure, headers, hashes. */
728 p = le64toh(f->header->header_size);
731 draw_progress(0x7FFF * p / le64toh(f->header->tail_object_offset), &last_usec);
733 r = journal_file_move_to_object(f, -1, p, &o);
735 log_error("Invalid object at %llu", (unsigned long long) p);
739 if (p > le64toh(f->header->tail_object_offset)) {
740 log_error("Invalid tail object pointer");
745 if (p == le64toh(f->header->tail_object_offset))
750 r = journal_file_object_verify(f, o);
752 log_error("Invalid object contents at %llu", (unsigned long long) p);
756 if ((o->object.flags & OBJECT_COMPRESSED) && !JOURNAL_HEADER_COMPRESSED(f->header)) {
757 log_error("Compressed object in file without compression at %llu", (unsigned long long) p);
762 switch (o->object.type) {
765 r = write_uint64(data_fd, p);
777 if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) {
778 log_error("First entry before first tag at %llu", (unsigned long long) p);
783 r = write_uint64(entry_fd, p);
787 if (le64toh(o->entry.realtime) < last_tag_realtime) {
788 log_error("Older entry after newer tag at %llu", (unsigned long long) p);
793 if (!entry_seqnum_set &&
794 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
795 log_error("Head entry sequence number incorrect at %llu", (unsigned long long) p);
800 if (entry_seqnum_set &&
801 entry_seqnum >= le64toh(o->entry.seqnum)) {
802 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
807 entry_seqnum = le64toh(o->entry.seqnum);
808 entry_seqnum_set = true;
810 if (entry_monotonic_set &&
811 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
812 entry_monotonic > le64toh(o->entry.monotonic)) {
813 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
818 entry_monotonic = le64toh(o->entry.monotonic);
819 entry_boot_id = o->entry.boot_id;
820 entry_monotonic_set = true;
822 if (!entry_realtime_set &&
823 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
824 log_error("Head entry realtime timestamp incorrect");
829 entry_realtime = le64toh(o->entry.realtime);
830 entry_realtime_set = true;
835 case OBJECT_DATA_HASH_TABLE:
836 if (n_data_hash_tables > 1) {
837 log_error("More than one data hash table at %llu", (unsigned long long) p);
842 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
843 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
844 log_error("Header fields for data hash table invalid");
849 n_data_hash_tables++;
852 case OBJECT_FIELD_HASH_TABLE:
853 if (n_field_hash_tables > 1) {
854 log_error("More than one field hash table at %llu", (unsigned long long) p);
859 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
860 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
861 log_error("Header fields for field hash table invalid");
866 n_field_hash_tables++;
869 case OBJECT_ENTRY_ARRAY:
870 r = write_uint64(entry_array_fd, p);
874 if (p == le64toh(f->header->entry_array_offset)) {
875 if (found_main_entry_array) {
876 log_error("More than one main entry array at %llu", (unsigned long long) p);
881 found_main_entry_array = true;
888 if (!JOURNAL_HEADER_SEALED(f->header)) {
889 log_error("Tag object in file without sealing at %llu", (unsigned long long) p);
894 if (le64toh(o->tag.seqnum) != n_tags + 1) {
895 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
900 if (le64toh(o->tag.epoch) < last_epoch) {
901 log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p);
910 log_debug("Checking tag %llu..", (unsigned long long) le64toh(o->tag.seqnum));
912 rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec;
913 if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) {
914 log_error("Tag/entry realtime timestamp out of synchronization at %llu", (unsigned long long) p);
919 /* OK, now we know the epoch. So let's now set
920 * it, and calculate the HMAC for everything
921 * since the last tag. */
922 r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
926 r = journal_file_hmac_start(f);
931 r = journal_file_hmac_put_header(f);
935 q = le64toh(f->header->header_size);
940 r = journal_file_move_to_object(f, -1, q, &o);
944 r = journal_file_hmac_put_object(f, -1, q);
948 q = q + ALIGN64(le64toh(o->object.size));
951 /* Position might have changed, let's reposition things */
952 r = journal_file_move_to_object(f, -1, p, &o);
956 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
957 log_error("Tag failed verification at %llu", (unsigned long long) p);
962 f->hmac_running = false;
963 last_tag_realtime = rt;
964 last_sealed_realtime = entry_realtime;
968 last_tag = p + ALIGN64(le64toh(o->object.size));
969 last_epoch = le64toh(o->tag.epoch);
978 if (p == le64toh(f->header->tail_object_offset))
981 p = p + ALIGN64(le64toh(o->object.size));
985 log_error("Tail object pointer dead");
990 if (n_objects != le64toh(f->header->n_objects)) {
991 log_error("Object number mismatch");
996 if (n_entries != le64toh(f->header->n_entries)) {
997 log_error("Entry number mismatch");
1002 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
1003 n_data != le64toh(f->header->n_data)) {
1004 log_error("Data number mismatch");
1009 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
1010 n_fields != le64toh(f->header->n_fields)) {
1011 log_error("Field number mismatch");
1016 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
1017 n_tags != le64toh(f->header->n_tags)) {
1018 log_error("Tag number mismatch");
1023 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
1024 n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
1025 log_error("Entry array number mismatch");
1030 if (n_data_hash_tables != 1) {
1031 log_error("Missing data hash table");
1036 if (n_field_hash_tables != 1) {
1037 log_error("Missing field hash table");
1042 if (!found_main_entry_array) {
1043 log_error("Missing entry array");
1048 if (entry_seqnum_set &&
1049 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
1050 log_error("Invalid tail seqnum");
1055 if (entry_monotonic_set &&
1056 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
1057 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
1058 log_error("Invalid tail monotonic timestamp");
1063 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
1064 log_error("Invalid tail realtime timestamp");
1069 /* Second iteration: we follow all objects referenced from the
1070 * two entry points: the object hash table and the entry
1071 * array. We also check that everything referenced (directly
1072 * or indirectly) in the data hash table also exists in the
1073 * entry array, and vice versa. Note that we do not care for
1074 * unreferenced objects. We only care that everything that is
1075 * referenced is consistent. */
1077 r = verify_entry_array(f,
1079 entry_fd, n_entries,
1080 entry_array_fd, n_entry_arrays,
1086 r = verify_hash_table(f,
1088 entry_fd, n_entries,
1089 entry_array_fd, n_entry_arrays,
1098 mmap_cache_close_fd(f->mmap, data_fd);
1099 mmap_cache_close_fd(f->mmap, entry_fd);
1100 mmap_cache_close_fd(f->mmap, entry_array_fd);
1102 close_nointr_nofail(data_fd);
1103 close_nointr_nofail(entry_fd);
1104 close_nointr_nofail(entry_array_fd);
1106 if (first_validated)
1107 *first_validated = last_tag_realtime ? le64toh(f->header->head_entry_realtime) : 0;
1109 *last_validated = last_sealed_realtime;
1111 *last_contained = le64toh(f->header->tail_entry_realtime);
1119 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
1121 (unsigned long long) p,
1122 (unsigned long long) f->last_stat.st_size,
1123 (unsigned long long) (100 * p / f->last_stat.st_size));
1126 mmap_cache_close_fd(f->mmap, data_fd);
1127 close_nointr_nofail(data_fd);
1130 if (entry_fd >= 0) {
1131 mmap_cache_close_fd(f->mmap, entry_fd);
1132 close_nointr_nofail(entry_fd);
1135 if (entry_array_fd >= 0) {
1136 mmap_cache_close_fd(f->mmap, entry_array_fd);
1137 close_nointr_nofail(entry_array_fd);