diff --git a/.gitignore b/.gitignore index aef7c163f02..1ccc49fc360 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ *.out /build .venv/ +*.mda.tar diff --git a/src/intel/mda/debug_archiver.c b/src/intel/mda/debug_archiver.c new file mode 100644 index 00000000000..2cc6e0bd800 --- /dev/null +++ b/src/intel/mda/debug_archiver.c @@ -0,0 +1,82 @@ +/* + * Copyright 2024 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#include "debug_archiver.h" + +#include "tar.h" +#include "util/ralloc.h" + +#include + +struct debug_archiver +{ + FILE *f; + tar_writer *tw; + char prefix[128]; +}; + +debug_archiver * +debug_archiver_open(void *mem_ctx, const char *name, const char *info) +{ + debug_archiver *da = rzalloc(mem_ctx, debug_archiver); + + char *filename = ralloc_asprintf(mem_ctx, "%s.mda.tar", name); + da->f = fopen(filename, "wb+"); + + da->tw = rzalloc(da, tar_writer); + tar_writer_init(da->tw, da->f); + + debug_archiver_set_prefix(da, ""); + + tar_writer_start_file(da->tw, "mesa.txt"); + fprintf(da->f, "Mesa %s\n", info); + tar_writer_finish_file(da->tw); + + return da; +} + +void +debug_archiver_set_prefix(debug_archiver *da, const char *prefix) +{ + if (!prefix || !*prefix) { + strcpy(da->prefix, "mda"); + } else { + snprintf(da->prefix, ARRAY_SIZE(da->prefix) - 1, "mda/%s", prefix); + } + + da->tw->prefix = da->prefix; +} + +void +debug_archiver_write_file(debug_archiver *da, + const char *filename, + const char *data, unsigned size) +{ + tar_writer_start_file(da->tw, filename); + fwrite(data, size, 1, da->tw->file); + tar_writer_finish_file(da->tw); +} + +FILE * +debug_archiver_start_file(debug_archiver *da, const char *filename) +{ + tar_writer_start_file(da->tw, filename); + return da->f; +} + +void +debug_archiver_finish_file(debug_archiver *da) +{ + tar_writer_finish_file(da->tw); +} + +void +debug_archiver_close(debug_archiver *da) +{ + if (da != NULL) { + fclose(da->f); + ralloc_free(da); + } +} diff --git a/src/intel/mda/debug_archiver.h b/src/intel/mda/debug_archiver.h new file mode 100644 index 00000000000..385847f8497 --- /dev/null +++ b/src/intel/mda/debug_archiver.h @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#ifndef MDA_DEBUG_ARCHIVE_H +#define MDA_DEBUG_ARCHIVE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct debug_archiver debug_archiver; + +debug_archiver *debug_archiver_open(void *mem_ctx, const char *name, const char *info); + +void debug_archiver_set_prefix(debug_archiver *da, const char *prefix); + +void debug_archiver_write_file(debug_archiver *da, const char *filename, const char *data, unsigned size); + +FILE *debug_archiver_start_file(debug_archiver *da, const char *filename); +void debug_archiver_finish_file(debug_archiver *da); + +void debug_archiver_close(debug_archiver *da); + +#ifdef __cplusplus +} +#endif + +#endif /* MDA_DEBUG_ARCHIVE_H */ diff --git a/src/intel/mda/meson.build b/src/intel/mda/meson.build new file mode 100644 index 00000000000..8fa71759240 --- /dev/null +++ b/src/intel/mda/meson.build @@ -0,0 +1,41 @@ +# Copyright 2024 Intel Corporation +# SPDX-License-Identifier: MIT + +_libmda = static_library( + 'mda', + [ + 'debug_archiver.h', + 'debug_archiver.c', + 'slice.h', + 'slice.c', + 'tar.h', + 'tar.c', + ], + include_directories: [inc_src], + c_args: [c_msvc_compat_args], + gnu_symbol_visibility: 'hidden', +) + +idep_mda = declare_dependency( + link_with: _libmda, +) + +if with_tests + test('mda_tests', + executable( + 'mda_tests', + [ + 'tar_test.cpp', + 'slice_test.cpp', + ], + dependencies: [ + idep_gtest, + idep_mda, + idep_mesautil, + ], + c_args: [c_msvc_compat_args], + ), + suite: 'mda', + protocol: 'gtest', + ) +endif diff --git a/src/intel/mda/slice.c b/src/intel/mda/slice.c new file mode 100644 index 00000000000..3603fab17aa --- /dev/null +++ b/src/intel/mda/slice.c @@ -0,0 +1,211 @@ +/* + * Copyright 2025 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "util/ralloc.h" +#include "util/hash_table.h" + +#include "slice.h" + +slice +slice_from_cstr(const char *str) +{ + return (slice){ str, str ? strlen(str) : 0 }; +} + +bool +slice_is_empty(slice s) +{ + return s.len == 0; +} + +bool +slice_equal(slice a, slice b) +{ + return a.len == b.len && (a.len == 0 || memcmp(a.data, b.data, a.len) == 0); +} + +bool +slice_equal_cstr(slice s, const char *cstr) +{ + slice cstr_slice = slice_from_cstr(cstr); + return slice_equal(s, cstr_slice); +} + +char * +slice_to_cstr(void *mem_ctx, slice s) +{ + char *str = ralloc_size(mem_ctx, s.len + 1); + memcpy(str, s.data, s.len); + str[s.len] = '\0'; + return str; +} + +slice +slice_find_char(slice s, char c) +{ + for (int i = 0; i < s.len; i++) { + if (s.data[i] == c) { + return slice_substr_from(s, i); + } + } + return (slice){}; +} + +slice +slice_find_str(slice s, slice needle) +{ + if (needle.len == 0) + return s; + if (needle.len > s.len) + return (slice){}; + + for (int i = 0; i <= s.len - needle.len; i++) { + if (memcmp(s.data + i, needle.data, needle.len) == 0) { + return slice_substr_from(s, i); + } + } + return (slice){}; +} + +bool +slice_contains_str(slice s, slice needle) +{ + return !slice_is_empty(slice_find_str(s, needle)); +} + +bool +slice_starts_with(slice s, slice prefix) +{ + if (prefix.len > s.len) + return false; + return memcmp(s.data, prefix.data, prefix.len) == 0; +} + +bool +slice_ends_with(slice s, slice suffix) +{ + if (suffix.len > s.len) + return false; + return memcmp(s.data + s.len - suffix.len, suffix.data, suffix.len) == 0; +} + +slice +slice_strip_prefix(slice s, slice prefix) +{ + if (slice_starts_with(s, prefix)) + return slice_substr_from(s, prefix.len); + return s; +} + +slice +slice_substr_from(slice s, int start) +{ + if (start < 0) + start = 0; + if (start >= s.len) + return (slice){}; + return (slice){.data = s.data + start, .len = s.len - start}; +} + +slice +slice_substr_to(slice s, int end) +{ + if (end < 0) + end = 0; + if (end > s.len) + end = s.len; + return (slice){.data = s.data, .len = end}; +} + +slice +slice_substr(slice s, int start, int end) +{ + if (start < 0) + start = 0; + if (end > s.len) + end = s.len; + + if (start >= end) + return (slice){}; + return (slice){.data = s.data + start, .len = end - start}; +} + +slice_cut_result +slice_cut(slice s, char c) +{ + return slice_cut_n(s, c, 1); +} + +slice_cut_result +slice_cut_n(slice s, char c, int n) +{ + if (n <= 0) { + slice_cut_result result = { .before = s, .after = {}, .found = false }; + return result; + } + + slice current = s; + int count = 0; + + while (!slice_is_empty(current)) { + slice found = slice_find_char(current, c); + if (slice_is_empty(found)) + break; + + count++; + if (count == n) { + int pos = found.data - s.data; + slice_cut_result result = { + .before = slice_substr_to(s, pos), + .after = slice_substr_from(s, pos + 1), + .found = true + }; + return result; + } + + /* Move past this occurrence. */ + current = slice_substr_from(found, 1); + } + + /* Not enough occurrences found. */ + slice_cut_result result = { .before = s, .after = {}, .found = false }; + return result; +} + +static uint32_t +slice_ptr_hash(const void *key) +{ + const slice *s = (const slice *)key; + return _mesa_hash_string_with_length(s->data, s->len); +} + +static bool +slice_ptr_equal(const void *a, const void *b) +{ + const slice *sa = (const slice *)a; + const slice *sb = (const slice *)b; + return slice_equal(*sa, *sb); +} + +struct hash_table * +slice_hash_table_create(void *mem_ctx) +{ + return _mesa_hash_table_create(mem_ctx, slice_ptr_hash, slice_ptr_equal); +} + +struct hash_entry * +slice_hash_table_insert(struct hash_table *ht, slice key, void *data) +{ + /* Create a new slice that will be stable in memory, so its pointer can + * be used as key. + */ + slice *key_copy = rzalloc(ht, slice); + *key_copy = key; + + return _mesa_hash_table_insert(ht, key_copy, data); +} diff --git a/src/intel/mda/slice.h b/src/intel/mda/slice.h new file mode 100644 index 00000000000..a53c5e0f406 --- /dev/null +++ b/src/intel/mda/slice.h @@ -0,0 +1,81 @@ +/* + * Copyright 2025 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#ifndef MDA_SLICE_H +#define MDA_SLICE_H + +#include +#include + +#include "util/hash_table.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Non-owning string slice. Makes convenient to refer to parts of an existing + * buffer instead of duplicating into new strings. + */ +typedef struct slice { + const char *data; + int len; +} slice; + +/* To be used when printf formatting pattern "%.*s". */ +#define SLICE_FMT(s) (s).len, (s).data + +slice slice_from_cstr(const char *str); + +bool slice_is_empty(slice s); +bool slice_equal(slice a, slice b); +bool slice_equal_cstr(slice s, const char *cstr); +bool slice_contains_str(slice s, slice needle); +bool slice_starts_with(slice s, slice prefix); +bool slice_ends_with(slice s, slice suffix); + +char *slice_to_cstr(void *mem_ctx, slice s); + +slice slice_find_char(slice s, char c); +slice slice_find_str(slice s, slice needle); + +slice slice_strip_prefix(slice s, slice prefix); +slice slice_substr_from(slice s, int start); +slice slice_substr_to(slice s, int end); +slice slice_substr(slice s, int start, int end); + +typedef struct slice_cut_result { + slice before; + slice after; + bool found; +} slice_cut_result; + +slice_cut_result slice_cut(slice s, char c); +slice_cut_result slice_cut_n(slice s, char c, int n); + +/* Hash table support. + * + * Mesa src/util/hash_table.h has support for keys up to pointer + * size, so a slice by itself can't be stored directly in the + * same way a number would. So the functions below will use + * pointers to slices, but ensure that when _stored_ as keys, + * a copy of the slice itself will be made and owned by the + * hash table. + * + * Note that the contents of the slices themselves are not + * owned by the slices, so also not by the hash table. + */ + +struct hash_table *slice_hash_table_create(void *mem_ctx); +struct hash_entry *slice_hash_table_insert(struct hash_table *ht, slice key, void *data); + +static inline struct hash_entry *slice_hash_table_search(struct hash_table *ht, slice key) { + return _mesa_hash_table_search(ht, &key); +} + +#ifdef __cplusplus +} +#endif + +#endif /* MDA_SLICE_H */ diff --git a/src/intel/mda/slice_test.cpp b/src/intel/mda/slice_test.cpp new file mode 100644 index 00000000000..9ff806d2d03 --- /dev/null +++ b/src/intel/mda/slice_test.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2025 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#include + +#include "slice.h" +#include "slice_test.h" + +TEST(Slice, Cut) +{ + slice s = slice_from_cstr("hello:world"); + + slice_cut_result result = slice_cut(s, ':'); + ASSERT_TRUE(result.found); + ASSERT_SLICE_EQ(result.before, "hello"); + ASSERT_SLICE_EQ(result.after, "world"); + + slice s2 = slice_from_cstr("no separator"); + slice_cut_result result2 = slice_cut(s2, ':'); + ASSERT_FALSE(result2.found); + ASSERT_SLICE_EQ(result2.before, s2); + ASSERT_SLICE_EMPTY(result2.after); +} + +TEST(Slice, CutN) +{ + slice s = slice_from_cstr("a:b:c:d"); + + slice_cut_result result1 = slice_cut_n(s, ':', 2); + ASSERT_TRUE(result1.found); + ASSERT_SLICE_EQ(result1.before, "a:b"); + ASSERT_SLICE_EQ(result1.after, "c:d"); + + slice_cut_result result2 = slice_cut_n(s, ':', 1); + ASSERT_TRUE(result2.found); + ASSERT_SLICE_EQ(result2.before, "a"); + ASSERT_SLICE_EQ(result2.after, "b:c:d"); + + slice_cut_result result3 = slice_cut_n(s, ':', 5); + ASSERT_FALSE(result3.found); + ASSERT_SLICE_EQ(result3.before, s); + ASSERT_SLICE_EMPTY(result3.after); + + slice_cut_result result4 = slice_cut_n(s, ':', 0); + ASSERT_FALSE(result4.found); + slice_cut_result result5 = slice_cut_n(s, ':', -1); + ASSERT_FALSE(result5.found); +} + +TEST(Slice, HashTable) +{ + struct hash_table *ht = slice_hash_table_create(NULL); + + const char *strings[] = { + "NIR-CS/v1", "NIR-CS/v2", "BRW-CS/v1", "BRW-CS/v2", + "ASM-CS/v1", "ASM-CS/v2", "NIR-FS/v1", "BRW-FS/v1" + }; + int values[] = {1, 2, 3, 4, 5, 6, 7, 8}; + + for (int i = 0; i < 8; i++) { + slice key = slice_from_cstr(strings[i]); + slice_hash_table_insert(ht, key, &values[i]); + } + + ASSERT_EQ(_mesa_hash_table_num_entries(ht), 8u); + + for (int i = 0; i < 8; i++) { + slice key = slice_from_cstr(strings[i]); + struct hash_entry *found = slice_hash_table_search(ht, key); + ASSERT_NE(found, nullptr); + ASSERT_EQ(*(int*)found->data, values[i]); + } + + const int index = 2; + slice same_content = slice_from_cstr(strings[index]); + struct hash_entry *found = slice_hash_table_search(ht, same_content); + ASSERT_NE(found, nullptr); + ASSERT_EQ(*(int*)found->data, values[index]); + + _mesa_hash_table_destroy(ht, NULL); +} diff --git a/src/intel/mda/slice_test.h b/src/intel/mda/slice_test.h new file mode 100644 index 00000000000..9c3c8e88082 --- /dev/null +++ b/src/intel/mda/slice_test.h @@ -0,0 +1,132 @@ +/* + * Copyright 2025 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include + +#include "slice.h" + +static inline +std::ostream& operator<<(std::ostream& os, const slice& s) +{ + os << "{data=\""; + if (s.data) { + os << std::string(s.data, s.len) << "\""; + + bool has_nul = false; + for (int i = 0; i < s.len; i++) { + if (s.data[i] == '\0') { + has_nul = true; + break; + } + } + + if (has_nul) { + os << " [bytes: "; + for (int i = 0; i < s.len; i++) { + if (i > 0) os << " "; + char buf[3]; + snprintf(buf, sizeof(buf), "%02x", (unsigned char)s.data[i]); + os << buf; + } + os << "]"; + } + } else { + os << "(null)\""; + } + os << ", len=" << s.len << "}"; + return os; +} + +static inline testing::AssertionResult +AssertSliceEmpty(const char* slice_expr, slice s) +{ + if (slice_is_empty(s)) + return testing::AssertionSuccess(); + + return testing::AssertionFailure() + << slice_expr << " is not empty\n" + << " " << slice_expr << " = " << s; +} + +#define EXPECT_SLICE_EMPTY(slice) EXPECT_PRED_FORMAT1(AssertSliceEmpty, slice) +#define ASSERT_SLICE_EMPTY(slice) ASSERT_PRED_FORMAT1(AssertSliceEmpty, slice) + + +static inline testing::AssertionResult +AssertSliceNotEmpty(const char* slice_expr, slice s) +{ + if (!slice_is_empty(s)) + return testing::AssertionSuccess(); + + return testing::AssertionFailure() + << slice_expr << " is empty when it should not be\n" + << " " << slice_expr << " = " << s; +} + +#define EXPECT_SLICE_NOT_EMPTY(slice) EXPECT_PRED_FORMAT1(AssertSliceNotEmpty, slice) +#define ASSERT_SLICE_NOT_EMPTY(slice) ASSERT_PRED_FORMAT1(AssertSliceNotEmpty, slice) + + +/* Use generics here to be able to compare slice with not only other + * slices but also regular C strings (including literals). + */ +template +testing::AssertionResult AssertSliceEqual(const char* slice_expr, + const char* other_expr, + slice s, + const T& other) { + /* String literals have type const char[N], not const char*, so we need decay + * to convert array types to pointer types for uniform handling. Note each + * different size N would be have been a different type to handle below. + */ + using DecayedT = std::decay_t; + + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_array_v, + "Second argument must be slice, const char*, char*, or string literal"); + + if constexpr (std::is_same_v || + std::is_same_v || + std::is_array_v) { + if (slice_equal_cstr(s, other)) + return testing::AssertionSuccess(); + + std::stringstream ss; + ss << slice_expr << " and " << other_expr << " are not equal\n" + << " " << slice_expr << " = " << s << "\n" + << " " << other_expr << " = \""; + + if constexpr (std::is_array_v) + ss << other; + else + ss << (other ? other : "(null)"); + + ss << "\""; + + return testing::AssertionFailure() << ss.str(); + + } else { + static_assert(std::is_same_v); + + if (slice_equal(s, other)) + return testing::AssertionSuccess(); + + return testing::AssertionFailure() + << slice_expr << " and " << other_expr << " are not equal\n" + << " " << slice_expr << " = " << s << "\n" + << " " << other_expr << " = " << other; + } +} + +#define EXPECT_SLICE_EQ(slice, other) EXPECT_PRED_FORMAT2(AssertSliceEqual, slice, other) +#define ASSERT_SLICE_EQ(slice, other) ASSERT_PRED_FORMAT2(AssertSliceEqual, slice, other) + diff --git a/src/intel/mda/tar.c b/src/intel/mda/tar.c new file mode 100644 index 00000000000..c302f7999a8 --- /dev/null +++ b/src/intel/mda/tar.c @@ -0,0 +1,310 @@ +/* + * Copyright 2023 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#include "tar.h" + +#include +#include +#include + +/* A tar archive contain a sequence of files, each files is composed of a + * sequence of fixed size records. The first record of a file has header, + * defined by the table below: + * + * Field Name Byte Offset Length in Bytes Field Type + * name 0 100 NUL-terminated if NUL fits + * mode 100 8 + * uid 108 8 + * gid 116 8 + * size 124 12 + * mtime 136 12 + * chksum 148 8 + * typeflag 156 1 see below + * linkname 157 100 NUL-terminated if NUL fits + * magic 256 6 must be TMAGIC (NUL term.) + * version 263 2 must be TVERSION + * uname 265 32 NUL-terminated + * gname 297 32 NUL-terminated + * devmajor 329 8 + * devminor 337 8 + * prefix 345 155 NUL-terminated if NUL fits + * + * The subsequent records contain the file contents, with extra padding to + * fill a full record. After that the header for the next file starts. + * There's no archive-wide index. See the code below for how checksum is + * calculated. + * + * Comprehensive references for the tar archive are available in + * https://www.loc.gov/preservation/digital/formats/fdd/fdd000531.shtml + * + * Note: the tar_writer implementation uses only the features and fields + * needed for storing debug files. The archive_reader implementation covers + * only what's provided by the writer. + */ + +enum { + HEADER_NAME_OFFSET = 0, + HEADER_NAME_LENGTH = 100, + + HEADER_MODE_OFFSET = 100, + HEADER_MODE_LENGTH = 8, + + HEADER_SIZE_OFFSET = 124, + HEADER_SIZE_LENGTH = 12, + + HEADER_MTIME_OFFSET = 136, + HEADER_MTIME_LENGTH = 12, + + HEADER_CHECKSUM_OFFSET = 148, + HEADER_CHECKSUM_LENGTH = 8, + + HEADER_MAGIC_OFFSET = 257, + HEADER_MAGIC_LENGTH = 6, + + HEADER_VERSION_OFFSET = 263, + HEADER_VERSION_LENGTH = 2, + + HEADER_PREFIX_OFFSET = 345, + HEADER_PREFIX_LENGTH = 155, +}; + +static void +archive_update_size(char *header, unsigned size) +{ + snprintf(&header[HEADER_SIZE_OFFSET], HEADER_SIZE_LENGTH, "%011o", size); + + /* Checksum of the header assumes the checksum field itself is + * filled with ASCII spaces (value 32). + */ + memset(&header[HEADER_CHECKSUM_OFFSET], 32, HEADER_CHECKSUM_LENGTH); + unsigned checksum = 0; + for (int i = 0; i < RECORD_SIZE; i++) + checksum += header[i]; + snprintf(&header[HEADER_CHECKSUM_OFFSET], HEADER_CHECKSUM_LENGTH, "%07o", checksum); +} + +static void +archive_start_header(char *header, const char *prefix, const char *filename, time_t timestamp) +{ + /* NOTE: If we ever need more, implement the more complex `path` extension. */ + assert(!prefix || strlen(prefix) < HEADER_PREFIX_LENGTH); + assert(strlen(filename) < HEADER_NAME_LENGTH); + + strncpy(&header[HEADER_NAME_OFFSET], filename, HEADER_NAME_LENGTH); + + if (prefix) + strncpy(&header[HEADER_PREFIX_OFFSET], prefix, HEADER_PREFIX_LENGTH); + + const char *filemode = "0644"; + strcpy(&header[HEADER_MODE_OFFSET], filemode); + + snprintf(&header[HEADER_MTIME_OFFSET], HEADER_MTIME_LENGTH, "%011lo", (unsigned long)timestamp); + + const char *ustar_magic = "ustar"; + strcpy(&header[HEADER_MAGIC_OFFSET], ustar_magic); + + const char *ustar_version = "00"; + strcpy(&header[HEADER_VERSION_OFFSET], ustar_version); +} + +static unsigned +archive_calculate_padding(unsigned size) +{ + const unsigned m = size % RECORD_SIZE; + return m ? RECORD_SIZE - m : 0; +} + +static const char archive_empty_records[RECORD_SIZE * 2] = {0}; + +static bool +archive_write_padding(FILE *archive, unsigned contents_size) +{ + const unsigned padding_size = archive_calculate_padding(contents_size); + return fwrite(archive_empty_records, padding_size, 1, archive) == 1; +} + +static void +archive_prewrite_end_of_archive(FILE *archive) +{ + /* Two empty records mark the proper end of the file, so always + * keep them but reposition the cursor so next write overwrites + * them. + */ + fwrite(&archive_empty_records, RECORD_SIZE * 2, 1, archive); + fflush(archive); + fseek(archive, -RECORD_SIZE * 2, SEEK_END); +} + +static bool +archive_file_from_bytes(FILE *archive, const char *prefix, const char *filename, + const char *contents, unsigned contents_size, time_t timestamp) +{ + char header[RECORD_SIZE] = {0}; + + archive_start_header(header, prefix, filename, timestamp); + archive_update_size(header, contents_size); + + if (fwrite(header, RECORD_SIZE, 1, archive) != 1) + return false; + + if (fwrite(contents, contents_size, 1, archive) != 1) + return false; + + archive_write_padding(archive, contents_size); + archive_prewrite_end_of_archive(archive); + + fflush(archive); + + return ferror(archive) == 0; +} + +void +tar_writer_init(tar_writer *tw, FILE *f) +{ + memset(tw, 0, sizeof(*tw)); + tw->file = f; + tw->header_pos = -1; + tw->error = false; + tw->prefix = NULL; + tw->timestamp = time(NULL); +} + +void +tar_writer_start_file(tar_writer *tw, const char *filename) +{ + assert(tw->header_pos == -1); + memset(tw->header, 0, RECORD_SIZE); + + archive_start_header(tw->header, tw->prefix, filename, tw->timestamp); + archive_update_size(tw->header, 0); + + tw->header_pos = ftell(tw->file); + if (tw->header_pos == -1) + goto fail; + + if (fwrite(tw->header, RECORD_SIZE, 1, tw->file) != 1) + goto fail; + + if (fflush(tw->file) != 0) + goto fail; + + return; + +fail: + tw->error = true; +} + +void +tar_writer_finish_file(tar_writer *tw) +{ + assert(tw->header_pos >= 0); + + const long end_pos = ftell(tw->file); + if (end_pos == -1) + goto fail; + + assert((tw->header_pos + RECORD_SIZE) <= end_pos); + const unsigned size = end_pos - tw->header_pos - RECORD_SIZE; + + archive_write_padding(tw->file, size); + + archive_update_size(tw->header, size); + + if (fseek(tw->file, tw->header_pos, SEEK_SET) == -1) + goto fail; + if (fwrite(tw->header, RECORD_SIZE, 1, tw->file) != 1) + goto fail; + if (fseek(tw->file, 0, SEEK_END) == -1) + goto fail; + + archive_prewrite_end_of_archive(tw->file); + + if (fflush(tw->file) != 0) + goto fail; + + goto end; + +fail: + tw->error = true; + +end: + tw->header_pos = -1; + memset(tw->header, 0, RECORD_SIZE); +} + +void +tar_writer_file_from_bytes(tar_writer *tw, const char *filename, + const char *contents, unsigned contents_size) +{ + assert(tw->header_pos == -1); + if (!archive_file_from_bytes(tw->file, tw->prefix, filename, contents, contents_size, tw->timestamp)) + tw->error = true; +} + +void +tar_reader_init_from_bytes(tar_reader *ar, const char *contents, unsigned contents_size) +{ + memset(ar, 0, sizeof(*ar)); + ar->contents = (slice){contents, contents_size}; +} + +bool +tar_reader_next(tar_reader *ar, tar_reader_entry *entry) +{ + if (ar->error) + return false; + + if (ar->pos >= ar->contents.len) + return false; + + if (ar->pos + RECORD_SIZE > ar->contents.len) { + ar->error = true; + return false; + } + + const char *header = &ar->contents.data[ar->pos]; + const char *name = &header[HEADER_NAME_OFFSET]; + const char *prefix = &header[HEADER_PREFIX_OFFSET]; + + ar->pos += RECORD_SIZE; + + /* The current writer enforces the NUL termination and padding, so for + * now let's rely on it. + */ + if (name[HEADER_NAME_LENGTH-1] != 0 || prefix[HEADER_PREFIX_LENGTH-1] != 0) { + ar->error = true; + return false; + } + + unsigned size = 0; + if (sscanf((const char *)&header[HEADER_SIZE_OFFSET], "%011o", &size) == EOF) { + ar->error = true; + return false; + } + + unsigned long mtime = 0; + if (sscanf((const char *)&header[HEADER_MTIME_OFFSET], "%011lo", &mtime) == EOF) { + ar->error = true; + return false; + } + + const unsigned padded_size = size + archive_calculate_padding(size); + if (ar->pos + padded_size > ar->contents.len) { + ar->error = true; + return false; + } + + slice contents = slice_substr(ar->contents, ar->pos, ar->pos + size); + ar->pos += padded_size; + + /* TODO: Verify checksum. */ + + entry->prefix = slice_from_cstr(prefix); + entry->name = slice_from_cstr(name); + entry->contents = contents; + entry->mtime = (time_t)mtime; + + return true; +} diff --git a/src/intel/mda/tar.h b/src/intel/mda/tar.h new file mode 100644 index 00000000000..f89fe705771 --- /dev/null +++ b/src/intel/mda/tar.h @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#ifndef MDA_TAR_H +#define MDA_TAR_H + +/* Subset of the tar archive format. The writer produces a fully valid tar file, + * and the reader is capable to read files procuded by that writer. + */ + +#include +#include +#include +#include +#include + +#include "slice.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + RECORD_SIZE = 512, +}; + +typedef long archive_pos; + +typedef struct tar_writer { + FILE *file; + archive_pos header_pos; + char header[RECORD_SIZE]; + bool error; + const char *prefix; + time_t timestamp; +} tar_writer; + +void tar_writer_init(tar_writer *tw, FILE *f); +void tar_writer_start_file(tar_writer *tw, const char *filename); +void tar_writer_finish_file(tar_writer *tw); +void tar_writer_file_from_bytes(tar_writer *tw, const char *filename, + const char *contents, unsigned contents_size); + +typedef struct { + slice contents; + + bool error; + + archive_pos pos; +} tar_reader; + +void tar_reader_init_from_bytes(tar_reader *tr, const char *contents, unsigned contents_size); + +typedef struct { + slice prefix; + slice name; + slice contents; + + time_t mtime; +} tar_reader_entry; + +bool tar_reader_next(tar_reader *tr, tar_reader_entry *entry); + +#ifdef __cplusplus +} +#endif + +#endif /* MDA_TAR_H */ diff --git a/src/intel/mda/tar_test.cpp b/src/intel/mda/tar_test.cpp new file mode 100644 index 00000000000..0b092ac1843 --- /dev/null +++ b/src/intel/mda/tar_test.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2024 Intel Corporation + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include "tar.h" + +TEST(Tar, RoundtripSmallFile) +{ + FILE *f = tmpfile(); + const char *test = "TEST TEST TEST"; + + { + tar_writer tw; + tar_writer_init(&tw, f); + + tar_writer_start_file(&tw, "test"); + + fwrite(test, strlen(test), 1, f); + + tar_writer_finish_file(&tw); + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + ASSERT_TRUE(size > 0); + ASSERT_TRUE(size % 512 == 0); + char *contents = new char[size]; + + fseek(f, 0, SEEK_SET); + fread(contents, size, 1, f); + fclose(f); + + { + tar_reader ar; + tar_reader_init_from_bytes(&ar, contents, size); + + tar_reader_entry entry; + + bool first_read = tar_reader_next(&ar, &entry); + ASSERT_TRUE(first_read); + + ASSERT_EQ(entry.name.len, 4); + ASSERT_TRUE(memcmp(entry.name.data, "test", 4) == 0); + + ASSERT_EQ(entry.contents.len, strlen(test)); + ASSERT_TRUE(memcmp(entry.contents.data, test, entry.contents.len) == 0); + + bool second_read = tar_reader_next(&ar, &entry); + ASSERT_FALSE(second_read); + } + + delete[] contents; +} + +TEST(Tar, RoundtripContentsWithRecordSize) +{ + FILE *f = tmpfile(); + + uint8_t test[512]; + + for (unsigned i = 0; i < sizeof(test); i++) { + test[i] = 'A' + (i % 26); + } + + { + tar_writer tw; + tar_writer_init(&tw, f); + tar_writer_file_from_bytes(&tw, "test", (const char*)test, sizeof(test)); + ASSERT_FALSE(tw.error); + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + ASSERT_TRUE(size > 0); + ASSERT_TRUE(size % 512 == 0); + char *contents = new char[size]; + + fseek(f, 0, SEEK_SET); + fread(contents, size, 1, f); + fclose(f); + + { + tar_reader ar; + tar_reader_init_from_bytes(&ar, contents, size); + ASSERT_FALSE(ar.error); + + tar_reader_entry entry; + + bool first_read = tar_reader_next(&ar, &entry); + ASSERT_TRUE(first_read); + + ASSERT_EQ(entry.name.len, 4); + ASSERT_TRUE(memcmp(entry.name.data, "test", 4) == 0); + + ASSERT_EQ(entry.contents.len, sizeof(test)); + ASSERT_TRUE(memcmp(entry.contents.data, test, sizeof(test)) == 0); + + bool second_read = tar_reader_next(&ar, &entry); + ASSERT_FALSE(second_read); + } + + delete[] contents; +} + +TEST(Tar, TimestampRoundtrip) +{ + FILE *f = tmpfile(); + + const char *test = "TEST TIMESTAMP"; + const time_t test_timestamp = 1234567890; // Known timestamp (February 13, 2009) + + { + tar_writer tw; + tar_writer_init(&tw, f); + tw.timestamp = test_timestamp; + + tar_writer_file_from_bytes(&tw, "timestamp_test", test, strlen(test)); + ASSERT_FALSE(tw.error); + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + ASSERT_TRUE(size > 0); + ASSERT_TRUE(size % 512 == 0); + char *contents = new char[size]; + + fseek(f, 0, SEEK_SET); + fread(contents, size, 1, f); + fclose(f); + + { + tar_reader ar; + tar_reader_init_from_bytes(&ar, contents, size); + ASSERT_FALSE(ar.error); + + tar_reader_entry entry; + + bool first_read = tar_reader_next(&ar, &entry); + ASSERT_TRUE(first_read); + + ASSERT_EQ(entry.name.len, strlen("timestamp_test")); + ASSERT_TRUE(memcmp(entry.name.data, "timestamp_test", strlen("timestamp_test")) == 0); + + ASSERT_EQ(entry.contents.len, strlen(test)); + ASSERT_TRUE(memcmp(entry.contents.data, test, strlen(test)) == 0); + + // Verify the timestamp matches + ASSERT_EQ(entry.mtime, test_timestamp); + + bool second_read = tar_reader_next(&ar, &entry); + ASSERT_FALSE(second_read); + } + + delete[] contents; +} diff --git a/src/intel/meson.build b/src/intel/meson.build index 6755f24e068..e5ef168c8a1 100644 --- a/src/intel/meson.build +++ b/src/intel/meson.build @@ -14,6 +14,7 @@ else endif endif +subdir('mda') subdir('genxml') subdir('dev') if with_intel_hasvk or with_intel_vk or with_gallium_crocus or with_gallium_iris