1 #include "libini2.h"
2 
3 #include "libini/ini.h"
4 
5 #include <errno.h>
6 #include <iio.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <glib.h>
10 
11 #ifdef _WIN32
12 #define LONG_LONG_FORMAT "%I64d"
13 #else
14 #define LONG_LONG_FORMAT "%lld"
15 #endif
16 
17 struct load_store_params {
18 	const struct iio_device *dev;
19 	const char * const *whitelist;
20 	size_t list_len;
21 	bool is_debug;
22 	FILE *f;
23 	struct INI *ini;
24 	const void *section_top;
25 };
26 
attr_matches(const char * dev_name,size_t dev_len,const char * attr,size_t attr_len,const char * key,size_t len,bool debug)27 static bool attr_matches(const char *dev_name, size_t dev_len,
28 		const char *attr, size_t attr_len,
29 		const char *key, size_t len, bool debug)
30 {
31 	return (!debug && (len == dev_len + 1 + attr_len) &&
32 			!strncmp(key, dev_name, dev_len) &&
33 			!strncmp(key + dev_len + 1, attr, attr_len)) ||
34 		(debug && (len == dev_len + sizeof("debug.") + attr_len) &&
35 			!strncmp(key, "debug.", sizeof("debug.") - 1) &&
36 			!strncmp(key + sizeof("debug.") - 1, dev_name, dev_len) &&
37 			!strncmp(key + sizeof("debug.") + dev_len, attr, attr_len));
38 }
39 
attr_in_whitelist(const char * attr,const char * dev_name,size_t dev_len,bool is_debug,const char * const * whitelist,size_t list_len)40 static bool attr_in_whitelist(const char *attr,
41 		const char *dev_name, size_t dev_len, bool is_debug,
42 		const char * const *whitelist, size_t list_len)
43 {
44 	unsigned int i;
45 	for (i = 0; i < list_len && whitelist[i]; i++)
46 		if ((!is_debug && !strncmp(whitelist[i], dev_name, dev_len) &&
47 					!strcmp(whitelist[i] + dev_len + 1, attr)) ||
48 				(is_debug && !strncmp(whitelist[i], "debug.", sizeof("debug.") - 1) &&
49 					!strncmp(whitelist[i] + sizeof("debug.") - 1, dev_name, dev_len) &&
50 					!strcmp(whitelist[i] + sizeof("debug.") + dev_len, attr)))
51 			return true;
52 	return false;
53 }
54 
read_from_ini(struct load_store_params * params,const char * dev_name,size_t name_len,const char * attr,void * buf,size_t len)55 static ssize_t read_from_ini(struct load_store_params *params,
56 		const char *dev_name, size_t name_len,
57 		const char *attr, void *buf, size_t len)
58 {
59 	bool found = false;
60 	const char *key, *value;
61 	size_t klen, vlen;
62 
63 	if (!len)
64 		return 0;
65 
66 	/* Rewind to the beginning of the section */
67 	ini_set_read_pointer(params->ini, params->section_top);
68 
69 	while (!found && ini_read_pair(params->ini,
70 				&key, &klen, &value, &vlen) > 0)
71 		found = attr_matches(dev_name, name_len, attr, strlen(attr),
72 				key, klen, params->is_debug);
73 	if (!found)
74 		return 0;
75 
76 	if (len > vlen)
77 		len = vlen;
78 	memcpy(buf, value, len);
79 	return (ssize_t) len;
80 }
81 
update_from_ini_dev_cb(struct iio_device * dev,const char * attr,void * buf,size_t len,void * d)82 static ssize_t update_from_ini_dev_cb(struct iio_device *dev,
83 		const char *attr, void *buf, size_t len, void *d)
84 {
85 	struct load_store_params *params = (struct load_store_params *) d;
86 	const char *dev_name = iio_device_get_name(dev);
87 	size_t name_len = dev_name ? strlen(dev_name) : 0;
88 
89 	if (attr_in_whitelist(attr, dev_name, name_len, params->is_debug,
90 				params->whitelist, params->list_len))
91 		return read_from_ini(params,
92 				dev_name, name_len, attr, buf, len);
93 	return 0;
94 }
95 
update_from_ini_chn_cb(struct iio_channel * chn,const char * attr,void * buf,size_t len,void * d)96 static ssize_t update_from_ini_chn_cb(struct iio_channel *chn,
97 		const char *attr, void *buf, size_t len, void *d)
98 {
99 	struct load_store_params *params = (struct load_store_params *) d;
100 	const char *dev_name = iio_device_get_name(params->dev);
101 	size_t name_len = dev_name ? strlen(dev_name) : 0;
102 	bool is_hardwaregain = !strncmp(attr, "hardwaregain", len);
103 
104 	attr = iio_channel_attr_get_filename(chn, attr);
105 	if (attr_in_whitelist(attr, dev_name, name_len, false,
106 				params->whitelist, params->list_len)) {
107 		ssize_t ret = read_from_ini(params,
108 				dev_name, name_len, attr, buf, len);
109 
110 		/* Dirty workaround that strips the "dB" suffix of
111 		 * hardwaregain value. Fix me when possible. */
112 		if (is_hardwaregain) {
113 			char *tmp = strstr((char *) buf, " dB");
114 			if (tmp)
115 				*tmp = '\0';
116 		}
117 		return ret;
118 	}
119 	return 0;
120 }
121 
update_from_ini(const char * ini_file,const char * driver_name,struct iio_device * dev,const char * const * whitelist,size_t list_len)122 void update_from_ini(const char *ini_file,
123 		const char *driver_name, struct iio_device *dev,
124 		const char * const *whitelist, size_t list_len)
125 {
126 	bool found = false;
127 	const char *name;
128 	size_t nlen, dlen;
129 	unsigned int i;
130 	struct INI *ini = ini_open(ini_file);
131 	struct load_store_params params = {
132 		.dev = dev,
133 		.whitelist = whitelist,
134 		.list_len = list_len,
135 		.is_debug = false,
136 		.ini = ini,
137 	};
138 
139 	if (!ini) {
140 		fprintf(stderr, "ERROR: Cannot open INI file %s\n", ini_file);
141 		return;
142 	}
143 
144 	dlen = strlen(driver_name);
145 
146 	while (!found && ini_next_section(ini, &name, &nlen) > 0) {
147 		if (nlen == dlen)
148 			found = !strncmp(name, driver_name, nlen);
149 	}
150 
151 	if (!found) {
152 		fprintf(stderr, "error parsing %s file: Could not find %s\n",
153 				ini_file, driver_name);
154 		ini_close(ini);
155 		return;
156 	}
157 
158 	params.section_top = name + nlen + 1;
159 
160 	for (i = 0; i < iio_device_get_channels_count(dev); i++)
161 		iio_channel_attr_write_all(iio_device_get_channel(dev, i),
162 				update_from_ini_chn_cb, &params);
163 
164 	if (iio_device_get_attrs_count(dev))
165 		iio_device_attr_write_all(dev, update_from_ini_dev_cb, &params);
166 
167 	params.is_debug = true;
168 	iio_device_debug_attr_write_all(dev, update_from_ini_dev_cb, &params);
169 
170 	ini_close(ini);
171 }
172 
read_token_from_ini(const char * ini_file,const char * driver_name,const char * token)173 char * read_token_from_ini(const char *ini_file,
174 		const char *driver_name, const char *token)
175 {
176 	char *dup;
177 	const char *name, *key, *value;
178 	size_t nlen, klen, vlen, tlen = strlen(token);
179 	bool found = false;
180 	struct INI *ini = ini_open(ini_file);
181 	if (!ini)
182 		return NULL;
183 
184 	while (!found && ini_next_section(ini, &name, &nlen) > 0)
185 		found = !strncmp(name, driver_name, nlen);
186 	if (!found)
187 		return NULL;
188 
189 	found = false;
190 	while (!found && ini_read_pair(ini, &key, &klen, &value, &vlen) > 0)
191 		found = (tlen == klen) && !strncmp(token, key, klen);
192 	if (!found)
193 		return NULL;
194 
195 	dup = malloc(vlen + 1);
196 	snprintf(dup, vlen + 1, "%.*s", (int) vlen, value);
197 
198 	ini_close(ini);
199 	return dup;
200 }
201 
write_to_ini(struct load_store_params * params,const char * dev_name,size_t name_len,const char * attr,const char * val,size_t len)202 static void write_to_ini(struct load_store_params *params, const char *dev_name,
203 		size_t name_len, const char *attr, const char *val, size_t len)
204 {
205 	FILE *f = params->f;
206 
207 	if (params->is_debug)
208 		fwrite("debug.", 1, sizeof("debug.") - 1, f);
209 	fwrite(dev_name, 1, name_len, f);
210 	fwrite(".", 1, 1, f);
211 	fwrite(attr, 1, strlen(attr), f);
212 	fwrite(" = ", 1, sizeof(" = ") - 1, f);
213 	fwrite(val, 1, len - 1, f);
214 	fwrite("\n", 1, 1, f);
215 }
216 
save_to_ini_dev_cb(struct iio_device * dev,const char * attr,const char * val,size_t len,void * d)217 static int save_to_ini_dev_cb(struct iio_device *dev,
218 		const char *attr, const char *val, size_t len, void *d)
219 {
220 	struct load_store_params *params = (struct load_store_params *) d;
221 	const char *dev_name = iio_device_get_name(dev);
222 	size_t name_len = dev_name ? strlen(dev_name) : 0;
223 
224 	if (attr_in_whitelist(attr, dev_name, name_len, params->is_debug,
225 				params->whitelist, params->list_len))
226 		write_to_ini(params, dev_name, name_len, attr, val, len);
227 	return 0;
228 }
229 
save_to_ini_chn_cb(struct iio_channel * chn,const char * attr,const char * val,size_t len,void * d)230 static int save_to_ini_chn_cb(struct iio_channel *chn,
231 		const char *attr, const char *val, size_t len, void *d)
232 {
233 	struct load_store_params *params = (struct load_store_params *) d;
234 	const char *dev_name = iio_device_get_name(params->dev);
235 	size_t name_len = dev_name ? strlen(dev_name) : 0;
236 
237 	attr = iio_channel_attr_get_filename(chn, attr);
238 	if (attr_in_whitelist(attr, dev_name, name_len, false,
239 				params->whitelist, params->list_len))
240 		write_to_ini(params, dev_name, name_len, attr, val, len);
241 	return 0;
242 }
243 
save_to_ini(FILE * f,const char * driver_name,struct iio_device * dev,const char * const * whitelist,size_t list_len)244 void save_to_ini(FILE *f, const char *driver_name, struct iio_device *dev,
245 		const char * const *whitelist, size_t list_len)
246 {
247 	unsigned int i;
248 	struct load_store_params params = {
249 		.dev = dev,
250 		.whitelist = whitelist,
251 		.list_len = list_len,
252 		.is_debug = false,
253 		.f = f,
254 	};
255 
256 	write_driver_name_to_ini(f, driver_name);
257 
258 	for (i = 0; i < iio_device_get_channels_count(dev); i++)
259 		iio_channel_attr_read_all(iio_device_get_channel(dev, i),
260 				save_to_ini_chn_cb, &params);
261 	iio_device_attr_read_all(dev, save_to_ini_dev_cb, &params);
262 
263 	params.is_debug = true;
264 	iio_device_debug_attr_read_all(dev, save_to_ini_dev_cb, &params);
265 }
266 
foreach_in_ini(const char * ini_file,int (* cb)(int,const char *,const char *,const char *))267 int foreach_in_ini(const char *ini_file,
268 		int (*cb)(int, const char *, const char *, const char *))
269 {
270 	int ret = 0;
271 	const char *name, *key, *value;
272 	size_t nlen, klen, vlen;
273 	struct INI *ini = ini_open(ini_file);
274 	if (!ini)
275 		return -1;
276 
277 	while (ini_next_section(ini, &name, &nlen) > 0) {
278 		char *n = malloc(nlen + 1);
279 		if (!n)
280 			goto err_ini_close;
281 
282 		snprintf(n, nlen + 1, "%.*s", (int) nlen, name);
283 
284 		while (ini_read_pair(ini, &key, &klen, &value, &vlen) > 0) {
285 			int line = ini_get_line_number(ini, key);
286 			char *v, *k = malloc(klen + 1);
287 			if (!k) {
288 				free(n);
289 				goto err_ini_close;
290 			}
291 
292 			v = malloc(vlen + 1);
293 			if (!v) {
294 				free(k);
295 				free(n);
296 				goto err_ini_close;
297 			}
298 
299 			snprintf(k, klen + 1, "%.*s", (int) klen, key);
300 			snprintf(v, vlen + 1, "%.*s", (int) vlen, value);
301 
302 			ret = cb(line, n, k, v);
303 
304 			/* only needed when debugging - this should be done in each section
305 			if (ret < 0) {
306 				fprintf(stderr, "issue in '%s' file: Section:'%s' key:'%s' value:'%s'\n",
307 						ini_file, n, k, v);
308 			}
309 			*/
310 
311 			free(k);
312 			free(v);
313 
314 			if (ret < 0) {
315 				free(n);
316 				goto err_ini_close;
317 			}
318 
319 			if (ret > 0) {
320 				ret = 0;
321 				break;
322 			}
323 		}
324 
325 		free(n);
326 	}
327 
328 err_ini_close:
329 	ini_close(ini);
330 	return ret;
331 }
332 
333 /*
334  * Types of loops that can be handled while parsing .ini files
335  * INI_LOOP_SEQ - Loops through a sequence of numbers. Ini syntax:
336  * <SEQ> [var] [first] [increment] [last]
337  * </SEQ>
338  *
339  * INI_LOOP_FOR - Loops through a given list of values. Ini syntax:
340  * <FOR> [var] in {space-separated values}
341  * </FOR>
342  *
343  */
344 enum ini_loop_types {
345 	INI_LOOP_SEQ,
346 	INI_LOOP_FOR,
347 };
348 
349 /*
350  * Structure of a ini loop that stores loop properties and attributes.
351  * type - The type of the loop. See enum ini_loop_types.
352  * var - The variable of the loop that is used as an iterator.
353  * end_loop - The keyword used to mark the end of the loop.
354  * first - first iteration to start from.
355  * inc - incrementation step.
356  * last - last iteration.
357  * i - Holds the current iteration of the loop.
358  * for_values - Use only by the <FOR> loop to store the list of values that the
359  *              loop will iterate through.
360  */
361 struct ini_loop {
362 	int type;
363 	char var[128];
364 	char end_loop[128];
365 	long long first;
366 	long long inc;
367 	long long last;
368 	long long i;
369 	gchar **for_values;
370 };
371 
372 /*
373  * Structure of a set of parameters that are used when parsing and expanding
374  * an ini loop structure and all its inner loops.
375  * in_file - input ini file
376  * out_file - output (expanded) ini file
377  * ini_loops - list of loop and its inner loops
378  * unclosed_loops - flag is set when one of the loops is left unclosed in the
379  *                  input ini file.
380  */
381 struct loops_parse_params {
382 	FILE *in_file;
383 	FILE *out_file;
384 	GSList *ini_loops;
385 	bool unclosed_loops;
386 };
387 
loops_params_init(struct loops_parse_params * p,FILE * i,FILE * o)388 static void loops_params_init(struct loops_parse_params *p, FILE *i, FILE *o)
389 {
390 	if (p) {
391 		p->in_file = i;
392 		p->out_file = o;
393 		p->ini_loops = NULL;
394 		p->unclosed_loops = false;
395 	} else {
396 		printf("Structure to init in %s is invalid\n", __func__);
397 	}
398 }
399 
400 /*
401  * Check if a char array starts with a keyword and extracts the keyword.
402  * A keyword is any char array between '<' and '>' characters and is found at
403  * the beginning of the array and does not start with '/' character.
404  */
ini_line_begins_with_keyword(char * line,char * extracted_keyword)405 static bool ini_line_begins_with_keyword(char *line, char *extracted_keyword)
406 {
407 	char *keyword_start = strstr(line, "<");
408 	char *keyword_end = strstr(line, ">");
409 	bool ret = false;
410 
411 	if (keyword_start && (keyword_start - line == 0) && keyword_end &&
412 						*(++keyword_start) != '/') {
413 		strncpy(extracted_keyword, keyword_start,
414 				keyword_end - keyword_start);
415 		extracted_keyword[keyword_end - keyword_start] = 0;
416 		ret = true;
417 	}
418 
419 	return ret;
420 }
421 
422 /*
423  * Create and initialize a new ini_loop structure base on the supplied data.
424  * buf_with_loop - array containing loop instruction and all its parameters.
425  * loop_name - keyword used to identify the type of loop.
426  */
ini_loop_new(char * buf_with_loop,char * loop_name)427 static struct ini_loop * ini_loop_new(char *buf_with_loop, char *loop_name)
428 {
429 	struct ini_loop *loop = NULL;
430 	long long first, inc, last;
431 	int loop_type;
432 	char var[128];
433 	gchar *for_raw_list = NULL;
434 	gchar **for_values = NULL;
435 	int ret;
436 
437 	/* Check loop type. Base on that gather up all of it's parameters */
438 	if (!strncmp(loop_name, "SEQ", sizeof("SEQ") - 1)) {
439 		loop_type = INI_LOOP_SEQ;
440 
441 		ret = sscanf(buf_with_loop, "<SEQ> %s %lli %lli %lli",
442 				var, &first, &inc, &last);
443 		if (ret != 4) {
444 			ret = -EINVAL;
445 			fprintf(stderr, "Unrecognized SEQ line\n");
446 			goto err_close;
447 		}
448 	} else if (!strncmp(loop_name, "FOR", sizeof("FOR") - 1)) {
449 		loop_type = INI_LOOP_FOR;
450 
451 		ret = sscanf(buf_with_loop, "<FOR> %s in {", var);
452 		for_raw_list = g_strstr_len(buf_with_loop, -1, "{");
453 		if (!for_raw_list) {
454 			ret = -EINVAL;
455 			fprintf(stderr, "Unrecognized FOR line\n");
456 			goto err_close;
457 		}
458 		char *s = g_strdup(for_raw_list);
459 		s = g_strstrip(g_strdelimit(s, "{}\n", ' '));
460 		for_values = g_strsplit_set(s, " {}\n", -1);
461 		g_free(s);
462 		first = 0;
463 		inc = 1;
464 		last = g_strv_length(for_values) - 1;
465 	} else {
466 		ret = -EINVAL;
467 		fprintf(stderr, "Unrecognized %s loop keyword\n", loop_name);
468 		goto err_close;
469 	}
470 
471 	/* Store all necessary parameters of a loop */
472 	loop = calloc(sizeof(struct ini_loop), 1);
473 	if (!loop) {
474 		fprintf(stderr, "%s is %s", strerror(errno), __func__);
475 		goto err_close;
476 	}
477 	loop->type = loop_type;
478 	loop->first = first;
479 	loop->inc = inc;
480 	loop->last = last;
481 	if (snprintf(loop->var, sizeof(loop->var), "<%s>", var) == sizeof(loop->var))
482 		goto err_close;
483 
484 	if (snprintf(loop->end_loop, sizeof(loop->end_loop), "</%s>", loop_name) == sizeof(loop->end_loop))
485 		goto err_close;
486 
487 	loop->for_values = for_values;
488 
489 err_close:
490 
491 	return loop;
492 }
493 
494 /*
495  * Get the current iteration that the loop is at.
496  */
ini_loop_get_iteration(struct ini_loop * loop)497 static char * ini_loop_get_iteration(struct ini_loop *loop)
498 {
499 	char *iteration;
500 
501 	switch (loop->type) {
502 	case INI_LOOP_SEQ:
503 		iteration = g_strdup_printf(LONG_LONG_FORMAT, loop->i);
504 		break;
505 	case INI_LOOP_FOR:
506 		iteration = g_strdup(loop->for_values[loop->i]);
507 		break;
508 	default:
509 		iteration = NULL;
510 	}
511 
512 	return iteration;
513 }
514 
515 /*
516  * Expand a <loop>..</loop> structure and any other inner loop structures.
517  */
loop_expand(struct loops_parse_params * parse_params,char * buf_with_loop,char * loop_name)518 static int loop_expand(struct loops_parse_params *parse_params,
519 		char *buf_with_loop, char *loop_name)
520 {
521 	char *replace, *eol;
522 	char buf[1024];
523 	char inner_loop[128];
524 	size_t tmplen;
525 	fpos_t pos;
526 	long long i, first, inc, last;
527 	int ret = 0;
528 	struct ini_loop *iniloop;
529 	bool unclosed_loop = true;
530 
531 	iniloop = ini_loop_new(buf_with_loop, loop_name);
532 	if (!iniloop)
533 		goto err_close;
534 
535 	parse_params->ini_loops = g_slist_prepend(parse_params->ini_loops,
536 					iniloop);
537 
538 	first = iniloop->first;
539 	inc = iniloop->inc;
540 	last = iniloop->last;
541 	fgetpos(parse_params->in_file, &pos);
542 	for (i = first; inc > 0 ? i <= last : i >= last; i = i + inc) {
543 		fsetpos(parse_params->in_file, &pos);
544 		iniloop->i = i;
545 
546 		while (fgets(buf, sizeof(buf), parse_params->in_file) != NULL) {
547 			if (ini_line_begins_with_keyword(buf, inner_loop)) {
548 				ret = loop_expand(parse_params, buf,
549 					inner_loop);
550 				if (ret < 0) {
551 					goto err_close;
552 				}
553 			} else {
554 				if (!strncmp(buf, iniloop->end_loop,
555 						strlen(iniloop->end_loop))) {
556 					unclosed_loop = false;
557 					break;
558 				}
559 
560 				GSList *list, *node;
561 				struct ini_loop *loop;
562 
563 				list = parse_params->ini_loops;
564 				replace = NULL;
565 				for (node = list; node;
566 						node = g_slist_next(node)) {
567 					loop = (struct ini_loop *)node->data;
568 					replace = strstr(buf, loop->var);
569 					if (replace)
570 						break;
571 				}
572 				if (!replace) {
573 					fprintf(parse_params->out_file, "%s",
574 						buf);
575 					continue;
576 				}
577 
578 				tmplen = strlen(iniloop->var);
579 				eol = strchr(buf, '\0');
580 				gchar *it = ini_loop_get_iteration(loop);
581 				fprintf(parse_params->out_file, "%.*s%s%.*s",
582 					(int) (long) (replace - buf), buf,
583 					it, (int) (eol - replace - tmplen),
584 					(char *)((uintptr_t) replace + tmplen));
585 				if (it)
586 					g_free(it);
587 			}
588 		}
589 	}
590 
591 err_close:
592 
593 	if (iniloop) {
594 		parse_params->ini_loops = g_slist_remove(
595 					parse_params->ini_loops, iniloop);
596 		if (iniloop->for_values)
597 			g_strfreev(iniloop->for_values);
598 		free(iniloop);
599 	}
600 
601 	if (unclosed_loop) {
602 		parse_params->unclosed_loops = true;
603 		ret = -EINVAL;
604 	}
605 
606 	return ret;
607 }
608 
ini_unroll(const char * input,const char * output)609 int ini_unroll(const char *input, const char *output)
610 {
611 	FILE *in, *out;
612 	char buf[1024];
613 	char loop_name[128];
614 	int ret = 0;
615 	struct loops_parse_params loops_params;
616 
617 	in = fopen(input, "r");
618 	out = fopen (output, "w");
619 
620 	if (!in) {
621 		ret = -errno;
622 		fprintf(stderr, "Failed to open %s : %s\n", input,
623 			strerror(-ret));
624 		goto err_close;
625 	}
626 
627 	if (!out) {
628 		ret = -errno;
629 		fprintf(stderr, "Failed to open %s : %s\n", output,
630 			strerror(-ret));
631 		goto err_close;
632 	}
633 
634 	loops_params_init(&loops_params, in, out);
635 	while (fgets(buf, sizeof(buf), in) != NULL) {
636 		if (!buf[0])
637 			continue;
638 
639 		if (!ini_line_begins_with_keyword(buf, loop_name)) {
640 			fprintf(out, "%s", buf);
641 			continue;
642 		}
643 		if (!strcmp(loop_name, "COMMENT")) {
644 			while (fgets(buf, sizeof(buf), in) != NULL) {
645 				if (!strncmp(buf, "</COMMENT>", strlen("</COMMENT>")))
646 					break;
647 			}
648 			continue;
649 		}
650 
651 		ret = loop_expand(&loops_params, buf, loop_name);
652 		if (ret < 0) {
653 			if (loops_params.unclosed_loops)
654 				printf("loop isn't closed in %s\n", input);
655 			break;
656 		}
657 	}
658 
659 	err_close:
660 	fclose(in);
661 	fclose(out);
662 	return ret;
663 }
664 
write_driver_name_to_ini(FILE * f,const char * driver_name)665 void write_driver_name_to_ini(FILE *f, const char *driver_name)
666 {
667 	if (driver_name) {
668 		fwrite("\n[", 1, 2, f);
669 		fwrite(driver_name, 1, strlen(driver_name), f);
670 		fwrite("]\n", 1, 2, f);
671 	}
672 }