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
|
*.out
|
||||||
/build
|
/build
|
||||||
.venv/
|
.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
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
subdir('mda')
|
||||||
subdir('genxml')
|
subdir('genxml')
|
||||||
subdir('dev')
|
subdir('dev')
|
||||||
if with_intel_hasvk or with_intel_vk or with_gallium_crocus or with_gallium_iris
|
if with_intel_hasvk or with_intel_vk or with_gallium_crocus or with_gallium_iris
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue