add error handling for gnunet-qr
[oweals/gnunet.git] / src / util / gnunet-qr.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2013-2019 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20 /**
21  * @file util/gnunet-qr.c
22  * @author Hartmut Goebel (original implementation)
23  * @author Martin Schanzenbach (integrate gnunet-uri)
24  * @author Christian Grothoff (error handling)
25  */
26 #include <stdio.h>
27 #include <zbar.h>
28 #include <stdbool.h>
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31
32 #define LOG(fmt, ...) if (verbose == true) printf(fmt, ## __VA_ARGS__)
33
34 /**
35  * Video device to capture from. Sane default for GNU/Linux systems.
36  */
37 static char* device = "/dev/video0";
38
39 /**
40  * --verbose option
41  */
42 static int verbose = false;
43
44 /**
45  * --silent option
46  */
47 static int silent = false;
48
49 /**
50  * Handler exit code
51  */
52 static long unsigned int exit_code = 1;
53
54 /**
55  * Helper process we started.
56  */
57 static struct GNUNET_OS_Process *p;
58
59
60 /**
61  * Pipe used to communicate child death via signal.
62  */
63 static struct GNUNET_DISK_PipeHandle *sigpipe;
64
65
66 /**
67  * Task triggered whenever we receive a SIGCHLD (child
68  * process died) or when user presses CTRL-C.
69  *
70  * @param cls closure, NULL
71  */
72 static void
73 maint_child_death (void *cls)
74 {
75   enum GNUNET_OS_ProcessStatusType type;
76
77   if ( (GNUNET_OK !=
78         GNUNET_OS_process_status (p, &type, &exit_code)) ||
79        (type != GNUNET_OS_PROCESS_EXITED) )
80     GNUNET_break (0 == GNUNET_OS_process_kill (p, GNUNET_TERM_SIG));
81   GNUNET_OS_process_destroy (p);
82 }
83
84
85 /**
86  * Dispatch URIs to the appropriate GNUnet helper process
87  *
88  * @param cls closure
89  * @param uri uri to dispatch
90  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
91  * @param cfg configuration
92  */
93 static void
94 gnunet_uri (void *cls,
95             const char *uri,
96             const char *cfgfile,
97             const struct GNUNET_CONFIGURATION_Handle *cfg)
98 {
99   const char *orig_uri;
100   const char *slash;
101   char *subsystem;
102   char *program;
103   struct GNUNET_SCHEDULER_Task * rt;
104
105   orig_uri = uri;
106   if (0 != strncasecmp ("gnunet://", uri, strlen ("gnunet://"))) {
107     fprintf (stderr,
108              _("Invalid URI: does not start with `%s'\n"),
109              "gnunet://");
110     return;
111   }
112   uri += strlen ("gnunet://");
113   if (NULL == (slash = strchr (uri, '/')))
114   {
115     fprintf (stderr,
116              _("Invalid URI: fails to specify subsystem\n"));
117     return;
118   }
119   subsystem = GNUNET_strndup (uri, slash - uri);
120   if (GNUNET_OK !=
121       GNUNET_CONFIGURATION_get_value_string (cfg,
122                                              "uri",
123                                              subsystem,
124                                              &program))
125   {
126     fprintf (stderr,
127              _("No handler known for subsystem `%s'\n"),
128              subsystem);
129     GNUNET_free (subsystem);
130     return;
131   }
132   GNUNET_free (subsystem);
133   rt = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
134                                        GNUNET_DISK_pipe_handle (sigpipe,
135                                                                 GNUNET_DISK_PIPE_END_READ),
136                                        &maint_child_death, NULL);
137   p = GNUNET_OS_start_process (GNUNET_NO, 0,
138                                NULL, NULL, NULL,
139                                program,
140                                program,
141                                orig_uri,
142                                NULL);
143   GNUNET_free (program);
144   if (NULL == p)
145     GNUNET_SCHEDULER_cancel (rt);
146 }
147
148
149 /**
150  * Obtain QR code 'symbol' from @a proc.
151  *
152  * @param proc zbar processor to use
153  * @return NULL on error
154  */
155 static const zbar_symbol_t *
156 get_symbol (zbar_processor_t *proc)
157 {
158   const zbar_symbol_set_t* symbols;
159   int rc;
160   int n;
161
162   if (0 !=
163       zbar_processor_parse_config (proc, "enable"))
164   {
165     GNUNET_break (0);
166     return NULL;
167   }
168
169   /* initialize the Processor */
170   if (0 !=
171       (rc = zbar_processor_init(proc, device, 1)))
172   {
173     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174                 "Failed to open device `%s': %d\n",
175                 device,
176                 rc);
177     return NULL;
178   }
179
180   /* enable the preview window */
181   if ( (0 != (rc = zbar_processor_set_visible (proc, 1))) ||
182        (0 != (rc = zbar_processor_set_active(proc, 1))) )
183   {
184     GNUNET_break (0);
185     return NULL;
186   }
187
188   /* read at least one barcode (or until window closed) */
189   LOG ("Capturing\n");
190   n = zbar_process_one (proc, -1);
191
192   /* hide the preview window */
193   (void) zbar_processor_set_active (proc, 0);
194   (void) zbar_processor_set_visible (proc, 0);
195   if (-1 == n)
196     return NULL; /* likely user closed the window */
197   LOG ("Got %i images\n",
198        n);
199   /* extract results */
200   symbols = zbar_processor_get_results (proc);
201   if (NULL == symbols)
202   {
203     GNUNET_break (0);
204     return NULL;
205   }
206   return zbar_symbol_set_first_symbol (symbols);
207 }
208
209
210 /**
211  * Run zbar QR code parser.
212  *
213  * @return NULL on error, otherwise the URI that we found
214  */
215 static char *
216 run_zbar ()
217 {
218   zbar_processor_t *proc;
219   const char *data;
220   char *ret;
221   const zbar_symbol_t* symbol;
222
223   /* configure the Processor */
224   proc = zbar_processor_create (1);
225   if (NULL == proc)
226   {
227     GNUNET_break (0);
228     return NULL;
229   }
230
231   symbol = get_symbol (proc);
232   if (NULL == symbol)
233   {
234     zbar_processor_destroy (proc);
235     return NULL;
236   }
237   data = zbar_symbol_get_data (symbol);
238   if (NULL == data)
239   {
240     GNUNET_break (0);
241     zbar_processor_destroy (proc);
242     return NULL;
243   }
244   LOG ("Found %s \"%s\"\n",
245        zbar_get_symbol_name (zbar_symbol_get_type(symbol)),
246        data);
247   ret = GNUNET_strdup (data);
248   /* clean up */
249   zbar_processor_destroy(proc);
250   return ret;
251 }
252
253
254 /**
255  * Main function that will be run by the scheduler.
256  *
257  * @param cls closure
258  * @param args remaining command-line arguments
259  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
260  * @param cfg configuration
261  */
262 static void
263 run (void *cls,
264      char *const *args,
265      const char *cfgfile,
266      const struct GNUNET_CONFIGURATION_Handle *cfg)
267 {
268   char *data;
269
270   data = run_zbar ();
271   if (NULL == data)
272     return;
273   gnunet_uri (cls,
274               data,
275               cfgfile,
276               cfg);
277   if (exit_code != 0)
278   {
279     printf ("Failed to add URI %s\n",
280             data);
281   }
282   else
283   {
284     printf ("Added URI %s\n",
285             data);
286   }
287   GNUNET_free (data);
288 };
289
290
291 int
292 main (int argc, char *const *argv)
293 {
294   int ret;
295   struct GNUNET_GETOPT_CommandLineOption options[] = {
296     GNUNET_GETOPT_option_string ('d', "device", "DEVICE",
297      gettext_noop ("use video-device DEVICE (default: /dev/video0"),
298      &device),
299     GNUNET_GETOPT_option_flag ('\0', "verbose",
300      gettext_noop ("be verbose"),
301      &verbose),
302     GNUNET_GETOPT_option_flag ('s', "silent",
303      gettext_noop ("do not show preview windows"),
304                                &silent),
305     GNUNET_GETOPT_OPTION_END
306   };
307
308   ret = GNUNET_PROGRAM_run (argc,
309                             argv,
310                             "gnunet-qr",
311                             gettext_noop ("Scan a QR code using a video device and import the uri read"),
312                             options, &run, NULL);
313   return ((GNUNET_OK == ret) && (0 == exit_code)) ? 0 : 1;
314 }