chiark / gitweb /
Properly quote various error messages
[ypp-sc-tools.main.git] / yarrg / convert.c
1 /*
2  * yarrg main program: argument parsing etc.
3  */
4 /*
5  *  This is part of ypp-sc-tools, a set of third-party tools for assisting
6  *  players of Yohoho Puzzle Pirates.
7  * 
8  *  Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
9  * 
10  *  This program is free software: you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation, either version 3 of the License, or
13  *  (at your option) any later version.
14  * 
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  * 
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  * 
23  *  Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
24  *  are used without permission.  This program is not endorsed or
25  *  sponsored by Three Rings.
26  */
27
28 #include "convert.h"
29
30 const char *get_vardir(void) { return "."; }
31 const char *get_libdir(void) { return "."; }
32
33
34 enum outmodekind {
35   omk_unset, omk_raw, omk_str, omk_upload_yarrg, omk_upload_pctb
36 };
37
38 typedef struct {
39   enum outmodekind kind; /* unset is sentinel */
40   const char *str; /* non-0 iff not unset or raw */
41 } OutputMode;
42
43 static OutputMode o_outmodes[10];
44
45 static char *o_screenshot_fn;
46 static const char *o_serv_pctb, *o_serv_yarrg;
47 static const char *o_serv_dict_fetch, *o_serv_dict_submit;
48
49 const char *o_resolver= "./dictionary-manager";
50 FILE *screenshot_file;
51 const char *o_ocean, *o_pirate;
52 int o_quiet;
53
54 static pid_t screenshot_compressor=-1;
55
56 enum mode o_mode= mode_all;
57 enum flags o_flags=
58    ff_charset_allowedit |
59    ff_dict_fetch|ff_dict_submit|ff_dict_pirate;
60
61 static void vbadusage(const char *fmt, va_list) FMT(1,0) NORET;
62 static void vbadusage(const char *fmt, va_list al) {
63   fputs("bad usage: ",stderr);
64   vfprintf(stderr,fmt,al);
65   fputc('\n',stderr);
66   exit(12);
67 }
68 DEFINE_VWRAPPERF(static, badusage, NORET);
69
70 static void open_screenshot_file(int for_write) {
71   if (!fnmatch("*.gz",o_screenshot_fn,0)) {
72     int mode= for_write ? O_WRONLY|O_CREAT|O_TRUNC : O_RDONLY;
73     sysassert(! gzopen(o_screenshot_fn, mode, &screenshot_file,
74                        &screenshot_compressor, "-1") );
75   } else {
76     screenshot_file= fopen(o_screenshot_fn, for_write ? "w" : "r");
77     if (!screenshot_file)
78       fatal("could not open screenshots file `%s': %s",
79             o_screenshot_fn, strerror(errno));
80   }
81 }
82
83 static void run_analysis(void) {
84   FILE *tf;
85   OutputMode *om;
86
87   sysassert( tf= tmpfile() );
88   progress("running recognition...");
89   analyse(tf);
90
91   if (o_flags & ff_upload) {
92     if (o_flags & ff_singlepage)
93       fatal("Recognition successful, but refusing to upload partial data\n"
94             " (--single-page specified).  Specify an output mode?");
95   }
96
97   for (om=o_outmodes; om->kind != omk_unset; om++) {
98     sysassert( fseek(tf,0,SEEK_SET) == 0);
99
100     progress_log("processing results (--%s)...", om->str);
101     pid_t processor;
102     sysassert( (processor= fork()) != -1 );
103
104     if (!processor) {
105       sysassert( dup2(fileno(tf),0) ==0 );
106       if (om->kind==omk_raw) {
107         execlp("cat","cat",(char*)0);
108         sysassert(!"execute cat");
109       } else {
110         EXECLP_HELPER("commod-results-processor", om->str, (char*)0);
111       }
112     }
113
114     waitpid_check_exitstatus(processor, "output processor/uploader", 0);
115   }
116   
117   fclose(tf);
118   progress_log("all complete.");
119 }
120
121 static void rsync_core(const char *stem, const char *suffix,
122                        const char *zopt) {
123   pid_t fetcher;
124
125   progress("Updating dictionary %s...",stem);
126
127   sysassert( (fetcher= fork()) != -1 );
128   if (!fetcher) {
129     const char *rsync= getenv("YPPSC_YARRG_RSYNC");
130     if (!rsync) rsync= "rsync";
131   
132     const char *src= getenv("YPPSC_YARRG_DICT_UPDATE");
133     char *remote= masprintf("%s/master-%s.txt%s", src, stem, suffix);
134     char *local= masprintf("_master-%s.txt%s", stem, suffix);
135     if (DEBUGP(rsync))
136       fprintf(stderr,"executing rsync to fetch %s to %s\n",remote,local);
137     char *opts= masprintf("-Lt%s%s",
138                           zopt,
139                           DEBUGP(rsync) ? "v" : "");
140     execlp(rsync, "rsync",opts,"--",remote,local,(char*)0);
141     sysassert(!"exec rsync failed");
142   }
143
144   waitpid_check_exitstatus(fetcher, "rsync", 0);
145 }
146
147 void fetch_with_rsync_gz(const char *stem) { rsync_core(stem,".gz",""); }
148 void fetch_with_rsync(const char *stem) { rsync_core(stem,"","z"); }
149
150 static void get_timestamp(void) {
151   FILE *tf;
152   pid_t child;
153   sysassert( tf= tmpfile() );
154
155   sysassert( (child= fork()) != -1 );
156   if (!child) {
157     sysassert( dup2(fileno(tf),1)==1 );
158     EXECLP_HELPER("database-info-fetch","timestamp",(char*)0);
159   }
160   waitpid_check_exitstatus(child,"timestamp request",0);
161
162   sysassert( fseek(tf,0,SEEK_SET) == 0 );
163   static char lbuf[30];
164   int l= fread(lbuf,1,sizeof(lbuf),tf);
165   sysassert( !ferror(tf) );
166   assert( feof(tf) );
167   assert( l>1 );
168   l--;
169   assert( lbuf[l]=='\n' );
170   lbuf[l]= 0;
171
172   sysassert(! setenv("YPPSC_DATA_TIMESTAMP",lbuf,1) );
173   fclose(tf);
174 }
175
176 static void set_server(const char *envname, const char *defprotocol,
177                        const char *defvalue, const char *defvalue_test,
178                        const char *userspecified,
179                        int enable) {
180   const char *value;
181   
182   if (!enable) { value= "0"; goto ok; }
183
184   if (userspecified)
185     value= userspecified;
186   else if ((value= getenv(envname)))
187     ;
188   else if (o_flags & ff_testservers)
189     value= defvalue_test;
190   else
191     value= defvalue;
192
193   if (value[0]=='/' || (value[0]=='.' && value[1]=='/'))
194     /* absolute or relative pathname - or anyway, something with no hostname */
195     goto ok;
196
197   const char *colon= strchr(value, ':');
198   const char *slash= strchr(value, '/');
199
200   if (colon && (!slash || colon < slash))
201     /* colon before the first slash, if any */
202     /* rsync :: protocol specification - anyway, adding scheme:// won't help */
203     goto ok;
204
205   int vallen= strlen(value);
206
207   value= masprintf("%s%s%s", defprotocol, value,
208                    vallen && value[vallen-1]=='/' ? "" : "/");
209
210  ok:
211   sysassert(! setenv(envname,value,1) );
212 }
213
214 static void outputmode(enum outmodekind kind, const char *str) {
215   OutputMode *om= o_outmodes;
216   OutputMode *sentinel= o_outmodes + ARRAYSIZE(o_outmodes) - 1;
217   for (;;) {
218     if (om==sentinel) badusage("too many output modes specified");
219     if (!om->kind) break;
220     om++;
221   }
222   om->kind= kind;
223   om->str=  str;
224 }
225
226 static void outputmode_uploads(void) {
227   outputmode(omk_upload_yarrg, "upload-yarrg");
228   outputmode(omk_upload_pctb, "upload-pctb");
229 }
230
231 int main(int argc, char **argv) {
232   const char *arg;
233
234   sysassert( setlocale(LC_MESSAGES,"") );
235   sysassert( setlocale(LC_CTYPE,"en_GB.UTF-8") ||
236              setlocale(LC_CTYPE,"en.UTF-8") );
237
238 #define ARGVAL  ((*++argv) ? *argv : \
239                  (badusage("missing value for option %s",arg),(char*)0))
240
241 #define IS(s) (!strcmp(arg,(s)))
242
243   while ((arg=*++argv)) {
244     if (IS("--find-window-only"))      o_mode= mode_findwindow;
245     else if (IS("--screenshot-only"))  o_mode= mode_screenshot;
246     else if (IS("--show-charset"))     o_mode= mode_showcharset;
247     else if (IS("--analyse-only") ||
248              IS("--same"))             o_mode= mode_analyse;
249     else if (IS("--everything"))       o_mode= mode_all;
250     else if (IS("--find-island"))      o_flags |= ffs_printisland;
251     else if (IS("--single-page"))      o_flags |= ff_singlepage;
252     else if (IS("--quiet"))            o_quiet= 1;
253     else if (IS("--edit-charset"))     o_flags |= ff_charset_edit;
254     else if (IS("--no-edit-charset"))  o_flags &= ~(ffm_charset);
255     else if (IS("--test-servers"))     o_flags |= ff_testservers;
256     else if (IS("--dict-local-only"))  o_flags &= ~ffs_dict;
257     else if (IS("--dict-read-only"))   o_flags &= (~ffs_dict | ff_dict_fetch);
258     else if (IS("--dict-anon"))        o_flags &= ~ff_dict_pirate;
259     else if (IS("--dict-submit"))      o_flags |= ff_dict_fetch|ff_dict_submit;
260     else if (IS("--dict-no-update"))   o_flags &= ~ff_dict_fetch; // testing
261     else if (IS("--raw-tsv"))          outputmode(omk_raw,0);
262     else if (IS("--upload"))           outputmode_uploads();
263     else if (IS("--upload-yarrg"))     outputmode(omk_upload_yarrg,arg+2);
264     else if (IS("--upload-pctb"))      outputmode(omk_upload_pctb,arg+2);
265     else if (IS("--arbitrage") ||
266              IS("--tsv") ||
267              IS("--best-prices"))      outputmode(omk_str,arg+2);
268     else if (IS("--screenshot-file")||
269              IS("--screenshots-file")) o_screenshot_fn= ARGVAL;
270     else if (IS("--yarrg-server"))        o_serv_yarrg=       ARGVAL;
271     else if (IS("--pctb-server"))         o_serv_pctb=        ARGVAL;
272     else if (IS("--dict-submit-server"))  o_serv_dict_submit= ARGVAL;
273     else if (IS("--dict-update-server"))  o_serv_dict_fetch=  ARGVAL;
274     else if (IS("--ocean"))            o_ocean=  ARGVAL;
275     else if (IS("--pirate"))           o_pirate= ARGVAL;
276 #define DF(f)                                   \
277     else if (IS("-D" #f))                       \
278       debug_flags |= dbg_##f;
279     DEBUG_FLAG_LIST
280 #undef DF
281     else if (IS("--window-id")) {
282       char *ep;
283       unsigned long windowid= strtoul(ARGVAL,&ep,0);
284       if (*ep) badusage("invalid window id");
285       set_yppclient_window(windowid);
286     } else
287       badusage("unknown option `%s'",arg);
288   }
289
290   /* Consequential changes to options */
291
292   if (o_mode & mf_analyse) {
293     if (!o_outmodes[0].kind) {
294       if (o_flags & ff_printisland) {
295         o_flags |= ff_singlepage;
296       } else {
297         outputmode_uploads();
298       }
299     }
300   } else {
301     if (o_outmodes[0].kind)
302       badusage("overall mode does not include analysis but output option(s)"
303                " (eg `--%s') specified",  o_outmodes[0].str);
304   }
305
306   OutputMode *om;
307   for (om=o_outmodes; om->kind; om++) {
308     switch (om->kind) {
309     case omk_upload_yarrg: o_flags |= ffs_upload | ff_use_yarrg; break;
310     case omk_upload_pctb:  o_flags |= ffs_upload | ff_use_pctb;  break;
311     default: ;
312     }
313   }
314
315   if ((o_flags & (ff_needisland|ff_upload)) &&
316       !(o_flags & (ffm_use)))
317     o_flags |= ffm_use; /* all */
318
319   if (o_serv_yarrg && !o_serv_dict_submit)
320     o_serv_dict_submit= o_serv_yarrg;
321   
322   /* Defaults */
323
324   set_server("YPPSC_YARRG_YARRG",
325              "http://",          "upload.yarrg.chiark.net",
326                                  "upload.yarrg.chiark.net/test",
327              o_serv_yarrg,       o_flags & ff_use_yarrg);
328
329   set_server("YPPSC_YARRG_PCTB",
330              "http://",          "pctb.crabdance.com",
331                                  "pctb.ilk.org",
332              o_serv_pctb,        o_flags & ff_use_pctb);
333              
334   set_server("YPPSC_YARRG_DICT_UPDATE",
335              "rsync://",         "rsync.yarrg.chiark.net/yarrg",
336                                  "rsync.yarrg.chiark.net/yarrg/test",
337              o_serv_dict_fetch,   o_flags & ff_dict_fetch);
338
339   set_server("YPPSC_YARRG_DICT_SUBMIT",
340              "http://",           "upload.yarrg.chiark.net",
341                                   "upload.yarrg.chiark.net/test",
342              o_serv_dict_submit,  o_flags & ff_dict_submit);
343
344   if (!o_screenshot_fn)
345     o_screenshot_fn= masprintf("%s/_pages.ppm.gz", get_vardir());
346
347   /* Actually do the work */
348
349   if ((o_flags & ff_upload) && (o_flags & ff_use_yarrg))
350     get_timestamp();
351              
352   canon_colour_prepare();
353   
354   if (o_mode & mf_findwindow) {
355     screenshot_startup();
356     find_yppclient_window();
357   }
358   if (!ocean)  ocean=  o_ocean;
359   if (!pirate) pirate= o_pirate;
360   
361   if (o_flags & ff_needisland)
362     if (!ocean)
363       badusage("need --ocean option when not using actual YPP client window"
364                " (consider supplying --pirate too)");
365   if (ocean)
366     sysassert(! setenv("YPPSC_OCEAN",ocean,1) );
367   if (pirate && (o_flags & ff_dict_pirate))
368     sysassert(! setenv("YPPSC_PIRATE",pirate,1) );
369
370   switch (o_mode & mfm_special) {
371   case 0:                                      break;
372   case mode_showcharset:  ocr_showcharsets();  exit(0);
373   default:                                     abort();
374   }
375
376   if (o_mode & mf_screenshot) {
377     open_screenshot_file(1);
378     if (o_flags & ff_singlepage) take_one_screenshot();
379     else take_screenshots();
380     progress_log("OK for you to move the mouse now, and you can"
381                  " use the YPP client again.");
382     progress("Finishing handling screenshots...");
383     gzclose(&screenshot_file,&screenshot_compressor,"screenshots output");
384   }
385   if (o_mode & mf_readscreenshot) {
386     if ((o_flags & ff_upload) && !(o_flags & ff_testservers))
387       badusage("must not reuse screenshots for upload to live databases");
388     open_screenshot_file(0);
389     if (o_flags & ff_singlepage) read_one_screenshot();
390     else read_screenshots();
391     gzclose(&screenshot_file,&screenshot_compressor,"screenshots input");
392   }
393   if (o_mode & mf_analyse) {
394     if (o_flags & ff_needisland) {
395       find_islandname();
396       if (o_flags & ff_printisland)
397         printf("%s, %s\n", archipelago, island);
398       sysassert(! setenv("YPPSC_ISLAND",island,1) );
399     }
400     if (o_outmodes[0].kind==omk_raw && o_outmodes[1].kind==omk_unset)
401       analyse(stdout);
402     else
403       run_analysis();
404   }
405   progress_log("Finished.");
406   return 0;
407 }
408
409
410 FILE *dbfile;
411 static const char *basepath; /* as passed in by caller */
412 static pid_t dbzcat;
413
414 int dbfile_gzopen(const char *basepath_spec) {
415   assert(!dbfile);
416
417   basepath= basepath_spec;
418
419   char *zpath= masprintf("%s.gz", basepath);
420   int e= gzopen(zpath, O_RDONLY, &dbfile, &dbzcat, 0);
421   free(zpath);
422   if (e) { errno=e; sysassert(errno==ENOENT); return 0; }
423   
424   return 1;
425 }  
426
427 int dbfile_open(const char *tpath) {
428   assert(!dbfile);
429
430   basepath= tpath;
431
432   dbzcat= -1;
433   dbfile= fopen(tpath,"r");
434   if (!dbfile) { sysassert(errno==ENOENT); return 0; }
435   return 1;
436 }  
437
438 void dbfile_close(void) {
439   gzclose(&dbfile, &dbzcat, basepath);
440 }
441
442 #define dbassertgl(x) ((x) ? (void)0 : dbfile_assertfail(file,line,#x))
443
444 void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line) {
445   errno=0;
446   char *s= fgets(lbuf,lbufsz,dbfile);
447   sysassert(!ferror(dbfile));
448   dbassertgl(!feof(dbfile));
449   assert(s);
450   int l= strlen(lbuf);
451   dbassertgl(l>0);  dbassertgl(lbuf[--l]=='\n');
452   lbuf[l]= 0;
453 }
454
455 int dbfile_vscanf(const char *fmt, va_list al) {
456   int r= vfscanf(dbfile,fmt,al);
457   sysassert(!ferror(dbfile));
458   return r;
459 }
460
461 int dbfile_scanf(const char *fmt, ...) {
462   va_list al;
463   va_start(al,fmt);
464   int r= dbfile_vscanf(fmt,al);
465   va_end(al);
466   return r;
467 }
468
469 void dbfile_assertfail(const char *file, int line, const char *m) {
470   if (dbzcat)
471     fatal("Error in dictionary file %s.gz:\n"
472           " Requirement not met at %s:%d:\n"
473           " %s",
474           basepath, file,line, m);
475   else if (dbfile)
476     fatal("Error in dictionary file %s at byte %ld:\n"
477           " Requirement not met at %s:%d:\n"
478           " %s",
479           basepath,(long)ftell(dbfile), file,line, m);
480   else
481     fatal("Semantic error in dictionaries:\n"
482           " Requirement not met at %s:%d:\n"
483           " %s",
484           file,line, m);
485 }
486
487 int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r,
488            const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */) {
489
490   int zfd= open(zpath, oflags, 0666);
491   if (zfd<0) return errno;
492
493   int pipefds[2];
494   sysassert(! pipe(pipefds) );
495
496   int oi,io; const char *cmd; const char *stdiomode;
497   switch ((oflags & O_ACCMODE)) {
498   case O_RDONLY: oi=0; io=1; cmd="gunzip"; stdiomode="r"; break;
499   case O_WRONLY: oi=1; io=0; cmd="gzip";   stdiomode="w"; break;
500   default: abort();
501   }
502
503   sysassert( (*pid_r=fork()) != -1 );
504   if (!*pid_r) {
505     sysassert( dup2(zfd,oi)==oi );
506     sysassert( dup2(pipefds[io],io)==io );
507     sysassert(! close(zfd) );
508     sysassert(! close(pipefds[0]) );
509     sysassert(! close(pipefds[1]) );
510     execlp(cmd,cmd,gziplevel,(char*)0);
511     sysassert(!"execlp gzip/gunzip");
512   }
513   sysassert(! close(zfd) );
514   sysassert(! close(pipefds[io]) );
515   sysassert( *f_r= fdopen(pipefds[oi], stdiomode) );
516
517   return 0;
518 }
519
520 void gzclose(FILE **f, pid_t *p, const char *what) {
521   if (!*f) return;
522   
523   sysassert(!ferror(*f));
524   sysassert(!fclose(*f));
525
526   if (*p != -1) {
527     char *process= masprintf("%s (de)compressor",what);
528     waitpid_check_exitstatus(*p,process,1);
529     free(process);
530     *p= -1;
531   }
532
533   *f= 0;
534 }