1 /* main.c
2  *
3  * Copyright (c) 2002 Lutz Mueller <lutz@users.sourceforge.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301  USA.
19  */
20 
21 #include "config.h"
22 
23 #include <errno.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 
29 #ifdef HAVE_UNISTD_H
30 #  include <unistd.h>
31 #endif
32 
33 #include <libexif/exif-data.h>
34 #include <libexif/exif-utils.h>
35 #include <libexif/exif-loader.h>
36 
37 #include "actions.h"
38 #include "exif-i18n.h"
39 #include "utils.h"
40 
41 /* Must be loaded after exif-i18n.h */
42 #include <popt.h>
43 
44 #ifdef HAVE_LOCALE_H
45 #  include <locale.h>
46 #endif
47 
48 #ifdef ENABLE_GLIBC_MEMDEBUG
49 #include <mcheck.h>
50 #endif
51 
52 /*! The minimum width of output that we can support */
53 #define MIN_WIDTH 52
54 
55 /*! A sane limit on output width */
56 #define MAX_WIDTH 9999
57 
58 /* Old versions of popt.h don't define POPT_TABLEEND */
59 #ifndef POPT_TABLEEND
60 #  define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
61 #endif
62 
63 /* ANSI escape codes for output colors */
64 #define COL_BLUE        "\033[34m"
65 #define COL_GREEN       "\033[32m"
66 #define COL_RED         "\033[31m"
67 #define COL_BOLD        "\033[1m"
68 #define COL_UNDERLINE   "\033[4m"
69 #define COL_NORMAL      "\033[m"
70 
71 /* Ensure that we're actually printing the escape codes to a TTY.
72  * FIXME: We should make sure the terminal can actually understand these
73  *        escape sequences, or better yet, use vidattr(3X) to set colours
74  *        instead.
75  */
76 
77 #if defined(HAVE_ISATTY) && defined(HAVE_FILENO)
78 #  define file_is_tty(file) (isatty(fileno(file)))
79 #else
80 #  define file_is_tty(file) (0==1)
81 #endif
82 
83 #define put_colorstring(file, colorstring) \
84 	do { \
85 		if (file_is_tty(file)) { \
86 			fputs (colorstring, file); \
87 		} \
88 	} while (0)
89 
90 typedef struct {
91 	unsigned int debug;
92 
93        /* input */
94        int ignore_corrupted;
95        /* output */
96        int corrupted;
97 } LogArg;
98 
99 static void
log_func(ExifLog * log,ExifLogCode code,const char * domain,const char * format,va_list args,void * data)100 log_func (ExifLog *log, ExifLogCode code, const char *domain,
101 	  const char *format, va_list args, void *data)
102 {
103 	LogArg *arg = data;
104 	(void) log;  /* unused */
105 
106 	arg->corrupted = 0;
107 
108 	/*
109 	 * When debugging, continue as far as possible. If not, make all errors
110 	 * fatal.
111 	 */
112 	switch (code) {
113 	case -1:
114 		put_colorstring (stderr, COL_RED);
115 		vfprintf (stderr, format, args);
116 		fprintf (stderr, "\n");
117 		put_colorstring (stderr, COL_NORMAL);
118 		exit (1);
119 	case EXIF_LOG_CODE_DEBUG:
120 		if (arg->debug) {
121 			put_colorstring (stdout, COL_GREEN);
122 			fprintf (stdout, "%s: ", domain);
123 			vfprintf (stdout, format, args);
124 			put_colorstring (stdout, COL_NORMAL);
125 			printf ("\n");
126 		}
127 		break;
128 	case EXIF_LOG_CODE_CORRUPT_DATA:
129 		arg->corrupted = 1;
130 		/* We ignore corrupted data event in some cases */
131 		if (arg->ignore_corrupted)
132 			return;
133 		/* Fall through to EXIF_LOG_CODE_NO_MEMORY */
134 	case EXIF_LOG_CODE_NO_MEMORY:
135 		put_colorstring (stderr, COL_RED COL_BOLD COL_UNDERLINE);
136 		fprintf (stderr, "%s\n", exif_log_code_get_title (code));
137 		put_colorstring (stderr, COL_NORMAL COL_RED);
138 		fprintf (stderr, "%s\n", exif_log_code_get_message (code));
139 		fprintf (stderr, "%s: ", domain);
140 		vfprintf (stderr, format, args);
141 		put_colorstring (stderr, COL_NORMAL);
142 		fprintf (stderr, "\n");
143 
144 		/*
145 		 * EXIF_LOG_CODE_NO_MEMORY is always a fatal error, so exit.
146 		 * EXIF_LOG_CODE_CORRUPT_DATA is only fatal if debug mode
147 		 * is off.
148 		 *
149 		 * Exiting the program due to a log message is really a bad
150 		 * idea to begin with. This should be removed once the libexif
151 		 * API is fixed to properly return error codes everywhere.
152 		 */
153 		if ((code == EXIF_LOG_CODE_NO_MEMORY) || !arg->debug)
154 			exit (1);
155 		break;
156 	default:
157 		if (arg->debug) {
158 			put_colorstring (stdout, COL_BLUE);
159 			printf ("%s: ", domain);
160 			vprintf (format, args);
161 			put_colorstring (stdout, COL_NORMAL);
162 			printf ("\n");
163 		}
164 		break;
165 	}
166 }
167 
168 /*
169  * Static variables. I had them first in main (), but people
170  * compiling exif on IRIX complained about that not being compatible
171  * with the "SGI MIPSpro C compiler", and elsewhere on Open Watcom C.
172  * This is due to the inability of these compilers to initialize a
173  * struct with the address of a stack-allocated variable.
174  */
175 static unsigned int list_tags = 0, show_description = 0;
176 static unsigned int xml_output = 0;
177 static unsigned int extract_thumbnail = 0, remove_thumb = 0;
178 static unsigned int remove_tag = 0, create_exif = 0, no_fixup = 0;
179 static unsigned int list_mnote = 0;
180 static unsigned int show_version = 0;
181 static char *output = NULL;
182 static char *ifd_string = NULL, *tag_string = NULL;
183 static ExifParams p = {EXIF_INVALID_TAG, EXIF_IFD_COUNT, 0, 0, 80,
184 		      		   NULL, NULL,NULL};
185 LogArg log_arg = {0, 0, 0};
186 
187 int
main(int argc,const char ** argv)188 main (int argc, const char **argv)
189 {
190 	/* POPT_ARG_NONE needs an int, not char! */
191 	poptContext ctx;
192 	const char * const *args;
193 	const struct poptOption options[] = {
194 		POPT_AUTOHELP
195 		{"version", 'v', POPT_ARG_NONE, &show_version, 0,
196 		 N_("Display software version"), NULL},
197 		{"ids", 'i', POPT_ARG_NONE, &p.use_ids, 0,
198 		 N_("Show IDs instead of tag names"), NULL},
199 		{"tag", 't', POPT_ARG_STRING, &tag_string, 0,
200 		 N_("Select tag"), N_("tag")},
201 		{"ifd", '\0', POPT_ARG_STRING, &ifd_string, 0,
202 		 N_("Select IFD"), N_("IFD")},
203 		{"list-tags", 'l', POPT_ARG_NONE, &list_tags, 0,
204 		 N_("List all EXIF tags"), NULL},
205 		{"show-mnote", '|', POPT_ARG_NONE, &list_mnote, 0,
206 		 N_("Show contents of tag MakerNote"), NULL},
207 		{"remove", '\0', POPT_ARG_NONE, &remove_tag, 0,
208 		 N_("Remove tag or ifd"), NULL},
209 		{"show-description", 's', POPT_ARG_NONE, &show_description, 0,
210 		 N_("Show description of tag"), NULL},
211 		{"extract-thumbnail", 'e', POPT_ARG_NONE, &extract_thumbnail, 0,
212 		 N_("Extract thumbnail"), NULL},
213 		{"remove-thumbnail", 'r', POPT_ARG_NONE, &remove_thumb, 0,
214 		 N_("Remove thumbnail"), NULL},
215 		{"insert-thumbnail", 'n', POPT_ARG_STRING, &p.set_thumb, 0,
216 		 N_("Insert FILE as thumbnail"), N_("FILE")},
217 		{"no-fixup", '\0', POPT_ARG_NONE, &no_fixup, 0,
218 		 N_("Do not fix existing tags in files"), NULL},
219 		{"output", 'o', POPT_ARG_STRING, &output, 0,
220 		 N_("Write data to FILE"), N_("FILE")},
221 		{"set-value", '\0', POPT_ARG_STRING, &p.set_value, 0,
222 		 N_("Value of tag"), N_("STRING")},
223 		{"create-exif", 'c', POPT_ARG_NONE, &create_exif, 0,
224 		 N_("Create EXIF data if not existing"), NULL},
225 		{"machine-readable", 'm', POPT_ARG_NONE, &p.machine_readable, 0,
226 		 N_("Output in a machine-readable (tab delimited) format"),
227 		 NULL},
228 		{"width", 'w', POPT_ARG_INT, &p.width, 0,
229 		 N_("Width of output"), N_("WIDTH")},
230 		{"xml-output", 'x', POPT_ARG_NONE, &xml_output, 0,
231 		 N_("Output in a XML format"),
232 		 NULL},
233 		{"debug", 'd', POPT_ARG_NONE, &log_arg.debug, 0,
234 		 N_("Show debugging messages"), NULL},
235 		POPT_TABLEEND};
236 #if 0
237 /* This is a hack to allow translation of popt 1.10 messages with gettext.
238  * Supposedly, this won't be necessary starting with popt 1.12
239  */
240 		N_("Help options:");
241 		N_("Show this help message");
242 		N_("Display brief usage message");
243 #endif
244 	ExifData *ed;
245 	ExifLog *log = NULL;
246 	char fout[1024] = {0, };
247 	int continue_without_file = 0;
248 	struct sigaction sa = {};
249 
250 #ifdef ENABLE_GLIBC_MEMDEBUG
251 	mcheck (NULL);
252 	mtrace ();
253 #endif
254 
255 #ifdef ENABLE_NLS
256 #ifdef HAVE_LOCALE_H
257 	setlocale (LC_ALL, "");
258 #endif
259 	bindtextdomain (PACKAGE, LOCALEDIR);
260 	textdomain (PACKAGE);
261 #endif
262 
263 	/* Return EPIPE errno instead of a SIGPIPE signal on a bad pipe read/write */
264 	sa.sa_handler = SIG_IGN;
265 	sa.sa_flags = 0;
266 	sigemptyset(&sa.sa_mask);
267 	sigaction(SIGPIPE, &sa, NULL);
268 
269 	ctx = poptGetContext (PACKAGE, argc, argv, options, 0);
270 	poptSetOtherOptionHelp (ctx, _("[OPTION...] file"));
271 	while (poptGetNextOpt (ctx) != -1)
272 		;
273 
274 	p.width = MIN(MAX_WIDTH, MAX(MIN_WIDTH, p.width));
275 
276 	log = exif_log_new ();
277 	exif_log_set_func (log, log_func, &log_arg);
278 
279 	/* Identify the parameters */
280 	if (ifd_string) {
281 		p.ifd = exif_ifd_from_string (ifd_string);
282 		if ((p.ifd < EXIF_IFD_0) || (p.ifd >= EXIF_IFD_COUNT) ||
283 		    !exif_ifd_get_name (p.ifd)) {
284 			exif_log (log, -1, "exif",
285 				_("Invalid IFD '%s'. Valid IFDs are "
286 				"'0', '1', 'EXIF', 'GPS', and "
287 				"'Interoperability'."), ifd_string);
288 			return 1;
289 		}
290 		free(ifd_string);
291 		ifd_string = NULL;
292 	}
293 	if (tag_string) {
294 		p.tag = exif_tag_from_string (tag_string);
295 		if (p.tag == EXIF_INVALID_TAG) {
296 			exif_log (log, -1, "exif", _("Invalid tag '%s'!"),
297 				tag_string);
298 			return 1;
299 		}
300 		free(tag_string);
301 		tag_string = NULL;
302 	}
303 
304 	/* Check for all necessary parameters */
305 	if ((p.tag == EXIF_INVALID_TAG) && (p.set_value || show_description)) {
306 		exif_log (log, -1, "exif", _("You need to specify a tag!"));
307 		return 1;
308 	}
309 	if (((p.ifd < EXIF_IFD_0) || (p.ifd >= EXIF_IFD_COUNT)) &&
310 	    (p.set_value || show_description)) {
311 		exif_log (log, -1, "exif", _("You need to specify an IFD!"));
312 		return 1;
313 	}
314 
315 	/* No command: Show help */
316 	if (argc <= 1) {
317 		poptPrintHelp (ctx, stdout, 0);
318 		exif_log_free (log);
319 		poptFreeContext(ctx);
320 		return (1);
321 	}
322 
323 	/* Commands not related to file. You can only specify one. */
324 	if (show_version) {
325 		printf ("%s\n", VERSION);
326 		exif_log_free (log);
327 		poptFreeContext (ctx);
328 		return 0;
329 	}
330 	if (show_description) {
331 		int rc = 0;
332 		const char *name = exif_tag_get_name_in_ifd (p.tag, p.ifd);
333 		if (!name) {
334 			exif_log (log, -1, "exif", _("Unknown Tag"));
335 			rc = 1;
336 
337 		} else if (p.machine_readable) {
338 			/*
339 			 * The C() macro can point to a static buffer so these printfs
340 			 * must be done separately.
341 			 */
342 			printf ("0x%04x\t%s\t", p.tag,
343 				C(exif_tag_get_name_in_ifd (p.tag, p.ifd)));
344 			printf ("%s\t", C(exif_tag_get_title_in_ifd (p.tag, p.ifd)));
345 			printf ("%s\n",
346 				C(exif_tag_get_description_in_ifd (p.tag, p.ifd)));
347 
348 		} else {
349 			printf (_("Tag '%s' "),
350 				C(exif_tag_get_title_in_ifd (p.tag, p.ifd)));
351 			printf (_("(0x%04x, '%s'): "), p.tag,
352 				C(exif_tag_get_name_in_ifd (p.tag, p.ifd)));
353 			printf ("%s\n",
354 				C(exif_tag_get_description_in_ifd (p.tag, p.ifd)));
355 		}
356 
357 		exif_log_free (log);
358 		poptFreeContext (ctx);
359 		return rc;
360 	}
361 
362 	/* Commands related to files */
363 	if (!((args = poptGetArgs (ctx)))) {
364 		if (!create_exif) {
365 			exif_log (log, -1, "exif", _("Specify input file or --create-exif"));
366 			poptPrintHelp (ctx, stdout, 0);
367 			exif_log_free (log);
368 			poptFreeContext (ctx);
369 			return 1;
370 		} else {
371 			/* Give a name to the synthesized EXIF tag set */
372 			static const char * const created_exif_name[] =
373 				{"(EXIF)", NULL};
374 			args = created_exif_name;
375 			continue_without_file = 1;
376 		}
377 	}
378 
379 	while (*args) {
380 		ExifLoader *l;
381 
382 		ed = NULL;
383 		p.fin = *args;
384 		if (!continue_without_file) {
385 			/* Identify the parameters */
386 			if (output)
387 				strncpy (fout, output, sizeof (fout) - 1);
388 			else {
389 				strncpy (fout, *args, sizeof (fout) - 1);
390 				strncat (fout, ".modified.jpeg",
391 					sizeof (fout) - strlen(fout) - 1);
392 				/* Should really abort if this file name is too long */
393 			}
394 
395 			/*
396 			 * Try to read EXIF data from the file.
397 			 * If there is no EXIF data, create it if the user
398 			 * told us to do so.
399 			 */
400 			l = exif_loader_new ();
401 			exif_loader_log (l, log);
402 			if (create_exif)
403 				log_arg.ignore_corrupted = 1;
404 			exif_loader_write_file (l, *args);
405 			log_arg.ignore_corrupted = 0;
406 			if (!log_arg.corrupted)
407 				create_exif = 0;
408 
409 			if (no_fixup)
410 				/* Override the default conversion options */
411 				ed = exif_get_data_opts(l, log, 0, EXIF_DATA_TYPE_UNKNOWN);
412 			else
413 				ed = exif_loader_get_data(l);
414 
415 			exif_loader_unref (l);
416 		}
417 		if (!ed) {
418 			if (create_exif) {
419 				/* Create a new EXIF data set */
420 				ed = exif_data_new ();
421 				exif_data_log (ed, log);
422 				exif_data_set_data_type(ed, EXIF_DATA_TYPE_COMPRESSED);
423 				if (!no_fixup) {
424 					/* Add all the mandatory fields */
425 					exif_data_fix(ed);
426 
427 					/* Create a new date tag */
428 					action_create_value (ed, log, EXIF_TAG_DATE_TIME, EXIF_IFD_0);
429 				}
430 			} else {
431 				exif_log (log, -1, "exif", _("'%s' is not "
432 				  "readable or does not contain EXIF data!"),
433 				  *args);
434 				return 1;
435 			}
436 		}
437 
438 		/* These options can be used in conjunction with others */
439 		if (remove_thumb) {
440 			action_remove_thumb (ed, log, p);
441 			if (!no_fixup)
442 				/* Remove all thumbnail tags */
443 				exif_data_fix(ed);
444 		}
445 		if (p.set_thumb) {
446 			action_insert_thumb (ed, log, p);
447 			if (!no_fixup)
448 				/* Add the mandatory thumbnail tags */
449 				exif_data_fix(ed);
450 		}
451 
452 		/* These options are mutually exclusive */
453 		if (list_tags)
454 			action_tag_table (ed, p);
455 		else if ((p.tag != EXIF_INVALID_TAG) &&
456 			 !p.set_value && !remove_tag)
457 			action_show_tag (ed, log, p);
458 		else if (extract_thumbnail)
459 			action_save_thumb (ed, log, p, fout);
460 		else if (p.set_value)
461 			action_set_value (ed, log, p);
462 		else if (remove_tag)
463 			action_remove_tag (ed, log, p);
464 		else if (list_mnote) {
465 			if (xml_output) {
466 				exif_log (log, -1, "exif", _("XML format is "
467 				  "not available for Maker Notes"));
468 				return 1;
469 			}
470 			action_mnote_list (ed, p);
471 		} else if (p.machine_readable)
472 			action_tag_list_machine (ed, p);
473 		else if (xml_output)
474 			action_tag_list_xml (ed, p);
475 		else if (create_exif && !continue_without_file)
476 			/* Nothing here. Data will be saved later. */
477 			;
478 		else
479 			action_tag_list (ed, p);
480 
481 		if (!continue_without_file &&
482 			(create_exif || p.set_thumb || remove_tag || remove_thumb ||
483 		     p.set_value))
484 			action_save (ed, log, p, fout);
485 
486 		exif_data_unref (ed);
487 
488 		args++;
489 	}
490 
491 	/* Free all remaining libpopt string arguments */
492 	free(p.set_thumb);
493 	free(output);
494 	free(p.set_value);
495 
496 	exif_log_free (log);
497 	poptFreeContext (ctx);
498 
499 	return 0;
500 }
501