]> git.donarmstrong.com Git - lilypond.git/blob - lily/main.cc
Merge branch 'lilypond/translation' of ssh://jomand@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / lily / main.cc
1 /*
2   main.cc -- implement main () entrypoint.
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2008 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "main.hh"
10
11 #include <cassert>
12 #include <clocale>
13 #include <cstring>
14 #include <cerrno>
15 using namespace std;
16
17 #include <unistd.h>
18 #include <sys/types.h>
19 #include "config.hh"
20
21 #if HAVE_GRP_H
22 #include <grp.h>
23 #endif
24 #if HAVE_PWD_H
25 #include <pwd.h>
26 #endif
27 #if HAVE_GETTEXT
28 #include <libintl.h>
29 #endif
30
31 #include "all-font-metrics.hh"
32 #include "file-name.hh"
33 #include "freetype.hh"
34 #include "getopt-long.hh"
35 #include "global-ctor.hh"
36 #include "international.hh"
37 #include "lily-version.hh"
38 #include "misc.hh"
39 #include "output-def.hh"
40 #include "program-option.hh"
41 #include "relocate.hh"
42 #include "string-convert.hh"
43 #include "version.hh"
44 #include "warn.hh"
45
46 /*
47  * Global options that can be overridden through command line.
48  */
49
50 /* Names of header fields to be dumped to a separate file. */
51 vector<string> dump_header_fieldnames_global;
52
53 /* Name of initialisation file. */
54 string init_name_global;
55
56
57 /* Output formats to generate.  */
58 string output_format_global = "";
59
60 /* Current output name. */
61 string output_name_global;
62
63 /* Run in safe mode? */
64 bool be_safe_global = false;
65
66 /* Provide URI links to the original file */
67 bool point_and_click_global = true;
68
69 /* Verbose progress indication? */
70 bool be_verbose_global = false;
71
72 /* Scheme code to execute before parsing, after .scm init.
73    This is where -e arguments are appended to.  */
74 string init_scheme_code_global;
75 string init_scheme_variables_global;
76
77 bool relocate_binary = true;
78
79
80 /*
81  * Miscellaneous global stuff.
82  */
83 File_path global_path;
84
85 /*
86  * File globals.
87  */
88
89 static char const *AUTHORS
90 = "  Han-Wen Nienhuys <hanwen@xs4all.nl>\n"
91   "  Jan Nieuwenhuizen <janneke@gnu.org>\n";
92
93 static char const *PROGRAM_NAME = "lilypond";
94 static char const *PROGRAM_URL = "http://lilypond.org";
95
96 static char const *NOTICE
97 = _i ("This program is free software.  It is covered by the GNU General Public\n"
98       "License and you are welcome to change it and/or distribute copies of it\n"
99       "under certain conditions.  Invoke as `%s --warranty' for more\n"
100       "information.\n");
101
102 static char const *WARRANTY
103 = _i ("    This program is free software; you can redistribute it and/or\n"
104       "modify it under the terms of the GNU General Public License version 2\n"
105       "as published by the Free Software Foundation.\n"
106       "\n"
107       "    This program is distributed in the hope that it will be useful,\n"
108       "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
109       "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
110       "General Public License for more details.\n"
111       "\n"
112       "    You should have received a copy of the\n"
113       "GNU General Public License along with this program; if not, write to\n"
114       "the Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n"
115       "Boston, MA 02111-1307, USA.\n");
116
117 /* Where the init files live.  Typically:
118    LILYPOND_DATADIR = /usr/share/lilypond
119 */
120 string lilypond_datadir;
121
122 /* The jail specification: USER, GROUP, JAIL, DIR. */
123 string jail_spec;
124
125 /*  The option parser */
126 static Getopt_long *option_parser = 0;
127
128 /* Internationalisation kludge in two steps:
129  * use _i () to get entry in POT file
130  * call gettext () explicitely for actual "translation"  */
131
132 static Long_option_init options_static[]
133 = {
134   {_i ("SYM[=VAL]"), "define-default", 'd',
135    _i ("set Scheme option SYM to VAL (default: #t).\n"
136        "Use -dhelp for help.")},
137
138   {_i ("EXPR"), "evaluate", 'e', _i ("evaluate scheme code")},
139   /* Bug in option parser: --output =foe is taken as an abbreviation
140      for --output-format.  */
141   {_i ("FORMATs"), "formats", 'f', _i ("dump FORMAT,...  Also as separate options:")},
142   {0, "pdf", 0, _i ("generate PDF (default)")},
143   {0, "png", 0, _i ("generate PNG")},
144   {0, "ps", 0, _i ("generate PostScript")},
145   {0, "help", 'h',  _i ("show this help and exit")},
146   {_i ("FIELD"), "header", 'H',  _i ("dump header field FIELD to file\n"
147                                      "named BASENAME.FIELD")},
148   {_i ("DIR"), "include", 'I',  _i ("add DIR to search path")},
149   {_i ("FILE"), "init", 'i',  _i ("use FILE as init file")},
150 #if HAVE_CHROOT
151   {_i ("USER, GROUP, JAIL, DIR"), "jail", 'j', _i ("chroot to JAIL, become USER:GROUP\n"
152                                                 "and cd into DIR")},
153 #endif
154   {_i ("FILE"), "output", 'o',  _i ("write output to FILE (suffix will be added)")},
155   {0, "relocate", 0, _i ("relocate using directory of lilypond program")},
156   {0, "version", 'v',  _i ("show version number and exit")},
157   {0, "verbose", 'V', _i ("be verbose")},
158   {0, "warranty", 'w',  _i ("show warranty and copyright")},
159   {0, 0, 0, 0}
160 };
161
162 char const *LILYPOND_DATADIR = PACKAGE_DATADIR "/" TOPLEVEL_VERSION;
163
164
165 /* x86 defaults to using 80-bit extended precision arithmetic. This can cause
166    problems because the truncation from 80 bits to 64 bits can occur in
167    unpredictable places. To get around this, we tell the x87 FPU to use only
168    double precision. Note that this is not needed for x86_64 because that uses
169    the SSE unit by default instead of the x87 FPU. */
170 #if ((defined(__x86__) || defined(__i386__)) \
171   && defined(HAVE_FPU_CONTROL_H) && (HAVE_FPU_CONTROL_H == 1))
172
173 #include <fpu_control.h>
174 static void configure_fpu() {
175   fpu_control_t fpu_control = 0x027f;
176   _FPU_SETCW (fpu_control);
177 }
178
179 #else
180
181 static void configure_fpu() {
182 }
183
184 #endif /* defined(__x86__) || defined(__i386__) */
185
186
187 static void
188 env_var_info (FILE *out, char const *key)
189 {
190   if (char const *value = getenv (key))
191     fprintf (out, "%s=\"%s\"\n", key, value);
192 }
193
194 static void
195 dir_info (FILE *out)
196 {
197   fputs ("\n", out);
198   fprintf (out, "LILYPOND_DATADIR=\"%s\"\n", LILYPOND_DATADIR);
199   env_var_info (out, "LILYPONDPREFIX");
200   env_var_info (out, "LILYPOND_DATADIR");
201   fprintf (out, "LOCALEDIR=\"%s\"\n", LOCALEDIR);
202
203   fprintf (out, "\nEffective prefix: \"%s\"\n", lilypond_datadir.c_str ());
204
205   if (relocate_binary)
206     {
207       env_var_info (out, "FONTCONFIG_FILE");
208       env_var_info (out, "FONTCONFIG_PATH");
209       env_var_info (out, "GS_FONTPATH");
210       env_var_info (out, "GS_LIB");
211       env_var_info (out, "GUILE_LOAD_PATH");
212       env_var_info (out, "PANGO_RC_FILE");
213       env_var_info (out, "PANGO_PREFIX");
214       env_var_info (out, "PATH");
215     }
216 }
217
218 static void
219 copyright ()
220 {
221   printf (_f ("Copyright (c) %s by\n%s  and others.",
222               "1996--2008",
223               AUTHORS).c_str ());
224   printf ("\n");
225 }
226
227 static void
228 identify (FILE *out)
229 {
230   fputs (gnu_lilypond_version_string ().c_str (), out);
231   fputs ("\n", out);
232 }
233
234 static void
235 notice ()
236 {
237   identify (stdout);
238   printf ("\n");
239   copyright ();
240   printf ("\n");
241   puts (_f (NOTICE, PROGRAM_NAME).c_str ());
242 }
243
244 LY_DEFINE (ly_usage, "ly:usage",
245            0, 0, 0, (),
246            "Print usage message.")
247 {
248   /* No version number or newline here.  It confuses help2man.  */
249   printf (_f ("Usage: %s [OPTION]... FILE...", PROGRAM_NAME).c_str ());
250   printf ("\n\n");
251   printf (_ ("Typeset music and/or produce MIDI from FILE.").c_str ());
252   printf ("\n\n");
253   printf (_ ("LilyPond produces beautiful music notation.").c_str ());
254   printf ("\n");
255   printf (_f ("For more information, see %s", PROGRAM_URL).c_str ());
256   printf ("\n\n");
257   printf (_ ("Options:").c_str ());
258   printf ("\n");
259   printf (Long_option_init::table_string (options_static).c_str ());
260   printf ("\n");
261   printf (_f ("Report bugs via %s",
262               "http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs"
263               ).c_str ());
264   printf ("\n");
265   printf ("\n");
266   return SCM_UNSPECIFIED;
267 }
268
269 static void
270 warranty ()
271 {
272   identify (stdout);
273   printf ("\n");
274   copyright ();
275   printf ("\n");
276   printf (_ (WARRANTY).c_str ());
277 }
278
279 static void
280 prepend_load_path (string dir)
281 {
282   string s = "(set! %load-path (cons \"" + dir + "\" %load-path))";
283   scm_c_eval_string (s.c_str ());
284 }
285
286 void init_global_tweak_registry ();
287 void init_fontconfig ();
288
289 #if HAVE_CHROOT
290 static void
291 do_chroot_jail ()
292 {
293   /* Now we chroot, setuid/setgrp and chdir.  If something goes wrong,
294      we exit (this is a security-sensitive area).  First we split
295      jail_spec into its components, then we retrieve the user/group id
296      (necessarily *before* chroot'ing) and finally we perform the
297      actual actions.  */
298
299   enum Jail
300     {
301       USER_NAME, GROUP_NAME, JAIL, DIR, JAIL_MAX
302     };
303
304   vector<string> components = string_split (jail_spec, ',');
305   if (components.size () != JAIL_MAX)
306     {
307       error (_f ("expected %d arguments with jail, found: %u", JAIL_MAX,
308                  (unsigned) components.size ()));
309       exit (2);
310     }
311
312   /* Hmm.  */
313   errno = 0;
314
315   int uid;
316   if (passwd * passwd = getpwnam (components[USER_NAME].c_str ()))
317     uid = passwd->pw_uid;
318   else
319     {
320       if (errno == 0)
321         error (_f ("no such user: %s", components[USER_NAME]));
322       else
323         error (_f ("cannot get user id from user name: %s: %s",
324                    components[USER_NAME],
325                    strerror (errno)));
326       exit (3);
327     }
328
329   /* Hmm.  */
330   errno = 0;
331
332   int gid;
333   if (group * group = getgrnam (components[GROUP_NAME].c_str ()))
334     gid = group->gr_gid;
335   else
336     {
337       if (errno == 0)
338         error (_f ("no such group: %s", components[GROUP_NAME]));
339       else
340         error (_f ("cannot get group id from group name: %s: %s",
341                    components[GROUP_NAME],
342                    strerror (errno)));
343       exit (3);
344     }
345
346   if (chroot (components[JAIL].c_str ()))
347     {
348       error (_f ("cannot chroot to: %s: %s", components[JAIL],
349                  strerror (errno)));
350       exit (3);
351     }
352
353   if (setgid (gid))
354     {
355       error (_f ("cannot change group id to: %d: %s", gid, strerror (errno)));
356       exit (3);
357     }
358
359   if (setuid (uid))
360     {
361       error (_f ("cannot change user id to: %d: %s", uid, strerror (errno)));
362       exit (3);
363     }
364
365   if (chdir (components[DIR].c_str ()))
366     {
367       error (_f ("cannot change working directory to: %s: %s", components[DIR],
368                  strerror (errno)));
369       exit (3);
370     }
371 }
372 #endif
373
374 static void
375 main_with_guile (void *, int, char **)
376 {
377   /* Engravers use lily.scm contents, need to make Guile find it.
378      Prepend onto GUILE %load-path, very ugh. */
379
380   prepend_load_path (lilypond_datadir);
381   prepend_load_path (lilypond_datadir + "/scm");
382
383   if (be_verbose_global)
384     dir_info (stderr);
385
386   init_scheme_variables_global = "(list " + init_scheme_variables_global + ")";
387   init_scheme_code_global = "(begin " + init_scheme_code_global + ")";
388
389   ly_c_init_guile ();
390   call_constructors ();
391   init_fontconfig ();
392
393   init_freetype ();
394   ly_reset_all_fonts ();
395
396   /* We accept multiple independent music files on the command line to
397      reduce compile time when processing lots of small files.
398      Starting the GUILE engine is very time consuming.  */
399
400   SCM files = SCM_EOL;
401   SCM *tail = &files;
402   while (char const *arg = option_parser->get_next_arg ())
403     {
404       *tail = scm_cons (scm_from_locale_string (arg), SCM_EOL);
405       tail = SCM_CDRLOC (*tail);
406     }
407   
408   delete option_parser;
409   option_parser = 0;
410
411 #if HAVE_CHROOT
412   if (!jail_spec.empty ())
413     do_chroot_jail ();
414 #endif
415
416   SCM result = scm_call_1 (ly_lily_module_constant ("lilypond-main"), files);
417   (void) result;
418
419   /* Unreachable.  */
420   exit (0);
421 }
422
423 static void
424 setup_localisation ()
425 {
426 #if HAVE_GETTEXT
427   /* Enable locales */
428   setlocale (LC_ALL, "");
429
430   /* FIXME: check if this is still true.
431      Disable localisation of float values. */
432   setlocale (LC_NUMERIC, "C");
433
434   string localedir = LOCALEDIR;
435   if (char const *env = getenv ("LILYPOND_LOCALEDIR"))
436     localedir = env;
437
438   bindtextdomain ("lilypond", localedir.c_str ());
439   textdomain ("lilypond");
440 #endif
441 }
442
443 static void
444 add_output_format (string format)
445 {
446   if (output_format_global != "")
447     output_format_global += ",";
448   output_format_global += format;
449 }
450
451 static void
452 parse_argv (int argc, char **argv)
453 {
454   bool show_help = false;
455   option_parser = new Getopt_long (argc, argv, options_static);
456   while (Long_option_init const *opt = (*option_parser) ())
457     {
458       switch (opt->shortname_char_)
459         {
460         case 0:
461           if (string (opt->longname_str0_) == "pdf"
462               || string (opt->longname_str0_) == "png"
463               || string (opt->longname_str0_) == "ps")
464             add_output_format (opt->longname_str0_);
465           else if (string (opt->longname_str0_) == "relocate")
466             relocate_binary = true;
467           break;
468
469         case 'd':
470           {
471             string arg (option_parser->optional_argument_str0_);
472             ssize eq = arg.find ('=');
473
474             string key = arg;
475             string val = "#t";
476
477             if (eq != NPOS)
478               {
479                 key = arg.substr (0, eq);
480                 val = arg.substr (eq + 1, arg.length () - 1);
481               }
482
483             init_scheme_variables_global
484               += "(cons \'" + key + " '" + val + ")\n";
485           }
486           break;
487
488         case 'v':
489           notice ();
490           exit (0);
491           break;
492         case 'o':
493           {
494             string s = option_parser->optional_argument_str0_;
495             File_name file_name (s);
496             output_name_global = file_name.to_string ();
497           }
498           break;
499         case 'j':
500           jail_spec = option_parser->optional_argument_str0_;
501           break;
502           
503         case 'e':
504           init_scheme_code_global += option_parser->optional_argument_str0_ + string (" ");
505           break;
506         case 'w':
507           warranty ();
508           exit (0);
509           break;
510
511         case 'f':
512           {
513             vector<string> components
514               = string_split (option_parser->optional_argument_str0_, ',');
515             for (vsize i = 0; i < components.size (); i++)
516               add_output_format (components[i]);
517           }
518           break;
519
520         case 'H':
521           dump_header_fieldnames_global
522             .push_back (option_parser->optional_argument_str0_);
523           break;
524         case 'I':
525           global_path.append (option_parser->optional_argument_str0_);
526           break;
527         case 'i':
528           init_name_global = option_parser->optional_argument_str0_;
529           break;
530         case 'h':
531           show_help = true;
532           break;
533         case 'V':
534           be_verbose_global = true;
535           break;
536         default:
537           programming_error (to_string ("unhandled short option: %c",
538                                         opt->shortname_char_));
539           assert (false);
540           break;
541         }
542     }
543
544   if (output_format_global == "")
545     output_format_global = "pdf";
546
547   if (show_help)
548     {
549       ly_usage ();
550       if (be_verbose_global)
551         dir_info (stdout);
552       exit (0);
553     }
554 }
555
556 void
557 setup_guile_env ()
558 {
559   const char *yield = getenv ("LILYPOND_GC_YIELD");
560   bool overwrite = true;
561   if (!yield)
562     {
563       yield = "65";
564       overwrite = false;
565     }
566
567   sane_putenv ("GUILE_MIN_YIELD_1", yield, overwrite);
568   sane_putenv ("GUILE_MIN_YIELD_2", yield, overwrite);
569   sane_putenv ("GUILE_MIN_YIELD_MALLOC", yield, overwrite);
570
571
572   sane_putenv ("GUILE_INIT_SEGMENT_SIZE_1",
573                "10485760", overwrite);
574   sane_putenv ("GUILE_MAX_SEGMENT_SIZE",
575                "104857600", overwrite);
576 }
577
578 vector<string> start_environment_global;
579  
580 int
581 main (int argc, char **argv, char **envp)
582 {
583   configure_fpu();
584
585   for (char **p = envp; *p; p++)
586     start_environment_global.push_back(*p);
587   
588   if (getenv ("LILYPOND_VERBOSE"))
589     be_verbose_global = true;
590
591   setup_localisation ();
592   parse_argv (argc, argv);
593   if (isatty (STDIN_FILENO))
594     identify (stderr);
595
596   setup_paths (argv[0]);
597   setup_guile_env ();
598
599 #if 0
600   /* Debugging aid.  */
601   try
602     {
603       scm_boot_guile (argc, argv, main_with_guile, 0);
604     }
605   catch (exception e)
606     {
607       error (_f ("exception caught: %s", e.what ()));
608     };
609 #else
610   scm_boot_guile (argc, argv, main_with_guile, 0);
611 #endif
612         
613   /* Only reachable if GUILE exits.  That is an error.  */
614   return 1;
615 }