Files
Igor Opaniuk ee3d93fc22 ux: route remaining diagnostics through shared logging
USB warnings, Sahara ramdump failures, fatal CLI and archive errors,
and packet hex dumps bypass the ux helpers, so they never get a
--debug timestamp and leave gaps in the protocol timeline.

Route them through shared logging via new warn and die helpers that
keep the program-name prefix, errno text, and exit status. Output
without --debug is unchanged and hex dumps stay visible in both modes.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
2026-10-01 10:21:20 +02:00

356 lines
7.3 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2016, Bjorn Andersson <bjorn@kryo.se>
* All rights reserved.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <unistd.h>
#include "file.h"
#include "oscompat.h"
#include "qdl.h"
static uint8_t to_hex(uint8_t ch)
{
ch &= 0xf;
return ch <= 9 ? '0' + ch : 'a' + ch - 10;
}
void print_hex_dump(const char *prefix, const void *buf, size_t len)
{
const uint8_t *ptr = buf;
size_t linelen;
uint8_t ch;
char line[16 * 3 + 16 + 1];
int li;
unsigned int i;
unsigned int j;
for (i = 0; i < len; i += 16) {
linelen = MIN(16u, (size_t)(len - i));
li = 0;
for (j = 0; j < linelen; j++) {
ch = ptr[i + j];
line[li++] = to_hex(ch >> 4);
line[li++] = to_hex(ch);
line[li++] = ' ';
}
for (; j < 16; j++) {
line[li++] = ' ';
line[li++] = ' ';
line[li++] = ' ';
}
for (j = 0; j < linelen; j++) {
ch = ptr[i + j];
line[li++] = isprint(ch) ? ch : '.';
}
line[li] = '\0';
ux_info("%s %04x: %s\n", prefix, i, line);
}
}
unsigned int attr_as_unsigned(xmlNode *node, const char *attr, int *errors)
{
unsigned int ret;
xmlChar *value;
value = xmlGetProp(node, (xmlChar *)attr);
if (!value) {
(*errors)++;
return 0;
}
ret = (unsigned int)strtoul((char *)value, NULL, 0);
xmlFree(value);
return ret;
}
const char *attr_as_string(xmlNode *node, const char *attr, int *errors)
{
xmlChar *value;
char *ret = NULL;
value = xmlGetProp(node, (xmlChar *)attr);
if (!value) {
(*errors)++;
return NULL;
}
if (value[0] != '\0')
ret = strdup((char *)value);
xmlFree(value);
return ret;
}
bool attr_as_bool(xmlNode *node, const char *attr, int *errors)
{
xmlChar *value;
bool ret = false;
if (!xmlHasProp(node, (xmlChar *)attr))
return false;
value = xmlGetProp(node, (xmlChar *)attr);
if (!value) {
(*errors)++;
return false;
}
ret = (xmlStrcmp(value, (xmlChar *)"true") == 0);
xmlFree(value);
return ret;
}
static const char * const storage_types[] = {
[QDL_STORAGE_EMMC] = "emmc",
[QDL_STORAGE_NAND] = "nand",
[QDL_STORAGE_NVME] = "nvme",
[QDL_STORAGE_SPINOR] = "spinor",
[QDL_STORAGE_UFS] = "ufs",
};
const char *encode_storage_type(enum qdl_storage_type storage)
{
if ((unsigned int)storage >= ARRAY_SIZE(storage_types))
return NULL;
return storage_types[storage];
}
enum qdl_storage_type decode_storage_type(const char *storage)
{
unsigned int i;
if (!storage)
return QDL_STORAGE_UNKNOWN;
for (i = 0; i < ARRAY_SIZE(storage_types); i++)
if (storage_types[i] && !strcmp(storage, storage_types[i]))
return i;
return QDL_STORAGE_UNKNOWN;
}
/***
* parse_storage_address() - parse a storage address specifier
* @address: specifier to be parsed
* @physical_partition: physical partition
* @start_sector: start_sector
* @num_sectors: number of sectors
* @gpt_partition: GPT name
*
* This function parses the provided address specifier and detects the
* following patterns:
*
* N => physical partition N, sector 0
* N/S => physical partition N, sector S
* N/S+L => physical partition N, L sectors at sector S
* name => GPT partition name match across all physical partitions
* N/name => GPT partition name match within physical partition N
*
* @physical_partition is either the requested physical partition, or -1 if
* none is specified. Either @start_sector and @num_sectors, or @gpt_partition
* will represent the requested address, the other(s) will be zeroed.
*
* Returns: 0 on success, -1 on failure
*/
int parse_storage_address(const char *address, int *physical_partition,
unsigned int *start_sector, unsigned int *num_sectors,
char **gpt_partition)
{
unsigned long length = 0;
const char *ptr = address;
unsigned long sector = 0;
long partition;
char *end;
char *gpt = NULL;
errno = 0;
partition = strtol(ptr, &end, 10);
if (end == ptr) {
partition = -1;
gpt = strdup(ptr);
goto done;
}
if ((errno == ERANGE && partition == LONG_MAX) || partition < 0)
return -1;
if (end[0] == '\0')
goto done;
if (end[0] != '/')
return -1;
ptr = end + 1;
errno = 0;
sector = strtoul(ptr, &end, 10);
if (end == ptr) {
gpt = strdup(ptr);
goto done;
}
if (errno == ERANGE && sector == ULONG_MAX)
return -1;
if (end[0] == '\0')
goto done;
if (end[0] != '+')
return -1;
ptr = end + 1;
errno = 0;
length = strtoul(ptr, &end, 10);
if (end == ptr)
return -1;
if (errno == ERANGE && length == ULONG_MAX)
return -1;
if (length == 0)
return -1;
if (end[0] != '\0')
return -1;
done:
*physical_partition = partition;
*start_sector = sector;
*num_sectors = length;
*gpt_partition = gpt;
return 0;
}
/*
* Parse a --backend= value into an enum. "auto" maps to the meta-backend
* QDL_DEVICE_AUTO, which inside its open path runs a unified wait loop
* over libusb and (on Windows) the QUD SetupAPI enumeration, binding
* whichever first reaches an EDL device. Explicit "usb"/"qud" pin to a
* single concrete transport and skip the meta layer entirely.
*
* QDL_DEVICE_SIM is intentionally not selectable via --backend; --dry-run /
* --create-digests pick it implicitly.
*/
int decode_backend(const char *name, enum QDL_DEVICE_TYPE *out)
{
if (!name || !strcmp(name, "auto")) {
*out = QDL_DEVICE_AUTO;
return 0;
}
if (!strcmp(name, "usb")) {
*out = QDL_DEVICE_USB;
return 0;
}
if (!strcmp(name, "qud")) {
*out = QDL_DEVICE_QUD;
return 0;
}
return -1;
}
/*
* Split a "<file>" or "<file>::<selector>" argument into its filename and
* optional selector. Returns a newly allocated filename (which the caller
* frees) with @specifier pointing into it (or NULL when absent), or NULL on
* a malformed argument (empty, or more than one "::").
*/
char *qdl_split_specifier(const char *param, char **specifier)
{
char *filename;
char *tmp;
if (!param || !param[0])
return NULL;
filename = strdup(param);
if (!filename) {
ux_err("internal error: unable to allocate memory for argument\n");
return NULL;
}
*specifier = NULL;
tmp = strstr(filename, "::");
if (tmp) {
if (strstr(tmp + 2, "::")) {
free(filename);
return NULL;
}
*tmp = '\0';
if (!filename[0] || !tmp[2]) {
free(filename);
return NULL;
}
*specifier = tmp + 2;
}
return filename;
}
/**
* load_sahara_image() - Load the content of the given file into the image
* @filename: file to be loaded
* @image: Sahara image object to be populated
*
* Read the content of the given @filename into the given @image, update the
* @image->len, and then populate the @image->name for debugging purposes.
*
* Returns: 0 on success, -1 on error
*/
int load_sahara_image(struct qdl_zip *zip, const char *filename, struct sahara_image *image)
{
struct qdl_file file;
size_t len;
void *ptr;
int ret;
ret = qdl_file_open(zip, filename, &file);
if (ret < 0) {
ux_err("failed to read \"%s\"\n", filename);
return -1;
}
ptr = qdl_file_load(&file, &len);
if (!ptr || len == 0)
goto err_close;
image->name = strdup(filename);
image->ptr = ptr;
image->len = len;
qdl_file_close(&file);
return 0;
err_close:
qdl_file_close(&file);
return -1;
}
void sahara_images_free(struct sahara_image *images, size_t count)
{
for (size_t i = 0; i < count; i++) {
free(images[i].name);
free(images[i].ptr);
images[i] = (struct sahara_image){};
}
}