mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2025-12-20 00:50:10 +01:00
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:
parent
186cd59cf2
commit
bccc0fa984
12 changed files with 1202 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,3 +6,4 @@
|
|||
*.out
|
||||
/build
|
||||
.venv/
|
||||
*.mda.tar
|
||||
|
|
|
|||
82
src/intel/mda/debug_archiver.c
Normal file
82
src/intel/mda/debug_archiver.c
Normal 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);
|
||||
}
|
||||
}
|
||||
32
src/intel/mda/debug_archiver.h
Normal file
32
src/intel/mda/debug_archiver.h
Normal 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
41
src/intel/mda/meson.build
Normal 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
211
src/intel/mda/slice.c
Normal 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
81
src/intel/mda/slice.h
Normal 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 */
|
||||
83
src/intel/mda/slice_test.cpp
Normal file
83
src/intel/mda/slice_test.cpp
Normal 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
132
src/intel/mda/slice_test.h
Normal 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
310
src/intel/mda/tar.c
Normal 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
70
src/intel/mda/tar.h
Normal 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
158
src/intel/mda/tar_test.cpp
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue