1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2011 Free Electrons
4 * David Wagner <david.wagner@free-electrons.com>
5 *
6 * Inspired from envcrc.c:
7 * (C) Copyright 2001
8 * Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it
9 */
10
11#include <errno.h>
12#include <fcntl.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <stdint.h>
16#include <string.h>
17#include <unistd.h>
18#include <libgen.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <sys/mman.h>
22
23#include "compiler.h"
24#include <u-boot/crc.h>
25#include <version.h>
26
27#define CRC_SIZE sizeof(uint32_t)
28
29static void usage(const char *exec_name)
30{
31	fprintf(stderr, "%s [-h] [-r] [-b] [-p <byte>] -s <environment partition size> -o <output> <input file>\n"
32	       "\n"
33	       "This tool takes a key=value input file (same as would a `printenv' show) and generates the corresponding environment image, ready to be flashed.\n"
34	       "\n"
35	       "\tThe input file is in format:\n"
36	       "\t\tkey1=value1\n"
37	       "\t\tkey2=value2\n"
38	       "\t\t...\n"
39	       "\tEmpty lines are skipped, and lines with a # in the first\n"
40	       "\tcolumn are treated as comments (also skipped).\n"
41	       "\t-r : the environment has multiple copies in flash\n"
42	       "\t-b : the target is big endian (default is little endian)\n"
43	       "\t-p <byte> : fill the image with <byte> bytes instead of 0xff bytes\n"
44	       "\t-V : print version information and exit\n"
45	       "\n"
46	       "If the input file is \"-\", data is read from standard input\n",
47	       exec_name);
48}
49
50long int xstrtol(const char *s)
51{
52	long int tmp;
53
54	errno = 0;
55	tmp = strtol(s, NULL, 0);
56	if (!errno)
57		return tmp;
58
59	if (errno == ERANGE)
60		fprintf(stderr, "Bad integer format: %s\n",  s);
61	else
62		fprintf(stderr, "Error while parsing %s: %s\n", s,
63				strerror(errno));
64
65	exit(EXIT_FAILURE);
66}
67
68#define CHUNK_SIZE 4096
69
70int main(int argc, char **argv)
71{
72	uint32_t crc, targetendian_crc;
73	const char *bin_filename = NULL;
74	int txt_fd, bin_fd;
75	unsigned char *dataptr, *envptr;
76	unsigned char *filebuf = NULL;
77	unsigned int filesize = 0, envsize = 0, datasize = 0;
78	int bigendian = 0;
79	int redundant = 0;
80	unsigned char padbyte = 0xff;
81	int readbytes = 0;
82
83	int option;
84	int ret = EXIT_SUCCESS;
85
86	int fp, ep;
87	const char *prg;
88
89	prg = basename(argv[0]);
90
91	/* Turn off getopt()'s internal error message */
92	opterr = 0;
93
94	/* Parse the cmdline */
95	while ((option = getopt(argc, argv, ":s:o:rbp:hV")) != -1) {
96		switch (option) {
97		case 's':
98			datasize = xstrtol(optarg);
99			break;
100		case 'o':
101			bin_filename = strdup(optarg);
102			if (!bin_filename) {
103				fprintf(stderr, "Can't strdup() the output filename\n");
104				return EXIT_FAILURE;
105			}
106			break;
107		case 'r':
108			redundant = 1;
109			break;
110		case 'b':
111			bigendian = 1;
112			break;
113		case 'p':
114			padbyte = xstrtol(optarg);
115			break;
116		case 'h':
117			usage(prg);
118			return EXIT_SUCCESS;
119		case 'V':
120			printf("%s version %s\n", prg, PLAIN_VERSION);
121			return EXIT_SUCCESS;
122		case ':':
123			fprintf(stderr, "Missing argument for option -%c\n",
124				optopt);
125			usage(prg);
126			return EXIT_FAILURE;
127		default:
128			fprintf(stderr, "Wrong option -%c\n", optopt);
129			usage(prg);
130			return EXIT_FAILURE;
131		}
132	}
133
134	/* Check datasize and allocate the data */
135	if (datasize == 0) {
136		fprintf(stderr, "Please specify the size of the environment partition.\n");
137		usage(prg);
138		return EXIT_FAILURE;
139	}
140
141	dataptr = malloc(datasize * sizeof(*dataptr));
142	if (!dataptr) {
143		fprintf(stderr, "Can't alloc %d bytes for dataptr.\n",
144				datasize);
145		return EXIT_FAILURE;
146	}
147
148	/*
149	 * envptr points to the beginning of the actual environment (after the
150	 * crc and possible `redundant' byte
151	 */
152	envsize = datasize - (CRC_SIZE + redundant);
153	envptr = dataptr + CRC_SIZE + redundant;
154
155	/* Pad the environment with the padding byte */
156	memset(envptr, padbyte, envsize);
157
158	/* Open the input file ... */
159	if (optind >= argc || strcmp(argv[optind], "-") == 0) {
160		txt_fd = STDIN_FILENO;
161	} else {
162		txt_fd = open(argv[optind], O_RDONLY);
163		if (txt_fd == -1) {
164			fprintf(stderr, "Can't open \"%s\": %s\n",
165					argv[optind], strerror(errno));
166			return EXIT_FAILURE;
167		}
168	}
169
170	do {
171		filebuf = realloc(filebuf, filesize + CHUNK_SIZE);
172		if (!filebuf) {
173			fprintf(stderr, "Can't realloc memory for the input file buffer\n");
174			return EXIT_FAILURE;
175		}
176		readbytes = read(txt_fd, filebuf + filesize, CHUNK_SIZE);
177		if (readbytes < 0) {
178			fprintf(stderr, "Error while reading: %s\n",
179				strerror(errno));
180			return EXIT_FAILURE;
181		}
182		filesize += readbytes;
183	} while (readbytes > 0);
184
185	if (txt_fd != STDIN_FILENO)
186		ret = close(txt_fd);
187
188	/* Parse a byte at time until reaching the file OR until the environment fills
189	 * up. Check ep against envsize - 1 to allow for extra trailing '\0'. */
190	for (fp = 0, ep = 0 ; fp < filesize && ep < envsize - 1; fp++) {
191		if (filebuf[fp] == '\n') {
192			if (fp == 0 || filebuf[fp-1] == '\n') {
193				/*
194				 * Skip empty lines.
195				 */
196				continue;
197			} else if (filebuf[fp-1] == '\\') {
198				/*
199				 * Embedded newline in a variable.
200				 *
201				 * The backslash was added to the envptr; rewind
202				 * and replace it with a newline
203				 */
204				ep--;
205				envptr[ep++] = '\n';
206			} else {
207				/* End of a variable */
208				envptr[ep++] = '\0';
209			}
210		} else if ((fp == 0 || filebuf[fp-1] == '\n') && filebuf[fp] == '#') {
211			/* Comment, skip the line. */
212			while (++fp < filesize && filebuf[fp] != '\n')
213			continue;
214		} else {
215			envptr[ep++] = filebuf[fp];
216		}
217	}
218	/* If there are more bytes in the file still, it means the env filled up
219	 * before parsing the whole file.  Eat comments & whitespace here to see if
220	 * there was anything meaning full left in the file, and if so, throw a error
221	 * and exit. */
222	for( ; fp < filesize; fp++ )
223	{
224		if (filebuf[fp] == '\n') {
225			if (fp == 0 || filebuf[fp-1] == '\n') {
226				/* Ignore blank lines */
227				continue;
228			}
229		} else if ((fp == 0 || filebuf[fp-1] == '\n') && filebuf[fp] == '#') {
230			while (++fp < filesize && filebuf[fp] != '\n')
231			continue;
232		} else {
233			fprintf(stderr, "The environment file is too large for the target environment storage\n");
234			return EXIT_FAILURE;
235		}
236	}
237	/*
238	 * Make sure there is a final '\0'
239	 * And do it again on the next byte to mark the end of the environment.
240	 */
241	if (envptr[ep-1] != '\0') {
242		envptr[ep++] = '\0';
243		/*
244		 * The text file doesn't have an ending newline.  We need to
245		 * check the env size again to make sure we have room for two \0
246		 */
247		if (ep >= envsize) {
248			fprintf(stderr, "The environment file is too large for the target environment storage\n");
249			return EXIT_FAILURE;
250		}
251		envptr[ep] = '\0';
252	} else {
253		envptr[ep] = '\0';
254	}
255
256	/* Computes the CRC and put it at the beginning of the data */
257	crc = crc32(0, envptr, envsize);
258	targetendian_crc = bigendian ? cpu_to_be32(crc) : cpu_to_le32(crc);
259
260	memcpy(dataptr, &targetendian_crc, sizeof(targetendian_crc));
261	if (redundant)
262		dataptr[sizeof(targetendian_crc)] = 1;
263
264	if (!bin_filename || strcmp(bin_filename, "-") == 0) {
265		bin_fd = STDOUT_FILENO;
266	} else {
267		bin_fd = creat(bin_filename, S_IRUSR | S_IWUSR | S_IRGRP |
268					     S_IWGRP);
269		if (bin_fd == -1) {
270			fprintf(stderr, "Can't open output file \"%s\": %s\n",
271					bin_filename, strerror(errno));
272			return EXIT_FAILURE;
273		}
274	}
275
276	if (write(bin_fd, dataptr, sizeof(*dataptr) * datasize) !=
277			sizeof(*dataptr) * datasize) {
278		fprintf(stderr, "write() failed: %s\n", strerror(errno));
279		return EXIT_FAILURE;
280	}
281
282	ret = close(bin_fd);
283
284	return ret;
285}
286