summaryrefslogtreecommitdiff
path: root/stat.c
blob: 4550a77a4c434af99adce06f5c37dfca3ca924f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/* SPDX-License-Identifier: GPL-2.0 */

/**
 *  \file stat.c Functions used for sending/receiving the status of para_server
 *  and para_audiod.
 */

#include "para.h"
#include "error.h"
#include "string.h"

/** The minimal length of a status item buffer. */
#define MIN_STAT_ITEM_LEN 9 /* 5 + 2 + 2, e.g. '0005 00:\n' */

/*
 * Read the size prefix of a status item buffer and return its value.
 *
 * Each status item sent by para_server is prefixed with its length, a four
 * digit hex number encoded in ascii format.
 */
static int read_size_header(const char *buf)
{
	int i, len = 0;

	for (i = 0; i < 4; i++) {
		unsigned char c = buf[i];
		len <<= 4;
		if (c >= '0' && c <= '9') {
			len += c - '0';
			continue;
		}
		if (c >= 'a' && c <= 'f') {
			len += c - 'a' + 10;
			continue;
		}
		return -1;
	}
	if (buf[4] != ' ')
		return -1;
	return len;
}

/**
 * Parse the status output of para_server or para_audiod.
 *
 * The item buffer is expected to contain status items in the format produced by
 * parser-friendly output mode of the stat command.
 *
 * \param item_buf The source buffer.
 * \param num_bytes The buffer length in bytes.
 * \param items Will be updated accordingly. Old values are freed.
 *
 * \return Mask of status items that have changed. Parse errors are logged
 * but are otherwise ignored.
 */
uint64_t parse_status_items(char *item_buf, size_t num_bytes, char **items)
{
	uint64_t mask = 0ULL;
	char *buf = item_buf;
	int len = num_bytes;

	for (;;) {
		int i, ret, item_len, item_num = 0;
		char *old, *new;
		if (len < MIN_STAT_ITEM_LEN)
			break;
		ret = read_size_header(buf);
		if (ret < 0)
			goto parse_error;
		item_len = ret;
		if (item_len > len - 5) /* item not complete */
			break;
		for (i = 0; i < 2; i++) {
			unsigned char c = buf[5 + i];
			item_num <<= 4;
			if (c >= '0' && c <= '9') {
				item_num += c - '0';
				continue;
			}
			if (c >= 'a' && c <= 'f') {
				item_num += c - 'a' + 10;
				continue;
			}
			goto parse_error;
		}
		if (buf[7] != ':' || buf[5 + item_len - 1] != '\n')
			goto parse_error;
		buf[5 + item_len - 1] = '\0';
		old = items[item_num];
		new = buf + 8;
		if (item_num >= NUM_STAT_ITEMS)
			PARA_WARNING_LOG("unknown status item %d: %s\n",
				item_num, new);
		else if (!old || strcmp(old, new)) {
			free(old);
			items[item_num] = para_strdup(new);
			mask |= 1ULL << item_num;
		}
		buf += 5 + item_len;
		len -= 5 + item_len;
		assert(len >= 0 && buf <= item_buf + num_bytes);
	}
	if (len != 0) {
		PARA_WARNING_LOG("len=%d, num_bytes=%zu\n", len, num_bytes);
		PARA_WARNING_LOG("buf: %s\n", buf);
		PARA_WARNING_LOG("mask: %" PRIu64 "\n", mask);
	}
	return mask;
parse_error:
	PARA_ERROR_LOG("status item parse error\n");
	return mask;
}