spa: Improve JSON error reporting

Add struct spa_error_location that holds information about some parsing
context such as the line and column number, error and line fragment
with the error.

Make spa_json_get_error() fill in the spa_error_location instead. Add
some error codes to the error state and use this to add a parsing reason
to the location.

Add a debug function to log the error location in a nice way. Also
add a FILE based debug context to log to any FILE.

Replace pw_properties_check_string() with
pw_properties_update_string_checked() and add
pw_properties_new_string_checked(). The check string behaviour can still
be done by setting props to NULL but the main purpose is to be able to
avoid parsing the json file twice in the future.

When using the old pw_properties_update_string(), log a warning to the
log when we fail to parse the complete string.

Use the new checked functions and the debug functions to report about
parsing errors in the tools and conf parsing.

This gives errors like:

```
> pw-loopback --playback-props '{ foo =  [ f : g ] }'
error: syntax error in --playback-props: Invalid array separator
line:      1 | { foo =  [ f : g ] }
col:      14 |              ^
```
This commit is contained in:
Wim Taymans 2024-03-27 15:31:48 +01:00
parent 96fb63dfa1
commit d4581755e6
17 changed files with 328 additions and 116 deletions

View File

@ -11,6 +11,7 @@ extern "C" {
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <spa/utils/defs.h>
/**
@ -31,6 +32,25 @@ struct spa_debug_context {
#define spa_debugc(_c,_fmt,...) (_c)?((_c)->log((_c),_fmt, ## __VA_ARGS__)):(void)spa_debug(_fmt, ## __VA_ARGS__)
static inline void spa_debugc_error_location(struct spa_debug_context *c,
struct spa_error_location *loc)
{
int i, skip = loc->col > 80 ? loc->col - 40 : 0;
char buf[80];
for (i = 0; (size_t)i < sizeof(buf)-1; i++) {
char ch = loc->location[i + skip];
if (ch == '\n' || ch == '\0')
break;
buf[i] = isspace(ch) ? ' ' : ch;
}
buf[i] = '\0';
spa_debugc(c, "line: %6d | %s%s", loc->line, skip ? "..." : "", buf);
for (i = 0; buf[i]; i++)
buf[i] = i+skip+1 == loc->col ? '^' : ' ';
spa_debugc(c, "col: %6d | %s%s", loc->col, skip ? " " : "", buf);
}
/**
* \}
*/

View File

@ -0,0 +1,62 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_DEBUG_FILE_H
#define SPA_DEBUG_FILE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <spa/utils/defs.h>
#include <spa/support/log.h>
#include <spa/debug/context.h>
#include <spa/debug/dict.h>
#include <spa/debug/format.h>
#include <spa/debug/mem.h>
#include <spa/debug/pod.h>
/**
* \addtogroup spa_debug
* \{
*/
struct spa_debug_file_ctx {
struct spa_debug_context ctx;
FILE *f;
};
SPA_PRINTF_FUNC(2,3)
static inline void spa_debug_file_log(struct spa_debug_context *ctx, const char *fmt, ...)
{
struct spa_debug_file_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_file_ctx, ctx);
va_list args;
va_start(args, fmt);
vfprintf(c->f, fmt, args); fputc('\n', c->f);
va_end(args);
}
#define SPA_DEBUG_FILE_INIT(_f) \
(struct spa_debug_file_ctx){ { spa_debug_file_log }, _f, }
#define spa_debug_file_error_location(f,loc,fmt,...) \
({ \
struct spa_debug_file_ctx c = SPA_DEBUG_FILE_INIT(f); \
if (fmt) spa_debugc(&c.ctx, fmt, __VA_ARGS__); \
spa_debugc_error_location(&c.ctx, loc); \
})
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_DEBUG_FILE_H */

View File

