intel/mda: Add code to produce mesa debug archives

Uses the tar format to collect multiple output files.  It can
be inspected using the regular UNIX tools, but a later commit
will add a specialized tool to perform common tasks.

The tar implementation is enough to fulfill the current needs
without adding a dependency.  There's also a small test mostly
to ensure scaffolding is there in case we need to expand the
implementation.

Acked-by: Kenneth Graunke <kenneth@whitecape.org>
Acked-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29146>
This commit is contained in:
Caio Oliveira 2023-06-09 23:54:51 -07:00
parent 186cd59cf2
commit bccc0fa984
12 changed files with 1202 additions and 0 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@
*.out
/build
.venv/
*.mda.tar

View file

@ -0,0 +1,82 @@
/*
* Copyright 2024 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include "debug_archiver.h"
#include "tar.h"
#include "util/ralloc.h"
#include <string.h>
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);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2024 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#ifndef MDA_DEBUG_ARCHIVE_H
#define MDA_DEBUG_ARCHIVE_H
#include <stdio.h>
#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 */

41
src/intel/mda/meson.build Normal file
View file

@ -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

211
src/intel/mda/slice.c Normal file
View file

@ -0,0 +1,211 @@
/*
* Copyright 2025 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <string.h>
#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);
}

81
src/intel/mda/slice.h Normal file
View file

@ -0,0 +1,81 @@
/*
* Copyright 2025 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#ifndef MDA_SLICE_H
#define MDA_SLICE_H
#include <stdbool.h>
#include <stdint.h>
#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 */

View file

@ -0,0 +1,83 @@
/*
* Copyright 2025 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include <gtest/gtest.h>
#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);
}

132
src/intel/mda/slice_test.h Normal file
View file

@ -0,0 +1,132 @@
/*
* Copyright 2025 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <gtest/gtest.h>
#include <string>
#include <type_traits>
#include <cstdio>
#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<typename T>
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<T>;
static_assert(std::is_same_v<DecayedT, slice> ||
std::is_same_v<DecayedT, const char*> ||
std::is_same_v<DecayedT, char*> ||
std::is_array_v<T>,
"Second argument must be slice, const char*, char*, or string literal");
if constexpr (std::is_same_v<DecayedT, const char*> ||
std::is_same_v<DecayedT, char*> ||
std::is_array_v<T>) {
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<T>)
ss << other;
else
ss << (other ? other : "(null)");
ss << "\"";
return testing::AssertionFailure() << ss.str();
} else {
static_assert(std::is_same_v<DecayedT, slice>);
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)

310
src/intel/mda/tar.c Normal file
View file

@ -0,0 +1,310 @@
/*
* Copyright 2023 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include "tar.h"
#include <assert.h>
#include <string.h>
#include <time.h>
/* 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;
}

70
src/intel/mda/tar.h Normal file
View file

@ -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 <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#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 */

158
src/intel/mda/tar_test.cpp Normal file
View file

@ -0,0 +1,158 @@
/*
* Copyright 2024 Intel Corporation
* SPDX-License-Identifier: MIT
*/
#include <gtest/gtest.h>
#include <cstring>
#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;
}

View file

@ -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