162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Lightweight buffered reading library.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2019 Google LLC.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#ifndef __API_IO__
862306a36Sopenharmony_ci#define __API_IO__
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <errno.h>
1162306a36Sopenharmony_ci#include <poll.h>
1262306a36Sopenharmony_ci#include <stdlib.h>
1362306a36Sopenharmony_ci#include <string.h>
1462306a36Sopenharmony_ci#include <unistd.h>
1562306a36Sopenharmony_ci#include <linux/types.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct io {
1862306a36Sopenharmony_ci	/* File descriptor being read/ */
1962306a36Sopenharmony_ci	int fd;
2062306a36Sopenharmony_ci	/* Size of the read buffer. */
2162306a36Sopenharmony_ci	unsigned int buf_len;
2262306a36Sopenharmony_ci	/* Pointer to storage for buffering read. */
2362306a36Sopenharmony_ci	char *buf;
2462306a36Sopenharmony_ci	/* End of the storage. */
2562306a36Sopenharmony_ci	char *end;
2662306a36Sopenharmony_ci	/* Currently accessed data pointer. */
2762306a36Sopenharmony_ci	char *data;
2862306a36Sopenharmony_ci	/* Read timeout, 0 implies no timeout. */
2962306a36Sopenharmony_ci	int timeout_ms;
3062306a36Sopenharmony_ci	/* Set true on when the end of file on read error. */
3162306a36Sopenharmony_ci	bool eof;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic inline void io__init(struct io *io, int fd,
3562306a36Sopenharmony_ci			    char *buf, unsigned int buf_len)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	io->fd = fd;
3862306a36Sopenharmony_ci	io->buf_len = buf_len;
3962306a36Sopenharmony_ci	io->buf = buf;
4062306a36Sopenharmony_ci	io->end = buf;
4162306a36Sopenharmony_ci	io->data = buf;
4262306a36Sopenharmony_ci	io->timeout_ms = 0;
4362306a36Sopenharmony_ci	io->eof = false;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci/* Reads one character from the "io" file with similar semantics to fgetc. */
4762306a36Sopenharmony_cistatic inline int io__get_char(struct io *io)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	char *ptr = io->data;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (io->eof)
5262306a36Sopenharmony_ci		return -1;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (ptr == io->end) {
5562306a36Sopenharmony_ci		ssize_t n;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci		if (io->timeout_ms != 0) {
5862306a36Sopenharmony_ci			struct pollfd pfds[] = {
5962306a36Sopenharmony_ci				{
6062306a36Sopenharmony_ci					.fd = io->fd,
6162306a36Sopenharmony_ci					.events = POLLIN,
6262306a36Sopenharmony_ci				},
6362306a36Sopenharmony_ci			};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci			n = poll(pfds, 1, io->timeout_ms);
6662306a36Sopenharmony_ci			if (n == 0)
6762306a36Sopenharmony_ci				errno = ETIMEDOUT;
6862306a36Sopenharmony_ci			if (n > 0 && !(pfds[0].revents & POLLIN)) {
6962306a36Sopenharmony_ci				errno = EIO;
7062306a36Sopenharmony_ci				n = -1;
7162306a36Sopenharmony_ci			}
7262306a36Sopenharmony_ci			if (n <= 0) {
7362306a36Sopenharmony_ci				io->eof = true;
7462306a36Sopenharmony_ci				return -1;
7562306a36Sopenharmony_ci			}
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci		n = read(io->fd, io->buf, io->buf_len);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		if (n <= 0) {
8062306a36Sopenharmony_ci			io->eof = true;
8162306a36Sopenharmony_ci			return -1;
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci		ptr = &io->buf[0];
8462306a36Sopenharmony_ci		io->end = &io->buf[n];
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci	io->data = ptr + 1;
8762306a36Sopenharmony_ci	return *ptr;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci/* Read a hexadecimal value with no 0x prefix into the out argument hex. If the
9162306a36Sopenharmony_ci * first character isn't hexadecimal returns -2, io->eof returns -1, otherwise
9262306a36Sopenharmony_ci * returns the character after the hexadecimal value which may be -1 for eof.
9362306a36Sopenharmony_ci * If the read value is larger than a u64 the high-order bits will be dropped.
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_cistatic inline int io__get_hex(struct io *io, __u64 *hex)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	bool first_read = true;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	*hex = 0;
10062306a36Sopenharmony_ci	while (true) {
10162306a36Sopenharmony_ci		int ch = io__get_char(io);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		if (ch < 0)
10462306a36Sopenharmony_ci			return ch;
10562306a36Sopenharmony_ci		if (ch >= '0' && ch <= '9')
10662306a36Sopenharmony_ci			*hex = (*hex << 4) | (ch - '0');
10762306a36Sopenharmony_ci		else if (ch >= 'a' && ch <= 'f')
10862306a36Sopenharmony_ci			*hex = (*hex << 4) | (ch - 'a' + 10);
10962306a36Sopenharmony_ci		else if (ch >= 'A' && ch <= 'F')
11062306a36Sopenharmony_ci			*hex = (*hex << 4) | (ch - 'A' + 10);
11162306a36Sopenharmony_ci		else if (first_read)
11262306a36Sopenharmony_ci			return -2;
11362306a36Sopenharmony_ci		else
11462306a36Sopenharmony_ci			return ch;
11562306a36Sopenharmony_ci		first_read = false;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci/* Read a positive decimal value with out argument dec. If the first character
12062306a36Sopenharmony_ci * isn't a decimal returns -2, io->eof returns -1, otherwise returns the
12162306a36Sopenharmony_ci * character after the decimal value which may be -1 for eof. If the read value
12262306a36Sopenharmony_ci * is larger than a u64 the high-order bits will be dropped.
12362306a36Sopenharmony_ci */
12462306a36Sopenharmony_cistatic inline int io__get_dec(struct io *io, __u64 *dec)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	bool first_read = true;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	*dec = 0;
12962306a36Sopenharmony_ci	while (true) {
13062306a36Sopenharmony_ci		int ch = io__get_char(io);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		if (ch < 0)
13362306a36Sopenharmony_ci			return ch;
13462306a36Sopenharmony_ci		if (ch >= '0' && ch <= '9')
13562306a36Sopenharmony_ci			*dec = (*dec * 10) + ch - '0';
13662306a36Sopenharmony_ci		else if (first_read)
13762306a36Sopenharmony_ci			return -2;
13862306a36Sopenharmony_ci		else
13962306a36Sopenharmony_ci			return ch;
14062306a36Sopenharmony_ci		first_read = false;
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/* Read up to and including the first newline following the pattern of getline. */
14562306a36Sopenharmony_cistatic inline ssize_t io__getline(struct io *io, char **line_out, size_t *line_len_out)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	char buf[128];
14862306a36Sopenharmony_ci	int buf_pos = 0;
14962306a36Sopenharmony_ci	char *line = NULL, *temp;
15062306a36Sopenharmony_ci	size_t line_len = 0;
15162306a36Sopenharmony_ci	int ch = 0;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* TODO: reuse previously allocated memory. */
15462306a36Sopenharmony_ci	free(*line_out);
15562306a36Sopenharmony_ci	while (ch != '\n') {
15662306a36Sopenharmony_ci		ch = io__get_char(io);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		if (ch < 0)
15962306a36Sopenharmony_ci			break;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		if (buf_pos == sizeof(buf)) {
16262306a36Sopenharmony_ci			temp = realloc(line, line_len + sizeof(buf));
16362306a36Sopenharmony_ci			if (!temp)
16462306a36Sopenharmony_ci				goto err_out;
16562306a36Sopenharmony_ci			line = temp;
16662306a36Sopenharmony_ci			memcpy(&line[line_len], buf, sizeof(buf));
16762306a36Sopenharmony_ci			line_len += sizeof(buf);
16862306a36Sopenharmony_ci			buf_pos = 0;
16962306a36Sopenharmony_ci		}
17062306a36Sopenharmony_ci		buf[buf_pos++] = (char)ch;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci	temp = realloc(line, line_len + buf_pos + 1);
17362306a36Sopenharmony_ci	if (!temp)
17462306a36Sopenharmony_ci		goto err_out;
17562306a36Sopenharmony_ci	line = temp;
17662306a36Sopenharmony_ci	memcpy(&line[line_len], buf, buf_pos);
17762306a36Sopenharmony_ci	line[line_len + buf_pos] = '\0';
17862306a36Sopenharmony_ci	line_len += buf_pos;
17962306a36Sopenharmony_ci	*line_out = line;
18062306a36Sopenharmony_ci	*line_len_out = line_len;
18162306a36Sopenharmony_ci	return line_len;
18262306a36Sopenharmony_cierr_out:
18362306a36Sopenharmony_ci	free(line);
18462306a36Sopenharmony_ci	return -ENOMEM;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci#endif /* __API_IO__ */
188