@ -83,6 +83,15 @@ static inline void spa_debug_log_log(struct spa_debug_context *ctx, const char *
spa_debugc_dict(&c.ctx, indent, dict); \
})
#define spa_debug_log_error_location(l,lev,loc,fmt,...) \
({ \
struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \
if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) { \
if (fmt) spa_debugc(&c.ctx, fmt, __VA_ARGS__); \
spa_debugc_error_location(&c.ctx, loc); \
} \
})
/**
* \}
*/

View File

@ -327,6 +327,13 @@ static inline bool spa_ptr_inside_and_aligned(const void *p1, size_t s1,
#define SPA_STRINGIFY_1(...) #__VA_ARGS__
#define SPA_STRINGIFY(...) SPA_STRINGIFY_1(__VA_ARGS__)
struct spa_error_location {
int line;
int col;
const char *location;
const char *reason;
};
#define spa_return_if_fail(expr) \
do { \
if (SPA_UNLIKELY(!(expr))) { \

View File

@ -34,6 +34,7 @@ struct spa_json {
const char *cur;
const char *end;
struct spa_json *parent;
#define SPA_JSON_ERROR_FLAG 0x100
uint32_t state;
uint32_t depth;
};
@ -57,27 +58,40 @@ static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub)
* is the length. Returns -1 on parse error, 0 on end of input. */
static inline int spa_json_next(struct spa_json * iter, const char **value)
{
int utf8_remain = 0;
int utf8_remain = 0, err = 0;
enum {
__NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT,
__ARRAY_FLAG = 0x10, /* in array context */
__PREV_ARRAY_FLAG = 0x20, /* depth=0 array context flag */
__ERROR_FLAG = 0x40,
__KEY_FLAG = 0x80, /* inside object key */
__SUB_FLAG = 0x100, /* not at top-level */
__KEY_FLAG = 0x40, /* inside object key */
__SUB_FLAG = 0x80, /* not at top-level */
__FLAGS = 0xff0,
__ERROR_SYSTEM = SPA_JSON_ERROR_FLAG,
__ERROR_INVALID_ARRAY_SEPARATOR,
__ERROR_EXPECTED_OBJECT_KEY,
__ERROR_EXPECTED_OBJECT_VALUE,
__ERROR_TOO_DEEP_NESTING,
__ERROR_EXPECTED_ARRAY_CLOSE,
__ERROR_EXPECTED_OBJECT_CLOSE,
__ERROR_MISMATCHED_BRACKET,
__ERROR_ESCAPE_NOT_ALLOWED,
__ERROR_CHARACTERS_NOT_ALLOWED,
__ERROR_INVALID_ESCAPE,
__ERROR_INVALID_STATE,
__ERROR_UNFINISHED_STRING,
};
uint64_t array_stack[8] = {0}; /* array context flags of depths 1...512 */
*value = iter->cur;
if (iter->state & __ERROR_FLAG)
if (iter->state & SPA_JSON_ERROR_FLAG)
return -1;
for (; iter->cur < iter->end; iter->cur++) {
unsigned char cur = (unsigned char)*iter->cur;
uint32_t flag;
#define _SPA_ERROR(reason) { err = __ERROR_ ## reason; goto error; }
again:
flag = iter->state & __FLAGS;
switch (iter->state & ~__FLAGS) {
@ -92,9 +106,9 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
continue;
case ':': case '=':
if (flag & __ARRAY_FLAG)
goto error;
_SPA_ERROR(INVALID_ARRAY_SEPARATOR);
if (!(flag & __KEY_FLAG))
goto error;
_SPA_ERROR(EXPECTED_OBJECT_KEY);
iter->state |= __SUB_FLAG;
continue;
case '#':
@ -115,7 +129,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
* accept array/object here.
*/
if ((iter->state & __SUB_FLAG) && !(flag & __KEY_FLAG))
goto error;
_SPA_ERROR(EXPECTED_OBJECT_KEY);
SPA_FLAG_CLEAR(flag, __KEY_FLAG);
}
iter->state = __STRUCT | __SUB_FLAG | flag;
@ -132,7 +146,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
SPA_FLAG_UPDATE(array_stack[(iter->depth-1) >> 6], mask, flag & __ARRAY_FLAG);
} else {
/* too deep */
goto error;
_SPA_ERROR(TOO_DEEP_NESTING);
}
*value = iter->cur;
@ -142,19 +156,19 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
return 1;
case '}': case ']':
if ((flag & __ARRAY_FLAG) && cur != ']')
goto error;
_SPA_ERROR(EXPECTED_ARRAY_CLOSE);
if (!(flag & __ARRAY_FLAG) && cur != '}')
goto error;
_SPA_ERROR(EXPECTED_OBJECT_CLOSE);
if (flag & __KEY_FLAG) {
/* incomplete key-value pair */
goto error;
_SPA_ERROR(EXPECTED_OBJECT_VALUE);
}
iter->state = __STRUCT | __SUB_FLAG | flag;
if (iter->depth == 0) {
if (iter->parent)
iter->parent->cur = iter->cur;
else
goto error;
_SPA_ERROR(MISMATCHED_BRACKET);
return 0;
}
--iter->depth;
@ -166,16 +180,16 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
SPA_FLAG_IS_SET(array_stack[(iter->depth-1) >> 6], mask));
} else {
/* too deep */
goto error;
_SPA_ERROR(TOO_DEEP_NESTING);
}
continue;
case '\\':
/* disallow bare escape */
goto error;
_SPA_ERROR(ESCAPE_NOT_ALLOWED);
default:
/* allow bare ascii */
if (!(cur >= 32 && cur <= 126))
goto error;
_SPA_ERROR(CHARACTERS_NOT_ALLOWED);
if (flag & __KEY_FLAG)
flag |= __SUB_FLAG;
if (!(flag & __ARRAY_FLAG))
@ -196,13 +210,13 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
return iter->cur - *value;
case '\\':
/* disallow bare escape */
goto error;
_SPA_ERROR(ESCAPE_NOT_ALLOWED);
default:
/* allow bare ascii */
if (cur >= 32 && cur <= 126)
continue;
}
goto error;
_SPA_ERROR(CHARACTERS_NOT_ALLOWED);
case __STRING:
switch (cur) {
case '\\':
@ -227,7 +241,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
if (cur >= 32 && cur <= 127)
continue;
}
goto error;
_SPA_ERROR(CHARACTERS_NOT_ALLOWED);
case __UTF8:
switch (cur) {
case 128 ... 191:
@ -235,7 +249,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
iter->state = __STRING | flag;
continue;
}
goto error;
_SPA_ERROR(CHARACTERS_NOT_ALLOWED);
case __ESC:
switch (cur) {
case '"': case '\\': case '/': case 'b': case 'f':
@ -243,7 +257,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
iter->state = __STRING | flag;
continue;
}
goto error;
_SPA_ERROR(INVALID_ESCAPE);
case __COMMENT:
switch (cur) {
case '\n': case '\r':
@ -251,17 +265,17 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
}
break;
default:
goto error;
_SPA_ERROR(INVALID_STATE);
}
}
if (iter->depth != 0 || iter->parent)
goto error;
_SPA_ERROR(MISMATCHED_BRACKET);
switch (iter->state & ~__FLAGS) {
case __STRING: case __UTF8: case __ESC:
/* string/escape not closed */
goto error;
_SPA_ERROR(UNFINISHED_STRING);
case __COMMENT:
/* trailing comment */
return 0;
@ -269,7 +283,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) {
/* incomplete key-value pair */
goto error;
_SPA_ERROR(EXPECTED_OBJECT_VALUE);
}
if ((iter->state & ~__FLAGS) != __STRUCT) {
@ -277,13 +291,14 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
return iter->cur - *value;
}
return 0;
#undef _SPA_ERROR
error:
iter->state |= __ERROR_FLAG;
iter->state = err;
while (iter->parent) {
if (iter->parent->state & __ERROR_FLAG)
if (iter->parent->state & SPA_JSON_ERROR_FLAG)
break;
iter->parent->state |= __ERROR_FLAG;
iter->parent->state = err;
iter->parent->cur = iter->cur;
iter = iter->parent;
}
@ -291,32 +306,52 @@ error:
}
/**
* Return whether parse error occurred, and its possible location.
* Return it there was a parse error, and its possible location.
*
* \since 1.1.0
*/
static inline bool spa_json_get_error(struct spa_json *iter, const char *start, int *line, int *col)
static inline bool spa_json_get_error(struct spa_json *iter, const char *start,
struct spa_error_location *loc)
{
int linepos = 1, colpos = 1;
const char *p;
static const char *reasons[] = {
"System error",
"Invalid array separator",
"Expected Object key",
"Expected Object value",
"Too deep nesting",
"Expected array close backet",
"Expected object close backet",
"Mismatched backet",
"Escape not allowed",
"Character not allowed",
"Invalid escape",
"Invalid state",
"Unfinished string",
"Expected key separtor",
};
if (!(iter->state & 0x40))
if (!(iter->state & SPA_JSON_ERROR_FLAG))
return false;
for (p = start; p && p != iter->cur; ++p) {
if (*p == '\n') {
linepos++;
colpos = 1;
} else {
colpos++;
if (loc) {
int linepos = 1, colpos = 1, code;
const char *p, *l;
for (l = p = start; p && p != iter->cur; ++p) {
if (*p == '\n') {
linepos++;
colpos = 1;
l = p+1;
} else {
colpos++;
}
}
code = SPA_CLAMP(iter->state & 0xff, 0u, SPA_N_ELEMENTS(reasons)-1);
loc->line = linepos;
loc->col = colpos;
loc->location = l;
loc->reason = code == 0 ? strerror(errno) : reasons[code];
}
if (line)
*line = linepos;
if (col)
*col = colpos;
return true;
}

View File

@ -36,6 +36,7 @@
#include <spa/utils/result.h>
#include <spa/utils/json.h>
#include <spa/utils/string.h>
#include <spa/debug/log.h>
#include "defs.h"
@ -159,7 +160,7 @@ static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len)
struct spa_json data = SPA_JSON_INIT(str, len);
struct spa_json rules;
char key[1024];
int line, col;
struct spa_error_location loc;
if (spa_json_enter_object(&data, &rules) <= 0)
spa_json_init(&rules, str, len);
@ -184,8 +185,9 @@ static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len)
this->device_rules = strndup(value, sz);
}
if (spa_json_get_error(&rules, str, &line, &col))
spa_log_error(this->log, "spa.bluez5 quirks syntax error, line:%d col:%d", line, col);
if (spa_json_get_error(&rules, str, &loc))
spa_debug_log_error_location(this->log, SPA_LOG_LEVEL_ERROR, &loc,
"spa.bluez5 quirks syntax error: %s", loc.reason);
}
static int load_conf(struct spa_bt_quirks *this, const char *path)

