Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/quarry/runtime_c/generic_brf.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ quarry_generic_status_t quarry_qbs_get_string(const quarry_qbs_view_t*, uint16_t
quarry_generic_status_t quarry_qbs_record_name(const quarry_qbs_view_t*,
const quarry_qbs_record_view_t*,
quarry_string_view_t*);
/* Returns a non-owning view into the mandatory QBS identity string section. */
quarry_generic_status_t quarry_qbs_record_identity(const quarry_qbs_view_t*,
const quarry_qbs_record_view_t*,
quarry_string_view_t*);
quarry_generic_status_t quarry_qbs_field_name(const quarry_qbs_view_t*,
const quarry_qbs_record_view_t*, uint16_t,
quarry_string_view_t*);
Expand Down
33 changes: 33 additions & 0 deletions runtime_c/generic_brf.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,39 @@ quarry_generic_status_t quarry_qbs_record_name(const quarry_qbs_view_t* q,
return quarry_qbs_get_string(q, record->name_index, out);
}

quarry_generic_status_t quarry_qbs_record_identity(const quarry_qbs_view_t* q,
const quarry_qbs_record_view_t* record,
quarry_string_view_t* out) {
uint32_t offset;
size_t i;
bool belongs = false;
size_t start, length = 0U;
if (out != NULL)
*out = (quarry_string_view_t){NULL, 0U};
if (q == NULL || record == NULL || out == NULL || q->bytes == NULL || q->records == NULL)
return QUARRY_GENERIC_INVALID_ARGUMENT;
for (i = 0U; i < q->record_count; ++i) {
if (&q->records[i] == record) {
belongs = true;
break;
}
}
if (!belongs)
return QUARRY_GENERIC_INVALID_ARGUMENT;
if (q->iss_offset > q->size || q->iss_size > q->size - q->iss_offset)
return QUARRY_GENERIC_MALFORMED_QBS;
offset = record->identity_offset;
if (offset >= q->iss_size)
return QUARRY_GENERIC_MALFORMED_QBS;
start = (size_t)q->iss_offset + (size_t)offset;
while (length < q->iss_size - (size_t)offset && q->bytes[start + length] != 0U)
++length;
if (length == q->iss_size - (size_t)offset)
return QUARRY_GENERIC_MALFORMED_QBS;
*out = (quarry_string_view_t){(const char*)q->bytes + start, length};
return QUARRY_GENERIC_OK;
}

quarry_generic_status_t quarry_qbs_field_name(const quarry_qbs_view_t* q,
const quarry_qbs_record_view_t* record, uint16_t index,
quarry_string_view_t* out) {
Expand Down
14 changes: 14 additions & 0 deletions tests/tools/brf_inspect_tool_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,17 @@ TEST(BrfInspectToolTest, MalformedInputsFailWithoutStdout) {
EXPECT_NE(result.find("BRF validation failed"), std::string::npos);
std::filesystem::remove(bad);
}

TEST(BrfInspectToolTest, ListsReflectiveRecordsInQbsTableOrder) {
// The checked-in conformance QBS is reflective and exercises the public listing path.
const auto result = command("--qbs " + (fixture / "schema.qbs").string() + " --list-records");
EXPECT_EQ(result, "0\n2 Child (Child)\n3 Item (Item)\n1 Parent (Parent)\n\n---ERR---\n");
EXPECT_EQ(result.find("---ERR---\n\n"), std::string::npos);
}

TEST(BrfInspectToolTest, ListingRejectsBrfAndSelectors) {
const std::string prefix = "--qbs " + (fixture / "schema.qbs").string() + " --list-records";
EXPECT_NE(command(prefix + " --brf " + (fixture / "record.brf").string()).find("standalone QBS"), std::string::npos);
EXPECT_NE(command(prefix + " --record-id 1").find("standalone QBS"), std::string::npos);
EXPECT_NE(command(prefix + " --record-name Parent").find("standalone QBS"), std::string::npos);
}
16 changes: 16 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,22 @@ tool binary. It may still depend on system or package-manager-provided dynamic
libraries such as libyaml, Protobuf, and absl according to the platform and
build configuration. Those third-party libraries are not bundled by
Quarry.
## Record discovery with `quarry-brf-inspect`

To discover the records available in a QBS image, use the installed generic
BRF inspection tool without a BRF input:

```sh
quarry-brf-inspect --qbs build/workload.qbs --list-records
```

This prints one record per line in validated QBS record-table order. Each line
starts with the exact numeric ID accepted by `--record-id`, followed by the
canonical record identity accepted by `--record-name`. Reflective QBS images
append the optional display name in parentheses; minimal QBS images still
provide the canonical identity. QBS has no runtime root-record concept, so all
records present in the image, including referenced records, are listed.

## Protobuf translator toolchain

The isolated `quarry-protobuf-translator` uses the protobuf descriptor-set
Expand Down
31 changes: 27 additions & 4 deletions tools/brf_inspect/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void usage(FILE* out) {
" --brf PATH BRF record image, or - for stdin\n"
" --record-id ID Select the record by numeric ID\n"
" --record-name NAME Select the record by reflective name\n"
" --list-records List QBS records and exit (standalone mode)\n"
" --indent-width N Spaces per nesting level (default: 2)\n"
" --max-output-bytes N Output limit (default: unlimited)\n"
" -h, --help Show this help text\n");
Expand Down Expand Up @@ -71,10 +72,11 @@ void error_status(const char* what, quarry_generic_status_t status) {

int main(int argc, char** argv) {
std::string qbs_path, brf_path, record_name; size_t record_id = 0, indent = 2, output_limit = SIZE_MAX;
bool have_id = false, have_name = false;
bool have_id = false, have_name = false, list_records = false;
for (int i = 1; i < argc; ++i) {
const std::string a = argv[i];
if (a == "-h" || a == "--help") { usage(stdout); return 0; }
if (a == "--list-records") { if (list_records) { std::fprintf(stderr, "quarry-brf-inspect: duplicate --list-records option\n"); return 2; } list_records = true; continue; }
auto value = [&](const char* option, std::string& dst) -> bool {
if (a != option || i + 1 >= argc || !*argv[i + 1]) return false;
dst = argv[++i]; return true;
Expand All @@ -88,12 +90,16 @@ int main(int argc, char** argv) {
if (a == "--max-output-bytes" && i + 1 < argc && number(argv[i + 1], &output_limit)) { ++i; continue; }
std::fprintf(stderr, "quarry-brf-inspect: invalid option or value: %s\n", a.c_str()); usage(stderr); return 2;
}
if (qbs_path.empty() || brf_path.empty() || have_id == have_name ||
(have_id && record_id > UINT32_MAX)) {
if (list_records && (qbs_path.empty() || !brf_path.empty() || have_id || have_name)) {
std::fprintf(stderr, "quarry-brf-inspect: --list-records is a standalone QBS discovery mode and cannot be combined with BRF input or record selectors\n");
return 2;
}
if (!list_records && (qbs_path.empty() || brf_path.empty() || have_id == have_name ||
(have_id && record_id > UINT32_MAX))) {
std::fprintf(stderr, "quarry-brf-inspect: exactly one record selector is required\n"); usage(stderr); return 2;
}
std::vector<uint8_t> qbs_bytes, brf_bytes;
if (!read_input(qbs_path, qbs_bytes) || !read_input(brf_path, brf_bytes)) {
if (!read_input(qbs_path, qbs_bytes) || (!list_records && !read_input(brf_path, brf_bytes))) {
std::fprintf(stderr, "quarry-brf-inspect: unable to read input\n"); return 3;
}
const size_t rc = cap(qbs_bytes.size(), 29), fc = cap(qbs_bytes.size(), 28), tc = cap(qbs_bytes.size(), 16), ec = cap(qbs_bytes.size(), 16);
Expand All @@ -107,6 +113,23 @@ int main(int argc, char** argv) {
const quarry_generic_limits_t generic_limits{kMaxInput, kMaxInput, work, work, work};
quarry_qbs_view_t schema{}; auto status = quarry_qbs_parse(qbs_bytes.data(), qbs_bytes.size(), &schema, &ws, &generic_limits);
if (status != QUARRY_GENERIC_OK) { error_status("QBS parsing failed", status); return 4; }
if (list_records) {
for (size_t index = 0U; index < schema.record_count; ++index) {
const quarry_qbs_record_view_t* listed = &schema.records[index];
quarry_string_view_t identity{};
status = quarry_qbs_record_identity(&schema, listed, &identity);
if (status != QUARRY_GENERIC_OK) { error_status("record identity lookup failed", status); return 4; }
std::printf("%" PRIu32 " %.*s", listed->record_id, static_cast<int>(identity.size), identity.data);
quarry_string_view_t display{};
status = quarry_qbs_record_name(&schema, listed, &display);
if (status == QUARRY_GENERIC_OK)
std::printf(" (%.*s)", static_cast<int>(display.size), display.data);
else if (status != QUARRY_GENERIC_FIELD_ABSENT)
{ error_status("record display-name lookup failed", status); return 4; }
std::putchar('\n');
}
return 0;
}
const quarry_qbs_record_view_t* record = nullptr;
status = have_id ? quarry_qbs_find_record_by_id(&schema, static_cast<uint32_t>(record_id), &record) : quarry_qbs_find_record_by_name(&schema, record_name.c_str(), record_name.size(), &record);
if (status != QUARRY_GENERIC_OK) { error_status("record selection failed", status); return 5; }
Expand Down
Loading