1 /* Hey EMACS -*- linux-c -*- */
2 /* $Id: grouped.c 1737 2006-01-23 12:54:47Z roms $ */
3 
4 /*  libtifiles - file format library, a part of the TiLP project
5  *  Copyright (C) 1999-2006  Romain Lievin
6  *  Copyright (C) 2006  Kevin Kofler
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software Foundation,
20  *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 /*
24 	TiGroup (*.tig) management
25 	A TiGroup file is in fact a ZIP archive with no compression (stored).
26 
27 	Please note that I don't use USEWIN32IOAPI!
28 */
29 
30 #ifdef HAVE_CONFIG_H
31 #  include <config.h>
32 #endif
33 
34 #include <glib.h>
35 #include <glib/gstdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 
39 #include <archive.h>
40 #include <archive_entry.h>
41 
42 #include <ticonv.h>
43 #include "tifiles.h"
44 #include "logging.h"
45 #include "error.h"
46 #include "rwfile.h"
47 
48 // Whether to print detailed information about TigEntry, TigContent instances throughout their lifecycle.
49 //#define TRACE_CONTENT_INSTANCES
50 
51 #define WRITEBUFFERSIZE (8192)
52 
53 /**
54  * tifiles_te_create:
55  * @filename: internal filename in archive.
56  * @type: file type (regular or flash)
57  * @model: calculator model
58  *
59  * Allocates a TigEntry structure and allocates fields (aka call #tifiles_content_create_flash/regular for you).
60  *
61  * Return value: the allocated block.
62  **/
tifiles_te_create(const char * filename,FileClass type,CalcModel model)63 TIEXPORT2 TigEntry* TICALL tifiles_te_create(const char *filename, FileClass type, CalcModel model)
64 {
65 	TigEntry *entry = NULL;
66 
67 	if (filename != NULL && strcmp(filename, ""))
68 	{
69 		entry = (TigEntry *)g_malloc0(sizeof(TigEntry));
70 		if (entry != NULL)
71 		{
72 			entry->filename = g_path_get_basename(filename);
73 			entry->type = type;
74 
75 			if (type == TIFILE_FLASH)
76 			{
77 				entry->content.flash = tifiles_content_create_flash(model);
78 			}
79 			else if (type & TIFILE_REGULAR)
80 			{
81 				entry->content.regular = tifiles_content_create_regular(model);
82 			}
83 		}
84 	}
85 	else
86 	{
87 		tifiles_critical("%s: invalid filename", __FUNCTION__);
88 	}
89 
90 #ifdef TRACE_CONTENT_INSTANCES
91 	tifiles_info("tifiles_te_create: %p", entry);
92 	tifiles_te_display(entry);
93 #endif
94 
95 	return entry;
96 }
97 
98 /**
99  * tifiles_te_delete:
100  * @entry: a #TigEntry structure.
101  *
102  * Destroy a #TigEntry structure as well as fields.
103  *
104  * Return value: always 0.
105  **/
tifiles_te_delete(TigEntry * entry)106 TIEXPORT2 int TICALL tifiles_te_delete(TigEntry* entry)
107 {
108 #ifdef TRACE_CONTENT_INSTANCES
109 	tifiles_info("tifiles_te_delete: %p", entry);
110 	tifiles_te_display(entry);
111 #endif
112 
113 	if (entry != NULL)
114 	{
115 		g_free(entry->filename);
116 
117 		if (entry->type == TIFILE_FLASH)
118 		{
119 			tifiles_content_delete_flash(entry->content.flash);
120 		}
121 		else if (entry->type & TIFILE_REGULAR)
122 		{
123 			tifiles_content_delete_regular(entry->content.regular);
124 		}
125 
126 		g_free(entry);
127 	}
128 	else
129 	{
130 		tifiles_critical("%s(NULL)", __FUNCTION__);
131 	}
132 
133 	return 0;
134 }
135 
136 /**
137  * tifiles_te_display:
138  * @entry: a #TigEntry structure pointer.
139  *
140  * Display a #TigEntry structure's contents.
141  *
142  * Return value: an error code, 0 otherwise.
143  **/
tifiles_te_display(TigEntry * entry)144 TIEXPORT2 int TICALL tifiles_te_display(TigEntry* entry)
145 {
146 	if (entry == NULL)
147 	{
148 		tifiles_critical("%s(NULL)", __FUNCTION__);
149 		return ERR_INVALID_FILE;
150 	}
151 
152 	tifiles_info("Filename:          %s", entry->filename);
153 	tifiles_info("File class:        %04X (%u)", entry->type, entry->type);
154 
155 	if (entry->type == TIFILE_FLASH)
156 	{
157 		tifiles_file_display_flash(entry->content.flash);
158 	}
159 	else if (entry->type & TIFILE_REGULAR)
160 	{
161 		tifiles_file_display_regular(entry->content.regular);
162 	}
163 	else
164 	{
165 		tifiles_info("Data:              %p", entry->content.data);
166 	}
167 
168 	return 0;
169 }
170 
171 /**
172  * tifiles_te_create_array:
173  * @nelts: size of NULL-terminated array (number of #TigEntry structures).
174  *
175  * Allocate a NULL-terminated array of #TigEntry structures. You have to allocate
176  * each element of the array by yourself.
177  *
178  * Return value: the array or NULL if error.
179  **/
tifiles_te_create_array(unsigned int nelts)180 TIEXPORT2 TigEntry**	TICALL tifiles_te_create_array(unsigned int nelts)
181 {
182 	return g_malloc0((nelts + 1) * sizeof(TigEntry *));
183 }
184 
185 /**
186  * tifiles_te_resize_array:
187  * @array: address of array
188  * @nelts: size of NULL-terminated array (number of #TigEntry structures).
189  *
190  * Re-allocate a NULL-terminated array of #TigEntry structures. You have to allocate
191  * each element of the array by yourself.
192  *
193  * Return value: the array or NULL if error.
194  **/
tifiles_te_resize_array(TigEntry ** array,unsigned int nelts)195 TIEXPORT2 TigEntry**	TICALL tifiles_te_resize_array(TigEntry** array, unsigned int nelts)
196 {
197 	TigEntry ** ptr = g_realloc(array, (nelts + 1) * sizeof(TigEntry *));
198 	if (ptr != NULL)
199 	{
200 		ptr[nelts] = NULL;
201 	}
202 	return ptr;
203 }
204 
205 /**
206  * tifiles_ve_delete_array:
207  * @array: an NULL-terminated array of TigEntry structures.
208  *
209  * Free the whole array (data buffer, TigEntry structure and array itself).
210  *
211  * Return value: none.
212  **/
tifiles_te_delete_array(TigEntry ** array)213 TIEXPORT2 void			TICALL tifiles_te_delete_array(TigEntry** array)
214 {
215 	TigEntry** ptr;
216 
217 #ifdef TRACE_CONTENT_INSTANCES
218 	tifiles_info("tifiles_te_delete_array: %p", array);
219 #endif
220 
221 	if (array != NULL)
222 	{
223 		for (ptr = array; *ptr; ptr++)
224 		{
225 			tifiles_te_delete(*ptr);
226 		}
227 		g_free(array);
228 	}
229 	else
230 	{
231 		tifiles_critical("%s(NULL)", __FUNCTION__);
232 	}
233 }
234 
235 /**
236  * tifiles_te_sizeof_array:
237  * @array: an NULL-terminated array of TigEntry structures.
238  * @r: number of FileContent entries
239  * @f: number of FlashContent entries
240  *
241  * Returns the size of a #TigEntry array.
242  *
243  * Return value: none.
244  **/
tifiles_te_sizeof_array(TigEntry ** array)245 TIEXPORT2 int TICALL tifiles_te_sizeof_array(TigEntry** array)
246 {
247 	int i = 0;
248 	TigEntry **p;
249 
250 	if (array != NULL)
251 	{
252 		for (p = array; *p; p++, i++);
253 	}
254 	else
255 	{
256 		tifiles_critical("%s(NULL)", __FUNCTION__);
257 	}
258 
259 	return i;
260 }
261 
262 // ---------------------------------------------------------------------------
263 
264 /**
265  * tifiles_content_add_te:
266  * @content: a file content (TiGroup).
267  * @te: the entry to add
268  *
269  * Adds the entry to the file content and updates internal structures.
270  * Beware: the entry is not duplicated.
271  *
272  * Return value: the number of entries.
273  **/
tifiles_content_add_te(TigContent * content,TigEntry * te)274 TIEXPORT2 int TICALL tifiles_content_add_te(TigContent *content, TigEntry *te)
275 {
276 	if (content == NULL || te == NULL)
277 	{
278 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
279 		return 0;
280 	}
281 
282 	if (te->type == TIFILE_FLASH)
283 	{
284 		int n = content->n_apps;
285 
286 		content->app_entries = tifiles_te_resize_array(content->app_entries, n + 1);
287 
288 		content->app_entries[n++] = te;
289 		content->app_entries[n] = NULL;
290 		content->n_apps = n;
291 
292 		return n;
293 	}
294 	else if (te->type & TIFILE_REGULAR)
295 	{
296 		int n = content->n_vars;
297 
298 		content->var_entries = tifiles_te_resize_array(content->var_entries, n + 1);
299 
300 		content->var_entries[n++] = te;
301 		content->var_entries[n] = NULL;
302 		content->n_vars = n;
303 
304 		return n;
305 	}
306 
307 	return 0;
308 }
309 
310 /**
311  * tifiles_content_del_te:
312  * @content: a file content (TiGroup).
313  * @te: the entry to remove
314  *
315  * Search for entry name and remove it from file content.
316  *
317  * Return value: the number of entries or -1 if not found.
318  **/
tifiles_content_del_te(TigContent * content,TigEntry * te)319 TIEXPORT2 int TICALL tifiles_content_del_te(TigContent *content, TigEntry *te)
320 {
321 	unsigned int i, j, k;
322 
323 	if (content == NULL || te == NULL)
324 	{
325 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
326 		return -1;
327 	}
328 
329 #ifdef TRACE_CONTENT_INSTANCES
330 	tifiles_info("tifiles_content_del_te: %p %p", content, te);
331 	tifiles_file_display_tigcontent(content);
332 	tifiles_te_display(te);
333 #endif
334 
335 	// Search for entry
336 	for (i = 0; i < content->n_vars && (te->type & TIFILE_REGULAR); i++)
337 	{
338 		TigEntry *s = content->var_entries[i];
339 
340 		if (!strcmp(s->filename, te->filename))
341 		{
342 			break;
343 		}
344 	}
345 
346 	for (j = 0; j < content->n_apps && (te->type & TIFILE_FLASH); j++)
347 	{
348 		TigEntry *s = content->app_entries[i];
349 
350 		if (!strcmp(s->filename, te->filename))
351 		{
352 			break;
353 		}
354 	}
355 
356 	// Not found ? Exit !
357 	if ((i == content->n_vars) && (j == content->n_apps))
358 	{
359 		return -1;
360 	}
361 
362 	// Release
363 	if (i < content->n_vars)
364 	{
365 		// Delete
366 		tifiles_te_delete(content->var_entries[i]);
367 
368 		// And shift
369 		for (k = i; k < content->n_vars; k++)
370 		{
371 			content->var_entries[k] = content->var_entries[k+1];
372 		}
373 		content->var_entries[k] = NULL;
374 
375 		// And resize
376 		content->var_entries = tifiles_te_resize_array(content->var_entries, content->n_vars - 1);
377 		content->n_vars--;
378 
379 		return content->n_vars;
380 	}
381 
382 	if (j < content->n_apps)
383 	{
384 		// Delete
385 		tifiles_te_delete(content->app_entries[j]);
386 
387 		// And shift
388 		for (k = j; k < content->n_apps; k++)
389 		{
390 			content->app_entries[k] = content->app_entries[k+1];
391 		}
392 		content->app_entries[k] = NULL;
393 
394 		// And resize
395 		content->app_entries = tifiles_te_resize_array(content->app_entries, content->n_apps - 1);
396 		content->n_apps--;
397 
398 		return content->n_apps;
399 	}
400 
401 	return 0;
402 }
403 
404 #ifndef __WIN32__
405 # define stricmp strcasecmp
406 #endif
407 
408 /**
409  * tifiles_tigroup_add_file:
410  * @src_filename: the file to add to TiGroup file
411  * @dst_filename: the TiGroup file (must exist!)
412  *
413  * Add src_filename content to dst_filename content and write to dst_filename.
414  *
415  * Return value: 0 if successful, an error code otherwise.
416  **/
tifiles_tigroup_add_file(const char * src_filename,const char * dst_filename)417 TIEXPORT2 int TICALL tifiles_tigroup_add_file(const char *src_filename, const char *dst_filename)
418 {
419 	CalcModel model;
420 	FileClass type;
421 	TigEntry *te;
422 	TigContent *content = NULL;
423 	int ret = 0;
424 
425 	if (src_filename == NULL || dst_filename == NULL)
426 	{
427 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
428 		return -1;
429 	}
430 
431 	// group file is created if non existent
432 	if (!stricmp(tifiles_fext_get(dst_filename), "tig"))
433 	{
434 		if (!g_file_test(dst_filename, G_FILE_TEST_EXISTS))
435 		{
436 			content = tifiles_content_create_tigroup(CALC_NONE, 0);
437 			tifiles_file_write_tigroup(dst_filename, content);
438 			tifiles_content_delete_tigroup(content);
439 			content = NULL;
440 		}
441 	}
442 
443 	// src can't be a TiGroup file but dst should be
444 	if (!(tifiles_file_is_ti(src_filename) && !tifiles_file_is_tigroup(src_filename) &&
445 		tifiles_file_is_tigroup(dst_filename)))
446 	{
447 		return -1;
448 	}
449 
450 	// load src file
451 	model = tifiles_file_get_model(src_filename);
452 	type = tifiles_file_get_class(src_filename);
453 
454 	te = tifiles_te_create(src_filename, type, model);
455 	if (te == NULL)
456 	{
457 		ret = ERR_BAD_FILE;
458 		goto ttaf;
459 	}
460 	if (type == TIFILE_FLASH)
461 	{
462 		ret = tifiles_file_read_flash(src_filename, te->content.flash);
463 		if (ret)
464 		{
465 			goto ttaf;
466 		}
467 	}
468 	else if (type & TIFILE_REGULAR)
469 	{
470 		ret = tifiles_file_read_regular(src_filename, te->content.regular);
471 		if (ret)
472 		{
473 			goto ttaf;
474 		}
475 	}
476 
477 	// load dst file
478 	content = tifiles_content_create_tigroup(CALC_NONE, 0);
479 	ret = tifiles_file_read_tigroup(dst_filename, content);
480 	if (ret)
481 	{
482 		goto ttaf;
483 	}
484 
485 	tifiles_content_add_te(content, te);
486 	ret = tifiles_file_write_tigroup(dst_filename, content);
487 	if (ret)
488 	{
489 		goto ttaf;
490 	}
491 
492 	tifiles_content_delete_tigroup(content);
493 
494 	return 0;
495 
496 ttaf:	// release on exit
497 	tifiles_te_delete(te);
498 	tifiles_content_delete_tigroup(content);
499 	return ret;
500 }
501 
502 /**
503  * tifiles_tigroup_del_file:
504  * @src_filename: the file to remove from TiGroup file
505  * @dst_filename: the TiGroup file
506  *
507  * Search for entry and remove it from file.
508  *
509  * Return value: 0 if successful, an error code otherwise.
510  **/
tifiles_tigroup_del_file(TigEntry * entry,const char * filename)511 TIEXPORT2 int TICALL tifiles_tigroup_del_file(TigEntry *entry, const char *filename)
512 {
513 	TigContent* content = NULL;
514 	int ret = 0;
515 
516 	if (entry == NULL || filename == NULL)
517 	{
518 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
519 		return -1;
520 	}
521 
522 #ifdef TRACE_CONTENT_INSTANCES
523 	tifiles_info("tifiles_tigroup_del_file: %p", entry);
524 	tifiles_te_display(entry);
525 #endif
526 
527 	content = tifiles_content_create_tigroup(CALC_NONE, 0);
528 	ret = tifiles_file_read_tigroup(filename, content);
529 	if (!ret)
530 	{
531 		(void)tifiles_content_del_te(content, entry);
532 		ret = tifiles_file_write_tigroup(filename, content);
533 	}
534 
535 	tifiles_content_delete_tigroup(content);
536 	return ret;
537 }
538 
539 // ---------------------------------------------------------------------------
540 
541 /**
542  * tifiles_tigroup_contents:
543  * @src_contents1: a pointer on an array of #FileContent structures or NULL. The array must be NULL-terminated.
544  * @src_contents2: a pointer on an array of #FlashContent structures or NULL. The array must be NULL-terminated.
545  * @dst_content: the address of a pointer. This pointer will see the allocated TiGroup file.
546  *
547  * Group several #FileContent/#FlashContent structures into a single one.
548  * Must be freed when no longer used by a call to #tifiles_content_delete_tigroup.
549  *
550  * Return value: an error code if unsuccessful, 0 otherwise.
551  **/
tifiles_tigroup_contents(FileContent ** src_contents1,FlashContent ** src_contents2,TigContent ** dst_content)552 TIEXPORT2 int TICALL tifiles_tigroup_contents(FileContent **src_contents1, FlashContent **src_contents2, TigContent **dst_content)
553 {
554 	TigContent *content;
555 	int i, m=0, n=0;
556 	CalcModel model = CALC_NONE;
557 
558 	if (src_contents1 == NULL && src_contents2 == NULL)
559 	{
560 		return -1;
561 	}
562 
563 	if (dst_content == NULL)
564 	{
565 		tifiles_critical("%s: dst_content is NULL", __FUNCTION__);
566 		return -1;
567 	}
568 
569 	if (src_contents1)
570 	{
571 		for (m = 0; src_contents1[m] != NULL; m++);
572 	}
573 	if (src_contents2)
574 	{
575 		for (n = 0; src_contents2[n] != NULL; n++);
576 	}
577 
578 	if (src_contents2)
579 	{
580 		if (*src_contents2)
581 		{
582 			model = src_contents2[0]->model;
583 		}
584 	}
585 	if (src_contents1)
586 	{
587 		if (*src_contents1)
588 		{
589 			model = src_contents1[0]->model;	// FileContent is more precise than FlashContent
590 		}
591 	}
592 
593 	content = tifiles_content_create_tigroup(model, m+n);
594 
595 	if (src_contents1)
596 	{
597 		for (i = 0; i < m; i++)
598 		{
599 			TigEntry *te = (TigEntry *)g_malloc0(sizeof(TigEntry));
600 
601 			te->filename = tifiles_build_filename(model, src_contents1[i]->entries[0]);
602 			te->type = TIFILE_GROUP;
603 			te->content.regular = tifiles_content_dup_regular(src_contents1[i]);
604 			tifiles_content_add_te(content, te);
605 		}
606 	}
607 
608 	if (src_contents2)
609 	{
610 		for (i = 0; i < n; i++)
611 		{
612 			TigEntry *te;
613 			VarEntry ve;
614 			FlashContent *ptr;
615 
616 			for (ptr = src_contents2[i]; ptr; ptr = ptr->next)
617 			{
618 				if (ptr->data_type == tifiles_flash_type(model))
619 				{
620 					break;
621 				}
622 			}
623 			if (ptr == NULL)
624 			{
625 				tifiles_critical("%s: ptr is NULL, skipping", __FUNCTION__);
626 				continue;
627 			}
628 
629 			te = (TigEntry *)g_malloc0(sizeof(TigEntry));
630 			ve.folder[0] = 0;
631 			strncpy(ve.name, ptr->name, sizeof(ve.name) - 1);
632 			ve.name[sizeof(ve.name) - 1] = 0;
633 			ve.type = ptr->data_type;
634 			te->filename = tifiles_build_filename(model, &ve);
635 			te->type = TIFILE_FLASH;
636 			te->content.flash = tifiles_content_dup_flash(src_contents2[i]);
637 			tifiles_content_add_te(content, te);
638 		}
639 	}
640 
641 	*dst_content = content;
642 
643 #ifdef TRACE_CONTENT_INSTANCES
644 	tifiles_info("tifiles_tigroup_contents: %p", content);
645 	tifiles_file_display_tigcontent(content);
646 #endif
647 
648 	return 0;
649 }
650 
651 /**
652  * tifiles_untigroup_content:
653  * @src_content: a pointer on the structure to unpack.
654  * @dst_contents1: the address of your pointer. This pointers will point on a
655  * @dst_contents2: the address of your pointer. This pointers will point on a
656  * dynamically allocated array of structures. The array is terminated by NULL.
657  *
658  * Ungroup a TiGroup file by exploding the structure into an array of structures.
659  * Must be freed when no longer used by a call to #tifiles_content_delete_tigroup.
660  *
661  * Return value: an error code if unsuccessful, 0 otherwise.
662  **/
tifiles_untigroup_content(TigContent * src_content,FileContent *** dst_contents1,FlashContent *** dst_contents2)663 TIEXPORT2 int TICALL tifiles_untigroup_content(TigContent *src_content, FileContent ***dst_contents1, FlashContent ***dst_contents2)
664 {
665 	TigContent *src = src_content;
666 	FileContent **dst1 = NULL;
667 	FlashContent **dst2 = NULL;
668 	unsigned int i, j;
669 
670 	if (src_content == NULL || dst_contents1 == NULL || dst_contents2 == NULL)
671 	{
672 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
673 		return -1;
674 	}
675 
676 #ifdef TRACE_CONTENT_INSTANCES
677 	tifiles_info("tifiles_untigroup_content: %p", src_content);
678 	tifiles_file_display_tigcontent(src_content);
679 #endif
680 
681 	// allocate an array of FileContent/FlashContent structures (NULL terminated)
682 	dst1 = (FileContent **)g_malloc0((src->n_vars+1) * sizeof(FileContent *));
683 	if (dst1 == NULL)
684 	{
685 		return ERR_MALLOC;
686 	}
687 	dst2 = (FlashContent **)g_malloc0((src->n_apps+1) * sizeof(FlashContent *));
688 	if (dst2 == NULL)
689 	{
690 		g_free(dst1);
691 		return ERR_MALLOC;
692 	}
693 
694 	// parse each entry and duplicate it into a single content
695 	for (i = 0; i < src->n_vars; i++)
696 	{
697 		TigEntry *te = src->var_entries[i];
698 
699 		dst1[i] = tifiles_content_dup_regular(te->content.regular);
700 	}
701 
702 	for (j = 0; j < src->n_apps; j++)
703 	{
704 		TigEntry *te = src->app_entries[j];
705 
706 		dst2[j] = tifiles_content_dup_flash(te->content.flash);
707 	}
708 
709 	*dst_contents1 = dst1;
710 	*dst_contents2 = dst2;
711 
712 	return 0;
713 }
714 
715 /**
716  * tifiles_group_files:
717  * @src_filenames: a NULL-terminated array of strings (list of files to group).
718  * @dst_filename: the filename where to store the TiGroup.
719  *
720  * Group several TI files (regular/flash) into a single one (TiGroup file).
721  *
722  * Return value: an error code if unsuccessful, 0 otherwise.
723  **/
tifiles_tigroup_files(char ** src_filenames,const char * dst_filename)724 TIEXPORT2 int TICALL tifiles_tigroup_files(char **src_filenames, const char *dst_filename)
725 {
726 	FileContent **src1 = NULL;
727 	FlashContent **src2 = NULL;
728 	TigContent *dst = NULL;
729 	CalcModel model;
730 	int i, j, k, m, n;
731 	int ret = 0;
732 
733 	if (src_filenames == NULL || dst_filename == NULL)
734 	{
735 		tifiles_critical("%s: an argument is NULL !", __FUNCTION__);
736 		return -1;
737 	}
738 
739 	// counts number of files to group and allocate space for that
740 	for (k = m = n = 0; src_filenames[k]; k++)
741 	{
742 		if (tifiles_file_is_regular(src_filenames[k]))
743 		{
744 			m++;
745 		}
746 		else if (tifiles_file_is_flash(src_filenames[k]))
747 		{
748 			n++;
749 		}
750 	}
751 	model = tifiles_file_get_model(src_filenames[0]);
752 
753 	// allocate space for that
754 	src1 = (FileContent **)g_malloc0((m + 1) * sizeof(FileContent *));
755 	if (src1 == NULL)
756 	{
757 		return ERR_MALLOC;
758 	}
759 
760 	src2 = (FlashContent **)g_malloc0((n + 1) * sizeof(FlashContent *));
761 	if (src2 == NULL)
762 	{
763 		g_free(src1);
764 		return ERR_MALLOC;
765 	}
766 
767 	for (i = j = k = 0; k < m+n; k++)
768 	{
769 		if (tifiles_file_is_regular(src_filenames[k]))
770 		{
771 			src1[i] = tifiles_content_create_regular(model);
772 			ret = tifiles_file_read_regular(src_filenames[k], src1[i]);
773 			if (ret)
774 			{
775 				goto tgf;
776 			}
777 			i++;
778 		}
779 		else if (tifiles_file_is_flash(src_filenames[k]))
780 		{
781 			src2[j] = tifiles_content_create_flash(model);
782 			ret = tifiles_file_read_flash(src_filenames[k], src2[j]);
783 			if (ret)
784 			{
785 				goto tgf;
786 			}
787 			j++;
788 		}
789 	}
790 
791 	ret = tifiles_tigroup_contents(src1, src2, &dst);
792 	if (ret)
793 	{
794 		goto tgf;
795 	}
796 
797 	ret = tifiles_file_write_tigroup(dst_filename, dst);
798 
799 tgf:
800 	for (i = 0; i < m; i++)
801 	{
802 		g_free(src1[i]);
803 	}
804 	g_free(src1);
805 	for (i = 0; i < n; i++)
806 	{
807 		g_free(src2[i]);
808 	}
809 	g_free(src2);
810 	tifiles_content_delete_tigroup(dst);
811 
812 	return ret;
813 }
814 
815 /**
816  * tifiles_ungroup_file:
817  * @src_filename: full path of file to ungroup.
818  * @dst_filenames: NULL or the address of a pointer where to store a NULL-terminated
819  * array of strings which contain the list of ungrouped files (regular/flash).
820  *
821  * Ungroup a TiGroup file into several files. Resulting files have the
822  * same name as the variable stored within group file.
823  * Beware: there is no existence check; files may be overwritten !
824  *
825  * %dst_filenames must be freed when no longer used.
826  *
827  * Return value: an error code if unsuccessful, 0 otherwise.
828  **/
tifiles_untigroup_file(const char * src_filename,char *** dst_filenames)829 TIEXPORT2 int TICALL tifiles_untigroup_file(const char *src_filename, char ***dst_filenames)
830 {
831 	TigContent *src = NULL;
832 	FileContent **ptr1, **dst1 = NULL;
833 	FlashContent **ptr2, **dst2 = NULL;
834 	char *real_name;
835 	unsigned int i, j;
836 	int ret = 0;
837 
838 	if (src_filename == NULL)
839 	{
840 		tifiles_critical("%s: src_filename is NULL !", __FUNCTION__);
841 		return -1;
842 	}
843 
844 	// read TiGroup file
845 	src = tifiles_content_create_tigroup(CALC_NONE, 0);
846 	ret = tifiles_file_read_tigroup(src_filename, src);
847 	if (ret)
848 	{
849 		goto tuf;
850 	}
851 
852 	// ungroup structure
853 	ret = tifiles_untigroup_content(src, &dst1, &dst2);
854 	if (ret)
855 	{
856 		goto tuf;
857 	}
858 
859 	// count number of structures and allocates array of strings
860 	if (dst_filenames != NULL)
861 	{
862 		*dst_filenames = (char **)g_malloc((src->n_vars + src->n_apps + 1) * sizeof(char *));
863 	}
864 
865 	// store each structure content to file
866 	for (ptr1 = dst1, i = 0; *ptr1 != NULL || i < src->n_vars; ptr1++, i++)
867 	{
868 		ret = tifiles_file_write_regular(NULL, *ptr1, &real_name);
869 		if (ret)
870 		{
871 			goto tuf;
872 		}
873 
874 		if (dst_filenames != NULL)
875 		{
876 			*dst_filenames[i] = real_name;
877 		}
878 		else
879 		{
880 			g_free(real_name);
881 		}
882 	}
883 
884 	for (ptr2 = dst2, j = 0; *ptr2 != NULL || j < src->n_apps; ptr2++, j++)
885 	{
886 		ret = tifiles_file_write_flash2(NULL, *ptr2, &real_name);
887 		if (ret)
888 		{
889 			goto tuf;
890 		}
891 
892 		if (dst_filenames != NULL)
893 		{
894 			*dst_filenames[i+j] = real_name;
895 		}
896 		else
897 		{
898 			g_free(real_name);
899 		}
900 	}
901 
902 	// release allocated memory
903 tuf:
904 	if (dst1)
905 	{
906 		for (ptr1 = dst1; *ptr1; ptr1++)
907 		{
908 			tifiles_content_delete_regular(*ptr1);
909 		}
910 	}
911 	if (dst2)
912 	{
913 		for (ptr2 = dst2; *ptr2; ptr2++)
914 		{
915 			tifiles_content_delete_flash(*ptr2);
916 		}
917 	}
918 	tifiles_content_delete_tigroup(src);
919 
920 	return ret;
921 }
922 
923 // ---------------------------------------------------------------------------
924 
925 /**
926  * tifiles_content_create_tigroup:
927  * @model: a calculator model or CALC_NONE.
928  * @n: number of #tigEntry entries
929  *
930  * Allocates a TigContent structure. Note: the calculator model is not required
931  * if the content is used for file reading but is compulsory for file writing.
932  *
933  * Return value: the allocated block.
934  **/
tifiles_content_create_tigroup(CalcModel model,unsigned int n)935 TIEXPORT2 TigContent* TICALL tifiles_content_create_tigroup(CalcModel model, unsigned int n)
936 {
937 	TigContent* content = g_malloc0(sizeof(*content));
938 	if (content != NULL)
939 	{
940 		content->model = content->model_dst = model;
941 		content->comment = g_strdup(tifiles_comment_set_tigroup());
942 		content->comp_level = 4;
943 		content->var_entries = (TigEntry **)g_malloc0(sizeof(TigEntry *));
944 		content->app_entries = (TigEntry **)g_malloc0(sizeof(TigEntry *));
945 	}
946 
947 #ifdef TRACE_CONTENT_INSTANCES
948 	tifiles_info("tifiles_content_create_tigroup: %p", content);
949 	tifiles_file_display_tigcontent(content);
950 #endif
951 
952 	return content;
953 }
954 
955 /**
956  * tifiles_content_delete_tigroup:
957  *
958  * Free the whole content of a @TigContent structure and the content itself.
959  *
960  * Return value: none.
961  **/
tifiles_content_delete_tigroup(TigContent * content)962 TIEXPORT2 int TICALL tifiles_content_delete_tigroup(TigContent *content)
963 {
964 	unsigned int i;
965 
966 #ifdef TRACE_CONTENT_INSTANCES
967 	tifiles_info("tifiles_content_delete_tigroup: %p", content);
968 	tifiles_file_display_tigcontent(content);
969 #endif
970 
971 	if (content != NULL)
972 	{
973 		if (content->var_entries != NULL)
974 		{
975 			for (i = 0; i < content->n_vars; i++)
976 			{
977 				TigEntry* entry = content->var_entries[i];
978 				tifiles_te_delete(entry);
979 			}
980 			g_free(content->var_entries);
981 		}
982 
983 		if (content->app_entries != NULL)
984 		{
985 			for (i = 0; i < content->n_apps; i++)
986 			{
987 				TigEntry* entry = content->app_entries[i];
988 				tifiles_te_delete(entry);
989 			}
990 			g_free(content->app_entries);
991 		}
992 
993 		g_free(content->comment);
994 
995 		g_free(content);
996 	}
997 	else
998 	{
999 		tifiles_critical("%s(NULL)", __FUNCTION__);
1000 	}
1001 
1002 	return 0;
1003 }
1004 
1005 /* Open a temporary file */
open_temp_file(const char * orig_name,char ** temp_name)1006 static int open_temp_file(const char *orig_name, char **temp_name)
1007 {
1008 	const char *suffix;
1009 	char *template;
1010 	int fd;
1011 
1012 	*temp_name = NULL;
1013 	suffix = strrchr(orig_name, '.');
1014 	if (suffix && (strchr(suffix, '/') || strchr(suffix, '\\')))
1015 	{
1016 		suffix = NULL;
1017 	}
1018 	template = g_strconcat("tigXXXXXX", suffix, NULL);
1019 
1020 	fd = g_file_open_tmp(template, temp_name, NULL);
1021 
1022 	g_free(template);
1023 	if (fd == -1) {
1024 		g_free(*temp_name);
1025 		*temp_name = NULL;
1026 	}
1027 
1028 	return fd;
1029 }
1030 
1031 /**
1032  * tifiles_file_read_tigroup:
1033  * @filename: the name of file to load.
1034  * @content: where to store content (may be re-allocated).
1035  *
1036  * This function loads a TiGroup from \a filename and places its content into \a content.
1037  * If an error occurs, the structure content is *NOT* released for you.
1038  *
1039  * The temporary folder is used by this function to store temporary files.
1040  *
1041  * Return value: an error code if unsuccessful, 0 otherwise.
1042  **/
tifiles_file_read_tigroup(const char * filename,TigContent * content)1043 TIEXPORT2 int TICALL tifiles_file_read_tigroup(const char *filename, TigContent *content)
1044 {
1045 	FILE *tigf;
1046 	struct archive *arc;
1047 	struct archive_entry *entry;
1048 	const char *filename_inzip;
1049 	int ret = 0;
1050 
1051 	if (filename == NULL || content == NULL)
1052 	{
1053 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
1054 		return -1;
1055 	}
1056 
1057 	// Open ZIP archive
1058 	tigf = g_fopen(filename, "rb");
1059 	if (tigf == NULL)
1060 	{
1061 		return ERR_FILE_OPEN;
1062 	}
1063 
1064 	if (!(arc = archive_read_new())
1065 	    || archive_read_support_format_zip(arc) != ARCHIVE_OK
1066 	    || archive_read_open_FILE(arc, tigf) != ARCHIVE_OK)
1067 	{
1068 		if (arc)
1069 		{
1070 			archive_read_free(arc);
1071 		}
1072 		fclose(tigf);
1073 		return ERR_FILE_ZIP;
1074 	}
1075 
1076 	g_free(content->var_entries);
1077 	content->var_entries = (TigEntry **)g_malloc0(1 * sizeof(TigEntry *));
1078 	content->n_vars = 0;
1079 
1080 	g_free(content->app_entries);
1081 	content->app_entries = (TigEntry **)g_malloc0(1 * sizeof(TigEntry *));
1082 	content->n_apps = 0;
1083 
1084 	// Get comment
1085 	g_free(content->comment);
1086 	// FIXME: any way to get this from libarchive?
1087 	content->comment = g_strdup("");
1088 
1089 	// Parse archive for files
1090 	while (archive_read_next_header(arc, &entry) == ARCHIVE_OK)
1091 	{
1092 		gchar *fname;
1093 		int fd;
1094 
1095 		filename_inzip = archive_entry_pathname(entry);
1096 		if (!filename_inzip)
1097 		{
1098 			tifiles_warning("archive contains a file with no name");
1099 			archive_read_data_skip(arc);
1100 			continue;
1101 		}
1102 
1103 		// create a temporary file
1104 		fd = open_temp_file(filename_inzip, &fname);
1105 		if (fd == -1)
1106 		{
1107 			ret = ERR_FILE_IO;
1108 			goto tfrt_exit;
1109 		}
1110 
1111 		// extract data into temporary file
1112 		if (archive_read_data_into_fd(arc, fd) != ARCHIVE_OK)
1113 		{
1114 			close(fd);
1115 			g_unlink(fname);
1116 			g_free(fname);
1117 			ret = ERR_FILE_IO;
1118 			goto tfrt_exit;
1119 		}
1120 		close(fd);
1121 
1122 		// add to TigContent
1123 		{
1124 			int model = tifiles_file_get_model(fname);
1125 
1126 			if (content->model == CALC_NONE)
1127 			{
1128 				content->model = model;
1129 			}
1130 
1131 			if (tifiles_file_is_regular(fname))
1132 			{
1133 				TigEntry *tigentry = tifiles_te_create(filename_inzip, tifiles_file_get_class(fname), content->model);
1134 
1135 				if (tigentry != NULL)
1136 				{
1137 					ret = tifiles_file_read_regular(fname, tigentry->content.regular);
1138 					if (ret)
1139 					{
1140 						g_free(tigentry);
1141 						g_unlink(fname);
1142 						g_free(fname);
1143 						break;
1144 					}
1145 
1146 					tifiles_content_add_te(content, tigentry);
1147 				}
1148 			}
1149 			else if (tifiles_file_is_flash(fname))
1150 			{
1151 				TigEntry *tigentry = tifiles_te_create(filename_inzip, tifiles_file_get_class(fname), content->model);
1152 
1153 				if (tigentry != NULL)
1154 				{
1155 					ret = tifiles_file_read_flash(fname, tigentry->content.flash);
1156 					if (ret)
1157 					{
1158 						g_free(tigentry);
1159 						g_unlink(fname);
1160 						g_free(fname);
1161 						break;
1162 					}
1163 
1164 					tifiles_content_add_te(content, tigentry);
1165 				}
1166 			}
1167 			else
1168 			{
1169 				// skip
1170 			}
1171 		}
1172 		g_unlink(fname);
1173 		g_free(fname);
1174 	}
1175 
1176 #ifdef TRACE_CONTENT_INSTANCES
1177 	tifiles_info("tifiles_file_read_tigroup: %p", content);
1178 	tifiles_file_display_tigcontent(content);
1179 #endif
1180 
1181 	// Close
1182 tfrt_exit:
1183 	archive_read_free(arc);
1184 	fclose(tigf);
1185 	return ret;
1186 }
1187 
zip_write(struct archive * arc,CalcModel model,const char * origfname,const char * tempfname)1188 static int zip_write(struct archive *arc, CalcModel model, const char *origfname, const char *tempfname)
1189 {
1190 	char *filenameinzip;
1191 	struct archive_entry *entry;
1192 	struct stat st;
1193 	int err = 0;
1194 	FILE *f = NULL;
1195 	int size_read;
1196 	void* buf=NULL;
1197 
1198 	if (arc == NULL)
1199 	{
1200 		tifiles_critical("zip_write: arc is NULL !");
1201 		return ERR_FILE_ZIP;
1202 	}
1203 
1204 	// Set metadata
1205 	entry = archive_entry_new();
1206 	if (entry == NULL)
1207 	{
1208 		tifiles_critical("zip_write: cannot allocate archive entry");
1209 		return ERR_FILE_ZIP;
1210 	}
1211 
1212 	if (g_stat(tempfname, &st))
1213 	{
1214 		tifiles_critical("zip_write: cannot stat temporary file");
1215 		archive_entry_free(entry);
1216 		return ERR_FILE_IO;
1217 	}
1218 	archive_entry_copy_stat(entry, &st);
1219 
1220 	// ZIP archives don't like greek chars
1221 	filenameinzip = ticonv_gfe_to_zfe(model, origfname);
1222 	archive_entry_set_pathname(entry, filenameinzip);
1223 	g_free(filenameinzip);
1224 
1225 	// missing tmp file !
1226 	f = g_fopen(tempfname, "rb");
1227 	if (f == NULL)
1228 	{
1229 		tifiles_critical("zip_write: cannot read temporary file");
1230 		archive_entry_free(entry);
1231 		err = ERR_FILE_IO;
1232 		goto end2;
1233 	}
1234 
1235 	if (archive_write_header(arc, entry) != ARCHIVE_OK)
1236 	{
1237 		archive_entry_free(entry);
1238 		err = ERR_FILE_IO;
1239 		goto end;
1240 	}
1241 	archive_entry_free(entry);
1242 
1243 	// Allocate buffer
1244 	buf = (void*)g_malloc(WRITEBUFFERSIZE);
1245 
1246 	do
1247 	{
1248 		// feed with our data
1249 		size_read = fread(buf, 1, WRITEBUFFERSIZE, f);
1250 
1251 		if (size_read < WRITEBUFFERSIZE)
1252 		{
1253 			if (!feof(f))
1254 			{
1255 				tifiles_critical("error in reading %s", tempfname);
1256 				err = ERR_FILE_IO;
1257 			}
1258 		}
1259 		if (size_read > 0)
1260 		{
1261 			if (archive_write_data(arc, buf, size_read) != size_read)
1262 			{
1263 				tifiles_critical("error in writing %s in the zipfile\n", origfname);
1264 				err = ERR_FILE_IO;
1265 			}
1266 		}
1267 	} while (!err && (size_read>0));
1268 
1269 	g_free(buf);
1270 end:
1271 	fclose(f);
1272 end2:
1273 	return err;
1274 }
1275 
1276 /**
1277  * tifiles_file_write_tigroup:
1278  * @filename: the name of file to load.
1279  * @content: where to store content.
1280  *
1281  * This function store TiGroup contents to file. Please note that contents
1282  * can contains no data. In this case, the file is void but created.
1283  *
1284  * The temporary folder is used by this function to store temporary files.
1285  *
1286  * Return value: an error code if unsuccessful, 0 otherwise.
1287  **/
tifiles_file_write_tigroup(const char * filename,TigContent * content)1288 TIEXPORT2 int TICALL tifiles_file_write_tigroup(const char *filename, TigContent *content)
1289 {
1290 	FILE *tigf;
1291 	struct archive *arc;
1292 	int err = 0;
1293 	TigEntry **ptr;
1294 
1295 	if (filename == NULL || content == NULL)
1296 	{
1297 		tifiles_critical("%s: an argument is NULL", __FUNCTION__);
1298 		return -1;
1299 	}
1300 
1301 #ifdef TRACE_CONTENT_INSTANCES
1302 	tifiles_info("tifiles_file_write_tigroup: %p", content);
1303 	tifiles_file_display_tigcontent(content);
1304 #endif
1305 
1306 	// Open ZIP archive
1307 	tigf = g_fopen(filename, "wb");
1308 	if (tigf == NULL)
1309 	{
1310 		return ERR_FILE_OPEN;
1311 	}
1312 
1313 	if (!(arc = archive_write_new()) || archive_write_set_format_zip(arc) != ARCHIVE_OK)
1314 	{
1315 		if (arc)
1316 		{
1317 			archive_write_close(arc);
1318 			archive_write_free(arc);
1319 		}
1320 		fclose(tigf);
1321 		return ERR_FILE_OPEN;
1322 	}
1323 
1324 	// tell libarchive not to pad output to 10240-byte blocks (why
1325 	// this is not the default for zip format, I have no idea)
1326 	archive_write_set_bytes_per_block(arc, 0);
1327 
1328 	if (content->comp_level > 0)
1329 	{
1330 		archive_write_set_options(arc, "compression=deflate");
1331 	}
1332 	else
1333 	{
1334 		archive_write_set_options(arc, "compression=store");
1335 	}
1336 
1337 	if (archive_write_open_FILE(arc, tigf) != ARCHIVE_OK)
1338 	{
1339 		err = ERR_FILE_OPEN;
1340 	}
1341 
1342 	// Parse entries and store
1343 	for (ptr = content->var_entries; *ptr && !err; ptr++)
1344 	{
1345 		TigEntry* entry = *ptr;
1346 		char *fname = NULL;
1347 		int fd;
1348 
1349 		// write TI file into tmp folder
1350 		fd = open_temp_file(entry->filename, &fname);
1351 		if (fd == -1)
1352 		{
1353 			g_free(fname);
1354 			err = ERR_FILE_OPEN;
1355 			break;
1356 		}
1357 		close(fd);
1358 
1359 		err = tifiles_file_write_regular(fname, entry->content.regular, NULL);
1360 		if (!err)
1361 		{
1362 			err = zip_write(arc, content->model, entry->filename, fname);
1363 		}
1364 
1365 		g_unlink(fname);
1366 		g_free(fname);
1367 	}
1368 
1369 	for (ptr = content->app_entries; *ptr && !err; ptr++)
1370 	{
1371 		TigEntry* entry = *ptr;
1372 		char *fname = NULL;
1373 		int fd;
1374 
1375 		// write TI file into tmp folder
1376 		fd = open_temp_file(entry->filename, &fname);
1377 		if (fd == -1)
1378 		{
1379 			g_free(fname);
1380 			err = ERR_FILE_OPEN;
1381 			break;
1382 		}
1383 		close(fd);
1384 
1385 		err = tifiles_file_write_flash(fname, entry->content.flash);
1386 		if (!err)
1387 		{
1388 			err = zip_write(arc, content->model, entry->filename, fname);
1389 		}
1390 
1391 		g_unlink(fname);
1392 		g_free(fname);
1393 	}
1394 
1395 	// close archive
1396 	if (archive_write_close(arc) != ARCHIVE_OK)
1397 	{
1398 		err = ERR_FILE_IO;
1399 	}
1400 	archive_write_free(arc);
1401 	fclose(tigf);
1402 	return err;
1403 }
1404 
1405 /**
1406  * tifiles_file_display_tigroup:
1407  * @filename: the name of file to load.
1408  *
1409  * This function shows file contents (similar to "unzip -l filename").
1410  *
1411  * Return value: an error code if unsuccessful, 0 otherwise.
1412  **/
tifiles_file_display_tigroup(const char * filename)1413 TIEXPORT2 int TICALL tifiles_file_display_tigroup(const char *filename)
1414 {
1415 	FILE *tigf;
1416 	struct archive *arc;
1417 	struct archive_entry *entry;
1418 
1419 	if (filename == NULL)
1420 	{
1421 		tifiles_critical("%s(NULL)", __FUNCTION__);
1422 		return -1;
1423 	}
1424 
1425 	tigf = g_fopen(filename, "rb");
1426 	if (tigf == NULL)
1427 	{
1428 		return ERR_FILE_OPEN;
1429 	}
1430 
1431 	if (!(arc = archive_read_new())
1432 	    || archive_read_support_format_zip(arc) != ARCHIVE_OK
1433 	    || archive_read_open_FILE(arc, tigf) != ARCHIVE_OK)
1434 	{
1435 		if (arc)
1436 		{
1437 			archive_read_free(arc);
1438 		}
1439 		fclose(tigf);
1440 		return ERR_FILE_ZIP;
1441 	}
1442 
1443 	tifiles_info("TIGroup file contents:");
1444 	tifiles_info(" Size    Name");
1445 	tifiles_info(" ------  ------");
1446 
1447 	while (archive_read_next_header(arc, &entry) == ARCHIVE_OK)
1448 	{
1449 		const char *name = archive_entry_pathname(entry);
1450 		char *dispname = g_filename_display_name(name);
1451 		unsigned long size = (unsigned long) archive_entry_size(entry);
1452 		tifiles_info(" %-7lu %s", size, dispname);
1453 		archive_read_data_skip(arc);
1454 		g_free(dispname);
1455 	}
1456 
1457 	archive_read_free(arc);
1458 	fclose(tigf);
1459 	return 0;
1460 }
1461 
1462 /**
1463  * tifiles_file_display_tigcontent:
1464  * @content: the tigroup content to show, TigContent pointer.
1465  *
1466  * Display tigroup information contained in a TigContent structure.
1467  *
1468  * Return value: an error code, 0 otherwise.
1469  **/
tifiles_file_display_tigcontent(TigContent * content)1470 TIEXPORT2 int TICALL tifiles_file_display_tigcontent(TigContent *content)
1471 {
1472 	unsigned int i;
1473 
1474 	if (content == NULL)
1475 	{
1476 		tifiles_critical("%s(NULL)", __FUNCTION__);
1477 		return ERR_INVALID_FILE;
1478 	}
1479 
1480 	tifiles_info("Model:             %02X (%u)", content->model, content->model);
1481 	tifiles_info("Signature:         %s", tifiles_calctype2signature(content->model));
1482 	tifiles_info("model_dst:         %02X (%u)", content->model_dst, content->model_dst);
1483 	tifiles_info("Comment:           %s", content->comment);
1484 	tifiles_info("Compression level: %d", content->comp_level);
1485 
1486 	tifiles_info("Number of vars:    %u", content->n_vars);
1487 	tifiles_info("Var entries:       %p", content->var_entries);
1488 
1489 	if (content->var_entries != NULL)
1490 	{
1491 		for (i = 0; i < content->n_vars; i++)
1492 		{
1493 			tifiles_te_display(content->var_entries[i]);
1494 		}
1495 	}
1496 
1497 	tifiles_info("Number of apps:    %u", content->n_apps);
1498 	tifiles_info("Apps entries:      %p", content->app_entries);
1499 
1500 	if (content->app_entries != NULL)
1501 	{
1502 		for (i = 0; i < content->n_apps; i++)
1503 		{
1504 			tifiles_te_display(content->app_entries[i]);
1505 		}
1506 	}
1507 
1508 	return 0;
1509 }
1510