View File

@ -110,7 +110,7 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value,
encode_string(file, value, len);
}
if (spa_json_get_error(it, NULL, NULL, NULL))
if (spa_json_get_error(it, NULL, NULL))
return -EINVAL;
return 0;
@ -140,10 +140,11 @@ static int process_json(const char *filename, void *buf, size_t size)
fflush(stdout);
if (res < 0) {
int line, col;
struct spa_error_location loc;
if (spa_json_get_error(&it, buf, &line, &col))
fprintf(stderr, "syntax error in file '%s': at line:%d col:%d\n", filename, line, col);
if (spa_json_get_error(&it, buf, &loc))
fprintf(stderr, "syntax error in file '%s': at line:%d col:%d: %s\n",
filename, loc.line, loc.col, loc.reason);
else
fprintf(stderr, "error parsing file '%s': %s\n", filename, spa_strerror(res));

View File

@ -27,6 +27,7 @@
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/debug/log.h>
#include <pipewire/cleanup.h>
#include <pipewire/impl.h>
@ -384,10 +385,10 @@ int pw_conf_save_state(const char *prefix, const char *name, const struct pw_pro
static int conf_load(const char *path, struct pw_properties *conf)
{
char *data;
char *data = NULL;
struct stat sbuf;
int count;
int line = -1, col = -1;
struct spa_error_location loc = { 0 };
int res;
spa_autoclose int fd = open(path, O_CLOEXEC | O_RDONLY);
@ -401,12 +402,11 @@ static int conf_load(const char *path, struct pw_properties *conf)
if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
goto error;
if (!pw_properties_check_string(data, sbuf.st_size, &line, &col)) {
count = pw_properties_update_string_checked(conf, data, sbuf.st_size, &loc);
if (count < 0) {
errno = EINVAL;
goto error;
}
count = pw_properties_update_string(conf, data, sbuf.st_size);
munmap(data, sbuf.st_size);
} else {
count = 0;
@ -418,8 +418,9 @@ static int conf_load(const char *path, struct pw_properties *conf)
error:
res = -errno;
if (line != -1)
pw_log_warn("%p: syntax error in config '%s': line:%d col:%d", conf, path, line, col);
if (loc.line != 0)
spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_WARN, &loc,
"%p: error in config '%s': %s", conf, path, loc.reason);
else
pw_log_warn("%p: error loading config '%s': %m", conf, path);
return res;

View File

@ -9,6 +9,7 @@
#include <spa/utils/json.h>
#include <spa/utils/string.h>
#include <spa/utils/cleanup.h>
#include <spa/debug/log.h>
#include "pipewire/array.h"
#include "pipewire/log.h"
@ -129,11 +130,14 @@ struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict)
return &impl->this;
}
static bool update_string(struct pw_properties *props, const char *str, size_t size,
int *count, int *err_line, int *err_col)
static int update_string(struct pw_properties *props, const char *str, size_t size,
int *count, struct spa_error_location *loc)
{
struct spa_json it[2];
char key[1024];
struct spa_error_location el;
bool err;
int cnt = 0;
spa_json_init(&it[0], str, size);
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
@ -155,14 +159,29 @@ static bool update_string(struct pw_properties *props, const char *str, size_t s
if (len <= 0)
break;
if (props && (val = malloc(len+1)) != NULL)
if (props) {
if ((val = malloc(len+1)) == NULL) {
errno = ENOMEM;
it[1].state = SPA_JSON_ERROR_FLAG;
break;
}
spa_json_parse_stringn(value, len, val, len+1);
}
}
if (props)
*count += pw_properties_set(props, key, val);
cnt += pw_properties_set(props, key, val);
}
return !spa_json_get_error(&it[1], str, err_line, err_col);
if ((err = spa_json_get_error(&it[1], str, &el))) {
if (loc == NULL)
spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_WARN,
&el, "error parsing more than %d properties: %s",
cnt, el.reason);
else
*loc = el;
}
if (count)
*count = cnt;
return !err;
}
/** Update the properties from the given string, overwriting any
@ -177,22 +196,38 @@ SPA_EXPORT
int pw_properties_update_string(struct pw_properties *props, const char *str, size_t size)
{
int count = 0;
update_string(props, str, size, &count, NULL, NULL);
update_string(props, str, size, &count, NULL);
return count;
}
/** Check \a str is a well-formed properties JSON string.
/** Check \a str is a well-formed properties JSON string and update
* the properties on success.
*
* \param line Return value for parse error line position
* \param col Return value for parse error column position
* \return true if string is valid
* \a str should be a whitespace separated list of key=value
* strings or a json object, see pw_properties_new_string().
*
* When the check fails, this function will not update \a props.
*
* \param props The properties to attempt to update, maybe be NULL
* to simply check the JSON string.
* \param str The JSON object with new values
* \param size The length of the JSON string.
* \param loc Return value for parse error location
* \return a negative value when string is not valid and \a loc contains
* the error location or the number of updated properties in
* \a props.
* \since 1.1.0
*/
SPA_EXPORT
bool pw_properties_check_string(const char *str, size_t size, int *line, int *col)
int pw_properties_update_string_checked(struct pw_properties *props,
const char *str, size_t size, struct spa_error_location *loc)
{
return update_string(NULL, str, size, NULL, line, col);
int count = 0;
if (!update_string(NULL, str, size, NULL, loc))
return -EINVAL;
if (props)
update_string(props, str, size, &count, NULL);
return count;
}
/** Make a new properties object from the given str
@ -224,6 +259,27 @@ error:
return NULL;
}
SPA_EXPORT
struct pw_properties *
pw_properties_new_string_checked(const char *object, size_t size, struct spa_error_location *loc)
{
struct properties *impl;
int res;
impl = properties_new(16);
if (impl == NULL)
return NULL;
if ((res = pw_properties_update_string_checked(&impl->this, object, size, loc)) < 0)
goto error;
return &impl->this;
error:
pw_properties_free(&impl->this);
errno = -res;
return NULL;
}
/** Copy a properties object
*
* \param properties properties to copy

View File

@ -40,6 +40,10 @@ pw_properties_new_dict(const struct spa_dict *dict);
struct pw_properties *
pw_properties_new_string(const char *args);
struct pw_properties *
pw_properties_new_string_checked(const char *args, size_t size,
struct spa_error_location *loc);
struct pw_properties *
pw_properties_copy(const struct pw_properties *properties);
@ -51,11 +55,13 @@ int pw_properties_update_ignore(struct pw_properties *props,
/* Update props with all key/value pairs from dict */
int pw_properties_update(struct pw_properties *props,
const struct spa_dict *dict);
/* Update props with all key/value pairs from str */
int pw_properties_update_string(struct pw_properties *props,
const char *str, size_t size);
bool pw_properties_check_string(const char *str, size_t size, int *line, int *col);
int pw_properties_update_string_checked(struct pw_properties *props,
const char *str, size_t size, struct spa_error_location *loc);
int pw_properties_add(struct pw_properties *oldprops,
const struct spa_dict *dict);

