1 /* vim: set ts=2 sw=2 et: */
2 
3 /*
4  * Copyright (C) 2002, 2004  Red Hat, Inc.
5  * Copyright (C) 2006-2008  Vincent Untz
6  *
7  * Program written by Havoc Pennington <hp@pobox.com>
8  *                    Ray Strode <rstrode@redhat.com>
9  *                    Vincent Untz <vuntz@gnome.org>
10  *
11  * update-desktop-database is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * update-desktop-database is distributed in the hope that it will be
17  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
18  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with update-desktop-database; see the file COPYING.  If not,
23  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite
24  * 330, Boston, MA 02111-1307, USA.
25  */
26 
27 #include <config.h>
28 
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <glib/gi18n.h>
32 
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <locale.h>
38 
39 #include "keyfileutils.h"
40 #include "validate.h"
41 
42 static gboolean edit_mode = FALSE;
43 static const char** args = NULL;
44 static gboolean delete_original = FALSE;
45 static gboolean rebuild_mime_info_cache = FALSE;
46 static char *vendor_name = NULL;
47 static char *target_dir = NULL;
48 static GSList *edit_actions = NULL;
49 static mode_t permissions = 0644;
50 
51 typedef enum
52 {
53   DFU_SET_KEY_BUILDING, /* temporary type to create an action in multiple steps */
54   DFU_SET_KEY,
55   DFU_REMOVE_KEY,
56   DFU_ADD_TO_LIST,
57   DFU_REMOVE_FROM_LIST,
58   DFU_COPY_KEY
59 } DfuEditActionType;
60 
61 typedef struct
62 {
63   DfuEditActionType  type;
64   char              *key;
65   char              *action_value;
66 } DfuEditAction;
67 
68 static DfuEditAction *
dfu_edit_action_new(DfuEditActionType type,const char * key,const char * action_value)69 dfu_edit_action_new (DfuEditActionType  type,
70                      const char        *key,
71                      const char        *action_value)
72 {
73   DfuEditAction *action;
74 
75   action = g_slice_new0 (DfuEditAction);
76   action->type = type;
77   action->key = g_strdup (key);
78   action->action_value = g_strdup (action_value);
79 
80   return action;
81 }
82 
83 static void
dfu_edit_action_free(DfuEditAction * action)84 dfu_edit_action_free (DfuEditAction *action)
85 {
86   g_assert (action != NULL);
87 
88   g_free (action->key);
89   g_free (action->action_value);
90 
91   g_slice_free (DfuEditAction, action);
92 }
93 
94 static gboolean
files_are_the_same(const char * first,const char * second)95 files_are_the_same (const char *first,
96                     const char *second)
97 {
98   /* This check gets confused by hard links.
99    * but it's too annoying to check if two
100    * paths are the same (though I'm sure there's a
101    * "path canonicalizer" I should be using...)
102    */
103 
104   struct stat first_sb;
105   struct stat second_sb;
106 
107   if (stat (first, &first_sb) < 0)
108     {
109       g_printerr (_("Could not stat \"%s\": %s\n"), first, g_strerror (errno));
110       return TRUE;
111     }
112 
113   if (stat (second, &second_sb) < 0)
114     {
115       g_printerr (_("Could not stat \"%s\": %s\n"), first, g_strerror (errno));
116       return TRUE;
117     }
118 
119   return ((first_sb.st_dev == second_sb.st_dev) &&
120           (first_sb.st_ino == second_sb.st_ino) &&
121           /* Broken paranoia in case an OS doesn't use inodes or something */
122           (first_sb.st_size == second_sb.st_size) &&
123           (first_sb.st_mtime == second_sb.st_mtime));
124 }
125 
126 static gboolean
rebuild_cache(const char * dir,GError ** err)127 rebuild_cache (const char  *dir,
128                GError     **err)
129 {
130   GError *spawn_error;
131   char *argv[4] = { "update-desktop-database", "-q", (char *) dir, NULL };
132   int exit_status;
133   gboolean retval;
134 
135   spawn_error = NULL;
136 
137   retval = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
138                          NULL, NULL, &exit_status, &spawn_error);
139 
140   if (spawn_error != NULL)
141     {
142       g_propagate_error (err, spawn_error);
143       return FALSE;
144     }
145 
146   return exit_status == 0 && retval;
147 }
148 
149 static void
process_one_file(const char * filename,GError ** err)150 process_one_file (const char *filename,
151                   GError    **err)
152 {
153   char *new_filename;
154   GKeyFile *kf = NULL;
155   GError *rebuild_error;
156   GSList *tmp;
157 
158   kf = g_key_file_new ();
159   if (!g_key_file_load_from_file (kf, filename,
160                                   G_KEY_FILE_KEEP_COMMENTS|
161                                   G_KEY_FILE_KEEP_TRANSLATIONS,
162                                   err)) {
163     g_key_file_free (kf);
164     return;
165   }
166 
167   if (!desktop_file_fixup (kf, filename)) {
168     g_key_file_free (kf);
169     g_set_error (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE,
170                  _("Failed to fix the content of the desktop file"));
171     return;
172   }
173 
174   /* Mark file as having been processed by us, so automated
175    * tools can check that desktop files went through our
176    * munging
177    */
178   g_key_file_set_string (kf, GROUP_DESKTOP_ENTRY,
179                          "X-Desktop-File-Install-Version", VERSION);
180 
181   tmp = edit_actions;
182   while (tmp != NULL)
183     {
184       DfuEditAction *action = tmp->data;
185 
186       switch (action->type)
187         {
188           case DFU_SET_KEY:
189             g_key_file_set_string (kf, GROUP_DESKTOP_ENTRY,
190                                    action->key, action->action_value);
191             dfu_key_file_drop_locale_strings (kf, GROUP_DESKTOP_ENTRY,
192                                               action->key);
193             break;
194           case DFU_REMOVE_KEY:
195             g_key_file_remove_key (kf, GROUP_DESKTOP_ENTRY,
196                                    action->key, NULL);
197             dfu_key_file_drop_locale_strings (kf, GROUP_DESKTOP_ENTRY,
198                                               action->key);
199             break;
200           case DFU_ADD_TO_LIST:
201             dfu_key_file_merge_list (kf, GROUP_DESKTOP_ENTRY,
202                                      action->key, action->action_value);
203             break;
204           case DFU_REMOVE_FROM_LIST:
205             dfu_key_file_remove_list (kf, GROUP_DESKTOP_ENTRY,
206                                       action->key, action->action_value);
207             break;
208           case DFU_COPY_KEY:
209             dfu_key_file_copy_key (kf, GROUP_DESKTOP_ENTRY, action->key,
210                                    GROUP_DESKTOP_ENTRY, action->action_value);
211             break;
212           default:
213             g_assert_not_reached ();
214         }
215 
216       tmp = tmp->next;
217     }
218 
219   if (edit_mode)
220     {
221       new_filename = g_strdup (filename);
222     }
223   else
224     {
225       char *basename = g_path_get_basename (filename);
226 
227       if (vendor_name && !g_str_has_prefix (basename, vendor_name))
228         {
229           char *new_base;
230           new_base = g_strconcat (vendor_name, "-", basename, NULL);
231           new_filename = g_build_filename (target_dir, new_base, NULL);
232           g_free (new_base);
233         }
234       else
235         {
236           new_filename = g_build_filename (target_dir, basename, NULL);
237         }
238 
239       g_free (basename);
240     }
241 
242   if (!dfu_key_file_to_path (kf, new_filename, err)) {
243     g_key_file_free (kf);
244     g_free (new_filename);
245     return;
246   }
247 
248   g_key_file_free (kf);
249 
250   /* Load and validate the file we just wrote */
251   if (!desktop_file_validate (new_filename, FALSE, TRUE, TRUE))
252     {
253       g_set_error (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE,
254                    _("Failed to validate the created desktop file"));
255 
256       if (!files_are_the_same (filename, new_filename))
257         g_unlink (new_filename);
258 
259       g_free (new_filename);
260       return;
261     }
262 
263   if (!edit_mode)
264     {
265       if (g_chmod (new_filename, permissions) < 0)
266         {
267           g_set_error (err, G_FILE_ERROR,
268                        g_file_error_from_errno (errno),
269                        _("Failed to set permissions %o on \"%s\": %s"),
270                        permissions, new_filename, g_strerror (errno));
271 
272           if (!files_are_the_same (filename, new_filename))
273             g_unlink (new_filename);
274 
275           g_free (new_filename);
276           return;
277         }
278 
279       if (delete_original &&
280           !files_are_the_same (filename, new_filename))
281         {
282           if (g_unlink (filename) < 0)
283             g_printerr (_("Error removing original file \"%s\": %s\n"),
284                         filename, g_strerror (errno));
285         }
286     }
287 
288   if (rebuild_mime_info_cache)
289     {
290       rebuild_error = NULL;
291       rebuild_cache (target_dir, &rebuild_error);
292 
293       if (rebuild_error != NULL)
294         g_propagate_error (err, rebuild_error);
295     }
296 
297   g_free (new_filename);
298 }
299 
300 static gboolean parse_install_options_callback (const gchar  *option_name,
301                                                 const gchar  *value,
302                                                 gpointer      data,
303                                                 GError      **error);
304 
305 static gboolean parse_edit_options_callback (const gchar  *option_name,
306                                              const gchar  *value,
307                                              gpointer      data,
308                                              GError      **error);
309 
310 
311 static const GOptionEntry main_options[] = {
312   {
313     "rebuild-mime-info-cache",
314     '\0',
315     '\0',
316     G_OPTION_ARG_NONE,
317     &rebuild_mime_info_cache,
318     N_("Rebuild the MIME types application database after processing desktop files"),
319     NULL
320   },
321   {
322     "edit-mode",
323     '\0',
324     G_OPTION_FLAG_HIDDEN, /* just for development purpose */
325     G_OPTION_ARG_NONE,
326     &edit_mode,
327     N_("Force edit mode"),
328     NULL
329   },
330   { G_OPTION_REMAINING,
331     0,
332     0,
333     G_OPTION_ARG_FILENAME_ARRAY,
334     &args,
335     NULL,
336     N_("[FILE...]") },
337   {
338     NULL
339   }
340 };
341 
342 static const GOptionEntry install_options[] = {
343   {
344 #define OPTION_DIR "dir"
345     OPTION_DIR,
346     '\0',
347     '\0',
348     G_OPTION_ARG_CALLBACK,
349     parse_install_options_callback,
350     N_("Install desktop files to the DIR directory"),
351     N_("DIR")
352   },
353   {
354 #define OPTION_MODE "mode"
355     OPTION_MODE,
356     'm',
357     '\0',
358     G_OPTION_ARG_CALLBACK,
359     parse_install_options_callback,
360     N_("Set the permissions of the destination files to MODE"),
361     N_("MODE")
362   },
363   {
364 #define OPTION_VENDOR "vendor"
365     OPTION_VENDOR,
366     '\0',
367     '\0',
368     G_OPTION_ARG_CALLBACK,
369     parse_install_options_callback,
370     N_("Add a vendor prefix to the desktop files, if not already present"),
371     N_("VENDOR")
372   },
373   {
374     "delete-original",
375     '\0',
376     '\0',
377     G_OPTION_ARG_NONE,
378     &delete_original,
379     N_("Delete the source desktop files, leaving only the target files (effectively \"renames\" the desktop files)"),
380     NULL
381   },
382   {
383     NULL
384   }
385 };
386 
387 static const GOptionEntry edit_options[] = {
388   {
389 #define OPTION_SET_KEY "set-key"
390     OPTION_SET_KEY,
391     '\0',
392     '\0',
393     G_OPTION_ARG_CALLBACK,
394     parse_edit_options_callback,
395     N_("Set the KEY key to VALUE passed to next --set-value option"),
396     N_("KEY")
397   },
398   {
399 #define OPTION_SET_VALUE "set-value"
400     OPTION_SET_VALUE,
401     '\0',
402     '\0',
403     G_OPTION_ARG_CALLBACK,
404     parse_edit_options_callback,
405     N_("Set the KEY key from previous --set-key option to VALUE"),
406     N_("VALUE")
407   },
408   {
409 #define OPTION_SET_NAME "set-name"
410     OPTION_SET_NAME,
411     '\0',
412     '\0',
413     G_OPTION_ARG_CALLBACK,
414     parse_edit_options_callback,
415     N_("Set the \"Name\" key to NAME"),
416     N_("NAME")
417   },
418   {
419 #define OPTION_COPY_GENERIC_NAME "copy-generic-name-to-name"
420     OPTION_COPY_GENERIC_NAME,
421     '\0',
422     G_OPTION_FLAG_NO_ARG,
423     G_OPTION_ARG_CALLBACK,
424     parse_edit_options_callback,
425     N_("Copy the value of the \"GenericName\" key to the \"Name\" key"),
426     NULL
427   },
428   {
429 #define OPTION_SET_GENERIC_NAME "set-generic-name"
430     OPTION_SET_GENERIC_NAME,
431     '\0',
432     '\0',
433     G_OPTION_ARG_CALLBACK,
434     parse_edit_options_callback,
435     N_("Set the \"GenericName\" key to GENERIC-NAME"),
436     N_("GENERIC-NAME")
437   },
438   {
439 #define OPTION_COPY_NAME "copy-name-to-generic-name"
440     OPTION_COPY_NAME,
441     '\0',
442     G_OPTION_FLAG_NO_ARG,
443     G_OPTION_ARG_CALLBACK,
444     parse_edit_options_callback,
445     N_("Copy the value of the \"Name\" key to the \"GenericName\" key"),
446     NULL
447   },
448   {
449 #define OPTION_SET_COMMENT "set-comment"
450     OPTION_SET_COMMENT,
451     '\0',
452     '\0',
453     G_OPTION_ARG_CALLBACK,
454     parse_edit_options_callback,
455     N_("Set the \"Comment\" key to COMMENT"),
456     N_("COMMENT")
457   },
458   {
459 #define OPTION_SET_ICON "set-icon"
460     OPTION_SET_ICON,
461     '\0',
462     '\0',
463     G_OPTION_ARG_CALLBACK,
464     parse_edit_options_callback,
465     N_("Set the \"Icon\" key to ICON"),
466     N_("ICON")
467   },
468   {
469 #define OPTION_ADD_CATEGORY "add-category"
470     OPTION_ADD_CATEGORY,
471     '\0',
472     '\0',
473     G_OPTION_ARG_CALLBACK,
474     parse_edit_options_callback,
475     N_("Add CATEGORY to the list of categories"),
476     N_("CATEGORY")
477   },
478   {
479 #define OPTION_REMOVE_CATEGORY "remove-category"
480     OPTION_REMOVE_CATEGORY,
481     '\0',
482     '\0',
483     G_OPTION_ARG_CALLBACK,
484     parse_edit_options_callback,
485     N_("Remove CATEGORY from the list of categories"),
486     N_("CATEGORY")
487   },
488   {
489 #define OPTION_ADD_MIME_TYPE "add-mime-type"
490     OPTION_ADD_MIME_TYPE,
491     '\0',
492     '\0',
493     G_OPTION_ARG_CALLBACK,
494     parse_edit_options_callback,
495     N_("Add MIME-TYPE to the list of MIME types"),
496     N_("MIME-TYPE")
497   },
498   {
499 #define OPTION_REMOVE_MIME_TYPE "remove-mime-type"
500     OPTION_REMOVE_MIME_TYPE,
501     '\0',
502     '\0',
503     G_OPTION_ARG_CALLBACK,
504     parse_edit_options_callback,
505     N_("Remove MIME-TYPE from the list of MIME types"),
506     N_("MIME-TYPE")
507   },
508   {
509 #define OPTION_ADD_ONLY_SHOW_IN "add-only-show-in"
510     OPTION_ADD_ONLY_SHOW_IN,
511     '\0',
512     '\0',
513     G_OPTION_ARG_CALLBACK,
514     parse_edit_options_callback,
515     N_("Add ENVIRONMENT to the list of desktop environment where the desktop files should be displayed"),
516     N_("ENVIRONMENT")
517   },
518   {
519 #define OPTION_REMOVE_ONLY_SHOW_IN "remove-only-show-in"
520     OPTION_REMOVE_ONLY_SHOW_IN,
521     '\0',
522     '\0',
523     G_OPTION_ARG_CALLBACK,
524     parse_edit_options_callback,
525     N_("Remove ENVIRONMENT from the list of desktop environment where the desktop files should be displayed"),
526     N_("ENVIRONMENT")
527   },
528   {
529 #define OPTION_ADD_NOT_SHOW_IN "add-not-show-in"
530     OPTION_ADD_NOT_SHOW_IN,
531     '\0',
532     '\0',
533     G_OPTION_ARG_CALLBACK,
534     parse_edit_options_callback,
535     N_("Add ENVIRONMENT to the list of desktop environment where the desktop files should not be displayed"),
536     N_("ENVIRONMENT")
537   },
538   {
539 #define OPTION_REMOVE_NOT_SHOW_IN "remove-not-show-in"
540     OPTION_REMOVE_NOT_SHOW_IN,
541     '\0',
542     '\0',
543     G_OPTION_ARG_CALLBACK,
544     parse_edit_options_callback,
545     N_("Remove ENVIRONMENT from the list of desktop environment where the desktop files should not be displayed"),
546     N_("ENVIRONMENT")
547   },
548   {
549 #define OPTION_REMOVE_KEY "remove-key"
550     OPTION_REMOVE_KEY,
551     '\0',
552     '\0',
553     G_OPTION_ARG_CALLBACK,
554     parse_edit_options_callback,
555     N_("Remove the KEY key from the desktop files, if present"),
556     N_("KEY")
557   },
558   {
559     NULL
560   }
561 };
562 
563 static gboolean
parse_install_options_callback(const gchar * option_name,const gchar * value,gpointer data,GError ** error)564 parse_install_options_callback (const gchar  *option_name,
565                                 const gchar  *value,
566                                 gpointer      data,
567                                 GError      **error)
568 {
569   /* skip "-" or "--" */
570   option_name++;
571   if (*option_name == '-')
572     option_name++;
573 
574   if (strcmp (OPTION_DIR, option_name) == 0)
575     {
576       if (target_dir)
577         {
578           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
579                        _("Can only specify --dir once"));
580           return FALSE;
581         }
582 
583       target_dir = g_strdup (value);
584     }
585 
586   else if (strcmp (OPTION_MODE, option_name) == 0 ||
587            strcmp ("m", option_name) == 0)
588     {
589       unsigned long ul;
590       char *end;
591 
592       end = NULL;
593       ul = strtoul (value, &end, 8);
594       if (*value && end && *end == '\0')
595         permissions = ul;
596       else
597         {
598           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
599                        _("Could not parse mode string \"%s\""), value);
600 
601           return FALSE;
602         }
603     }
604 
605   else if (strcmp (OPTION_VENDOR, option_name) == 0)
606     {
607       if (vendor_name)
608         {
609           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
610               _("Can only specify --vendor once"));
611           return FALSE;
612         }
613 
614       vendor_name = g_strdup (value);
615     }
616 
617   else
618     {
619       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
620                    _("Unknown option \"%s\""), option_name);
621 
622       return FALSE;
623     }
624 
625   return TRUE;
626 }
627 
628 static gboolean
parse_edit_options_callback(const gchar * option_name,const gchar * value,gpointer data,GError ** error)629 parse_edit_options_callback (const gchar  *option_name,
630                              const gchar  *value,
631                              gpointer      data,
632                              GError      **error)
633 {
634   /* Note: we prepend actions to the list, so we'll need to reverse it later */
635 
636   DfuEditAction  *action;
637   char          **list;
638   int             i;
639 
640   /* skip "-" or "--" */
641   option_name++;
642   if (*option_name == '-')
643     option_name++;
644 
645 #define PARSE_OPTION_LIST(type, key)                                    \
646   do {                                                                  \
647     list = g_strsplit (value, ";", 0);                                  \
648     for (i = 0; list[i]; i++)                                           \
649       {                                                                 \
650         if (*list[i] == '\0')                                           \
651           continue;                                                     \
652                                                                         \
653         action = dfu_edit_action_new (type, key, list[i]);              \
654         edit_actions = g_slist_prepend (edit_actions, action);          \
655       }                                                                 \
656   } while (0)
657 
658   if (strcmp (OPTION_SET_KEY, option_name) == 0)
659     {
660       action = dfu_edit_action_new (DFU_SET_KEY_BUILDING, value, NULL);
661       edit_actions = g_slist_prepend (edit_actions, action);
662     }
663 
664   else if (strcmp (OPTION_SET_VALUE, option_name) == 0)
665     {
666       gboolean handled = FALSE;
667 
668       if (edit_actions != NULL)
669         {
670           action = edit_actions->data;
671           if (action->type == DFU_SET_KEY_BUILDING)
672             {
673               action->type = DFU_SET_KEY;
674               action->action_value = g_strdup (value);
675 
676               handled = TRUE;
677             }
678         }
679 
680       if (!handled)
681         {
682           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
683                        _("Option \"--%s\" used without a prior \"--%s\" option"),
684                        OPTION_SET_VALUE, OPTION_SET_KEY);
685 
686           return FALSE;
687         }
688     }
689 
690   else if (strcmp (OPTION_SET_NAME, option_name) == 0)
691     {
692       action = dfu_edit_action_new (DFU_SET_KEY, "Name", value);
693       edit_actions = g_slist_prepend (edit_actions, action);
694     }
695 
696   else if (strcmp (OPTION_COPY_GENERIC_NAME, option_name) == 0)
697     {
698       action = dfu_edit_action_new (DFU_COPY_KEY, "GenericName", "Name");
699       edit_actions = g_slist_prepend (edit_actions, action);
700     }
701 
702   else if (strcmp (OPTION_SET_GENERIC_NAME, option_name) == 0)
703     {
704       action = dfu_edit_action_new (DFU_SET_KEY, "GenericName", value);
705       edit_actions = g_slist_prepend (edit_actions, action);
706     }
707 
708   else if (strcmp (OPTION_COPY_NAME, option_name) == 0)
709     {
710       action = dfu_edit_action_new (DFU_COPY_KEY, "Name", "GenericName");
711       edit_actions = g_slist_prepend (edit_actions, action);
712     }
713 
714   else if (strcmp (OPTION_SET_COMMENT, option_name) == 0)
715     {
716       action = dfu_edit_action_new (DFU_SET_KEY, "Comment", value);
717       edit_actions = g_slist_prepend (edit_actions, action);
718     }
719 
720   else if (strcmp (OPTION_SET_ICON, option_name) == 0)
721     {
722       action = dfu_edit_action_new (DFU_SET_KEY, "Icon", value);
723       edit_actions = g_slist_prepend (edit_actions, action);
724     }
725 
726   else if (strcmp (OPTION_ADD_CATEGORY, option_name) == 0)
727     {
728       PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "Categories");
729     }
730 
731   else if (strcmp (OPTION_REMOVE_CATEGORY, option_name) == 0)
732     {
733       PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "Categories");
734     }
735 
736   else if (strcmp (OPTION_ADD_MIME_TYPE, option_name) == 0)
737     {
738       PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "MimeType");
739     }
740 
741   else if (strcmp (OPTION_REMOVE_MIME_TYPE, option_name) == 0)
742     {
743       PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "MimeType");
744     }
745 
746   else if (strcmp (OPTION_ADD_ONLY_SHOW_IN, option_name) == 0)
747     {
748       PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "OnlyShowIn");
749     }
750 
751   else if (strcmp (OPTION_REMOVE_ONLY_SHOW_IN, option_name) == 0)
752     {
753       PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "OnlyShowIn");
754     }
755 
756   else if (strcmp (OPTION_ADD_NOT_SHOW_IN, option_name) == 0)
757     {
758       PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "NotShowIn");
759     }
760 
761   else if (strcmp (OPTION_REMOVE_NOT_SHOW_IN, option_name) == 0)
762     {
763       PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "NotShowIn");
764     }
765 
766   else if (strcmp (OPTION_REMOVE_KEY, option_name) == 0)
767     {
768       action = dfu_edit_action_new (DFU_REMOVE_KEY, value, NULL);
769       edit_actions = g_slist_prepend (edit_actions, action);
770     }
771 
772   else
773     {
774       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
775                    _("Unknown option \"%s\""), option_name);
776 
777       return FALSE;
778     }
779 
780   return TRUE;
781 }
782 
783 static gboolean
check_no_building_in_edit_actions(GError ** error)784 check_no_building_in_edit_actions (GError **error)
785 {
786   GSList *tmp = edit_actions;
787 
788   while (tmp != NULL)
789     {
790       /* If we have an action that is BUILDING, then it means that we had a
791        * --set-key not followed by a --set-value. This can happen when:
792        *   + the very last argument was --set-key
793        *   + --set-key was followed by another editing option
794        * In both cases, that's bad as what we want is a --set-value following
795        * each --set-key.
796        */
797       DfuEditAction *action = tmp->data;
798 
799       if (action->type == DFU_SET_KEY_BUILDING)
800         {
801           g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
802                        _("Option \"--%s\" used without a following \"--%s\" option"),
803                        OPTION_SET_KEY, OPTION_SET_VALUE);
804 
805           return FALSE;
806         }
807 
808       tmp = tmp->next;
809     }
810 
811   return TRUE;
812 }
813 
814 static gboolean
post_parse_edit_options_callback(GOptionContext * context,GOptionGroup * group,gpointer data,GError ** error)815 post_parse_edit_options_callback (GOptionContext  *context,
816                                   GOptionGroup    *group,
817                                   gpointer         data,
818                                   GError         **error)
819 {
820   if (!check_no_building_in_edit_actions (error))
821     return FALSE;
822 
823   /* Reverse list we created by prepending elements */
824   edit_actions = g_slist_reverse (edit_actions);
825 
826   return TRUE;
827 }
828 
829 int
main(int argc,char ** argv)830 main (int argc, char **argv)
831 {
832   GOptionContext *context;
833   GOptionGroup *group;
834   GError* err = NULL;
835   int i;
836   int args_len;
837   mode_t dir_permissions;
838   char *basename;
839 
840   setlocale (LC_ALL, "");
841 
842   basename = g_path_get_basename (argv[0]);
843   if (g_strcmp0 (basename, "desktop-file-edit") == 0)
844     edit_mode = TRUE;
845   g_free (basename);
846 
847   context = g_option_context_new ("");
848   g_option_context_set_summary (context, edit_mode ? _("Edit a desktop file.") : _("Install desktop files."));
849   g_option_context_add_main_entries (context, main_options, NULL);
850 
851   if (!edit_mode)
852     {
853       group = g_option_group_new ("install", _("Installation options for desktop file"), _("Show desktop file installation options"), NULL, NULL);
854       g_option_group_add_entries (group, install_options);
855       g_option_context_add_group (context, group);
856     }
857 
858   group = g_option_group_new ("edit", _("Edition options for desktop file"), _("Show desktop file edition options"), NULL, NULL);
859   g_option_group_add_entries (group, edit_options);
860   g_option_group_set_parse_hooks (group, NULL, post_parse_edit_options_callback);
861   g_option_context_add_group (context, group);
862 
863   err = NULL;
864   g_option_context_parse (context, &argc, &argv, &err);
865 
866   if (err != NULL) {
867     g_printerr ("%s\n", err->message);
868     g_printerr (_("Run '%s --help' to see a full list of available command line options.\n"), argv[0]);
869     g_error_free (err);
870     return 1;
871   }
872 
873   if (!edit_mode)
874     {
875       if (vendor_name == NULL && g_getenv ("DESKTOP_FILE_VENDOR"))
876         vendor_name = g_strdup (g_getenv ("DESKTOP_FILE_VENDOR"));
877 
878       if (target_dir == NULL && g_getenv ("DESKTOP_FILE_INSTALL_DIR"))
879         target_dir = g_strdup (g_getenv ("DESKTOP_FILE_INSTALL_DIR"));
880 
881       if (target_dir == NULL)
882         {
883           if (g_getenv ("RPM_BUILD_ROOT"))
884             target_dir = g_build_filename (g_getenv ("RPM_BUILD_ROOT"), DATADIR, "applications", NULL);
885           else
886             target_dir = g_build_filename (DATADIR, "applications", NULL);
887         }
888 
889       /* Create the target directory */
890       dir_permissions = permissions;
891 
892       /* Add search bit when the target file is readable */
893       if (permissions & 0400)
894         dir_permissions |= 0100;
895       if (permissions & 0040)
896         dir_permissions |= 0010;
897       if (permissions & 0004)
898         dir_permissions |= 0001;
899 
900       g_mkdir_with_parents (target_dir, dir_permissions);
901     }
902 
903   args_len = 0;
904   for (i = 0; args && args[i]; i++)
905     args_len++;
906 
907   if (edit_mode)
908     {
909       if (args_len == 0)
910         {
911           g_printerr (_("Must specify a desktop file to process.\n"));
912           return 1;
913         }
914       if (args_len > 1)
915         {
916           g_printerr (_("Only one desktop file can be processed at once.\n"));
917           return 1;
918         }
919     }
920   else
921     {
922       if (args_len == 0)
923         {
924           g_printerr (_("Must specify one or more desktop files to process.\n"));
925           return 1;
926         }
927     }
928 
929   for (i = 0; args && args[i]; i++)
930     {
931       err = NULL;
932       process_one_file (args[i], &err);
933       if (err != NULL)
934         {
935           g_printerr (_("Error on file \"%s\": %s\n"),
936                       args[i], err->message);
937           g_error_free (err);
938 
939           return 1;
940         }
941     }
942 
943 #if GLIB_CHECK_VERSION(2,28,0)
944   g_slist_free_full (edit_actions, (GDestroyNotify) dfu_edit_action_free);
945 #else
946   g_slist_foreach (edit_actions, (GFunc) dfu_edit_action_free, NULL);
947   g_slist_free (edit_actions);
948 #endif
949 
950   g_option_context_free (context);
951 
952   return 0;
953 }
954