View File

@ -26,6 +26,7 @@
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/debug/types.h>
#include <spa/debug/file.h>
#include <pipewire/cleanup.h>
#include <pipewire/pipewire.h>
@ -1606,8 +1607,9 @@ int main(int argc, char *argv[])
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const char *prog;
int exit_code = EXIT_FAILURE, c, ret, line, col;
int exit_code = EXIT_FAILURE, c, ret;
enum pw_stream_flags flags = 0;
struct spa_error_location loc;
setlocale(LC_ALL, "");
pw_init(&argc, &argv);
@ -1729,11 +1731,12 @@ int main(int argc, char *argv[])
break;
case 'P':
if (!pw_properties_check_string(optarg, strlen(optarg), &line, &col)) {
fprintf(stderr, "error: syntax error in --properties at line:%d col:%d\n", line, col);
if (pw_properties_update_string_checked(data.props, optarg, strlen(optarg), &loc) < 0) {
spa_debug_file_error_location(stderr, &loc,
"error: syntax error in --properties: %s",
loc.reason);
goto error_usage;
}
pw_properties_update_string(data.props, optarg, strlen(optarg));
break;
case OPT_TARGET:

View File

@ -1405,17 +1405,13 @@ static bool do_info(struct data *data, const char *cmd, char *args, char **error
static struct pw_properties *properties_new_checked(const char *str, char **error)
{
struct pw_properties *props;
int line, col;
struct spa_error_location loc;
if (!pw_properties_check_string(str, strlen(str), &line, &col)) {
*error = spa_aprintf("syntax error in properties, line:%d col:%d", line, col);
return NULL;
props = pw_properties_new_string_checked(str, strlen(str), &loc);
if (!props) {
*error = spa_aprintf("syntax error in properties, line:%d col:%d: %s",
loc.line, loc.col, loc.reason);
}
props = pw_properties_new_string(str);
if (!props)
*error = spa_aprintf("failed to allocate properties");
return props;
}

View File

@ -18,6 +18,7 @@
#include <spa/debug/pod.h>
#include <spa/debug/format.h>
#include <spa/debug/types.h>
#include <spa/debug/file.h>
#include <pipewire/pipewire.h>
#include <pipewire/extensions/security-context.h>
@ -147,7 +148,8 @@ int main(int argc, char *argv[])
{ "properties", required_argument, NULL, 'P' },
{ NULL, 0, NULL, 0}
};
int c, res, listen_fd, close_fd[2], line, col;
struct spa_error_location loc;
int c, res, listen_fd, close_fd[2];
char temp[PATH_MAX] = "/tmp/pipewire-XXXXXX";
struct sockaddr_un sockaddr = {0};
@ -176,11 +178,12 @@ int main(int argc, char *argv[])
opt_remote = optarg;
break;
case 'P':
if (!pw_properties_check_string(optarg, strlen(optarg), &line, &col)) {
fprintf(stderr, "error: syntax error in --properties at line:%d col:%d\n", line, col);
if (pw_properties_update_string_checked(data.props, optarg, strlen(optarg), &loc) < 0) {
spa_debug_file_error_location(stderr, &loc,
"error: syntax error in --properties: %s",
loc.reason);
return -1;
}
pw_properties_update_string(data.props, optarg, strlen(optarg));
break;
default:
show_help(&data, argv[0], true);

View File

@ -16,6 +16,7 @@
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/debug/types.h>
#include <spa/debug/file.h>
#include <pipewire/pipewire.h>
@ -1277,7 +1278,7 @@ static int get_data_from_json(struct data *data, const char *json_path)
struct stat sbuf;
struct spa_json it[2];
const char *value;
int line, col;
struct spa_error_location loc;
if ((fd = open(json_path, O_CLOEXEC | O_RDONLY)) < 0) {
fprintf(stderr, "error opening file '%s': %m\n", json_path);
@ -1314,8 +1315,10 @@ static int get_data_from_json(struct data *data, const char *json_path)
munmap(json, sbuf.st_size);
if (spa_json_get_error(&it[0], json, &line, &col)) {
fprintf(stderr, "JSON syntax error on line:%d col:%d\n", line, col);
if (spa_json_get_error(&it[0], json, &loc)) {
spa_debug_file_error_location(stderr, &loc,
"JSON syntax error: %s\n",
loc.reason);
return -1;
}

View File

@ -13,6 +13,7 @@
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/utils/defs.h>
#include <spa/debug/file.h>
#include <pipewire/pipewire.h>
#include <pipewire/filter.h>
@ -873,7 +874,8 @@ static int run(int argc, char *argv[])
.objects = SPA_LIST_INIT(&data.objects),
.target_links = SPA_LIST_INIT(&data.target_links),
};
int res = 0, c, line, col;
int res = 0, c;
struct spa_error_location loc;
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@ -942,11 +944,11 @@ static int run(int argc, char *argv[])
pw_properties_set(data.props, PW_KEY_LINK_PASSIVE, "true");
break;
case 'p':
if (!pw_properties_check_string(optarg, strlen(optarg), &line, &col)) {
fprintf(stderr, "error: syntax error in --props at line:%d col:%d\n", line, col);
if (pw_properties_update_string_checked(data.props, optarg, strlen(optarg), &loc) < 0) {
spa_debug_file_error_location(stderr, &loc,
"error: syntax error in --props: %s", loc.reason);
return -1;
}
pw_properties_update_string(data.props, optarg, strlen(optarg));
break;
case 'd':
data.opt_mode = MODE_DISCONNECT;

View File

@ -16,6 +16,7 @@
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/raw.h>
#include <spa/utils/json.h>
#include <spa/debug/file.h>
#include <pipewire/pipewire.h>
#include <pipewire/impl.h>
@ -110,7 +111,8 @@ int main(int argc, char *argv[])
{ "playback-props", required_argument, NULL, 'o' },
{ NULL, 0, NULL, 0}
};
int c, res = -1, line, col;
int c, res = -1;
struct spa_error_location loc;
setlocale(LC_ALL, "");
pw_init(&argc, &argv);
@ -170,18 +172,22 @@ int main(int argc, char *argv[])
pw_properties_set(data.playback_props, PW_KEY_TARGET_OBJECT, optarg);
break;
case 'i':
if (!pw_properties_check_string(optarg, strlen(optarg), &line, &col)) {
fprintf(stderr, "error: syntax error in --capture-props at line:%d col:%d\n", line, col);
if (pw_properties_update_string_checked(data.capture_props,
optarg, strlen(optarg), &loc) < 0) {
spa_debug_file_error_location(stderr, &loc,
"error: syntax error in --capture-props: %s",
loc.reason);
return -1;
}
pw_properties_update_string(data.capture_props, optarg, strlen(optarg));
break;
case 'o':
if (!pw_properties_check_string(optarg, strlen(optarg), &line, &col)) {
fprintf(stderr, "error: syntax error in --playback-props at line:%d col:%d\n", line, col);
if (pw_properties_update_string_checked(data.playback_props,
optarg, strlen(optarg), &loc) < 0) {
spa_debug_file_error_location(stderr, &loc,
"error: syntax error in --playback-props: %s",
loc.reason);
return -1;
}
pw_properties_update_string(data.playback_props, optarg, strlen(optarg));
break;
default:
show_help(&data, argv[0], true);

View File

@ -87,12 +87,12 @@ static void expect_parse_error(struct spa_json *it, const char *str, int line, i
{
const char *value;
struct spa_json it2;
int linepos = 0, colpos = 0;
struct spa_error_location loc = { 0 };
pwtest_int_eq(spa_json_next(it, &value), -1);
pwtest_bool_true(spa_json_get_error(it, str, &linepos, &colpos));
pwtest_int_eq(linepos, line);
pwtest_int_eq(colpos, col);
pwtest_bool_true(spa_json_get_error(it, str, &loc));
pwtest_int_eq(loc.line, line);
pwtest_int_eq(loc.col, col);
/* parse error is idempotent also for parents */
while (it) {
@ -245,10 +245,10 @@ PWTEST(json_parse)
expect_end(&it[3]);
expect_end(&it[2]);
pwtest_bool_false(spa_json_get_error(&it[0], NULL, NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[1], NULL, NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[2], NULL, NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[3], NULL, NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[0], NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[1], NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[2], NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it[3], NULL, NULL));
json = "section={\"key\":value}, section2=[item1,item2]";
@ -891,7 +891,7 @@ static int validate_strict_json(struct spa_json *it, int depth, FILE *f)
}
done:
if (spa_json_get_error(it, NULL, NULL, NULL))
if (spa_json_get_error(it, NULL, NULL))
return -1;
return len;
@ -1052,9 +1052,9 @@ PWTEST(json_data)
fprintf(stdout, "%s (expect %s)\n", name, expect ? "fail" : "ok");
fflush(stdout);
pwtest_bool_eq(res == -2 || spa_json_get_error(&it, NULL, NULL, NULL), expect);
pwtest_bool_eq(res == -2 || spa_json_get_error(&it, NULL, NULL), expect);
if (res == -2)
pwtest_bool_false(spa_json_get_error(&it, NULL, NULL, NULL));
pwtest_bool_false(spa_json_get_error(&it, NULL, NULL));
if (result) {
while (strlen(result) > 0 && result[strlen(result) - 1] == '\n')