1/* Writing C# satellite assemblies.
2   Copyright (C) 2003-2005 Free Software Foundation, Inc.
3   Written by Bruno Haible <bruno@clisp.org>, 2003.
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2, or (at your option)
8   any later version.
9
10   This program 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
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software Foundation,
17   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22#include <alloca.h>
23
24/* Specification.  */
25#include "write-csharp.h"
26
27#include <errno.h>
28#include <stdbool.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <string.h>
32
33#include <sys/stat.h>
34#if STAT_MACROS_BROKEN
35# undef S_ISDIR
36#endif
37#if !defined S_ISDIR && defined S_IFDIR
38# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
39#endif
40#if !S_IRUSR && S_IREAD
41# define S_IRUSR S_IREAD
42#endif
43#if !S_IRUSR
44# define S_IRUSR 00400
45#endif
46#if !S_IWUSR && S_IWRITE
47# define S_IWUSR S_IWRITE
48#endif
49#if !S_IWUSR
50# define S_IWUSR 00200
51#endif
52#if !S_IXUSR && S_IEXEC
53# define S_IXUSR S_IEXEC
54#endif
55#if !S_IXUSR
56# define S_IXUSR 00100
57#endif
58#if !S_IRGRP
59# define S_IRGRP (S_IRUSR >> 3)
60#endif
61#if !S_IWGRP
62# define S_IWGRP (S_IWUSR >> 3)
63#endif
64#if !S_IXGRP
65# define S_IXGRP (S_IXUSR >> 3)
66#endif
67#if !S_IROTH
68# define S_IROTH (S_IRUSR >> 6)
69#endif
70#if !S_IWOTH
71# define S_IWOTH (S_IWUSR >> 6)
72#endif
73#if !S_IXOTH
74# define S_IXOTH (S_IXUSR >> 6)
75#endif
76
77#if HAVE_UNISTD_H
78# include <unistd.h>
79#endif
80
81#ifdef __MINGW32__
82/* mingw's mkdir() function has 1 argument, but we pass 2 arguments.
83   Therefore we have to disable the argument count checking.  */
84# define mkdir ((int (*)()) mkdir)
85#endif
86
87#include "c-ctype.h"
88#include "error.h"
89#include "relocatable.h"
90#include "csharpcomp.h"
91#include "message.h"
92#include "mkdtemp.h"
93#include "msgfmt.h"
94#include "msgl-iconv.h"
95#include "pathmax.h"
96#include "plural-exp.h"
97#include "po-charset.h"
98#include "xalloc.h"
99#include "xallocsa.h"
100#include "pathname.h"
101#include "fatal-signal.h"
102#include "fwriteerror.h"
103#include "tmpdir.h"
104#include "utf8-ucs4.h"
105#include "gettext.h"
106
107#define _(str) gettext (str)
108
109
110/* Convert a resource name to a class name.
111   Return a nonempty string consisting of alphanumerics and underscores
112   and starting with a letter or underscore.  */
113static char *
114construct_class_name (const char *resource_name)
115{
116  /* This code must be kept consistent with intl.cs, function
117     GettextResourceManager.ConstructClassName.  */
118  /* We could just return an arbitrary fixed class name, like "Messages",
119     assuming that every assembly will only ever contain one
120     GettextResourceSet subclass, but this assumption would break the day
121     we want to support multi-domain PO files in the same format...  */
122  bool valid;
123  const char *p;
124
125  /* Test for a valid ASCII identifier:
126     - nonempty,
127     - first character is A..Za..z_ - see x-csharp.c:is_identifier_start.
128     - next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part.
129   */
130  valid = (resource_name[0] != '\0');
131  for (p = resource_name; valid && *p != '\0'; p++)
132    {
133      char c = *p;
134      if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
135	    || (p > resource_name && c >= '0' && c <= '9')))
136	valid = false;
137    }
138  if (valid)
139    return xstrdup (resource_name);
140  else
141    {
142      static const char hexdigit[] = "0123456789abcdef";
143      const char *str = resource_name;
144      const char *str_limit = str + strlen (str);
145      char *class_name = (char *) xmalloc (12 + 6 * (str_limit - str) + 1);
146      char *b;
147
148      b = class_name;
149      memcpy (b, "__UESCAPED__", 12); b += 12;
150      while (str < str_limit)
151	{
152	  unsigned int uc;
153	  str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
154	  if (uc >= 0x10000)
155	    {
156	      *b++ = '_';
157	      *b++ = 'U';
158	      *b++ = hexdigit[(uc >> 28) & 0x0f];
159	      *b++ = hexdigit[(uc >> 24) & 0x0f];
160	      *b++ = hexdigit[(uc >> 20) & 0x0f];
161	      *b++ = hexdigit[(uc >> 16) & 0x0f];
162	      *b++ = hexdigit[(uc >> 12) & 0x0f];
163	      *b++ = hexdigit[(uc >> 8) & 0x0f];
164	      *b++ = hexdigit[(uc >> 4) & 0x0f];
165	      *b++ = hexdigit[uc & 0x0f];
166	    }
167	  else if (!((uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z')
168		     || (uc >= '0' && uc <= '9')))
169	    {
170	      *b++ = '_';
171	      *b++ = 'u';
172	      *b++ = hexdigit[(uc >> 12) & 0x0f];
173	      *b++ = hexdigit[(uc >> 8) & 0x0f];
174	      *b++ = hexdigit[(uc >> 4) & 0x0f];
175	      *b++ = hexdigit[uc & 0x0f];
176	    }
177	  else
178	    *b++ = uc;
179	}
180      *b++ = '\0';
181      return (char *) xrealloc (class_name, b - class_name);
182    }
183}
184
185
186/* Write a string in C# Unicode notation to the given stream.  */
187static void
188write_csharp_string (FILE *stream, const char *str)
189{
190  static const char hexdigit[] = "0123456789abcdef";
191  const char *str_limit = str + strlen (str);
192
193  fprintf (stream, "\"");
194  while (str < str_limit)
195    {
196      unsigned int uc;
197      str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
198      if (uc == 0x0000)
199	fprintf (stream, "\\0");
200      else if (uc == 0x0007)
201	fprintf (stream, "\\a");
202      else if (uc == 0x0008)
203	fprintf (stream, "\\b");
204      else if (uc == 0x0009)
205	fprintf (stream, "\\t");
206      else if (uc == 0x000a)
207	fprintf (stream, "\\n");
208      else if (uc == 0x000b)
209	fprintf (stream, "\\v");
210      else if (uc == 0x000c)
211	fprintf (stream, "\\f");
212      else if (uc == 0x000d)
213	fprintf (stream, "\\r");
214      else if (uc == 0x0022)
215	fprintf (stream, "\\\"");
216      else if (uc == 0x005c)
217	fprintf (stream, "\\\\");
218      else if (uc >= 0x0020 && uc < 0x007f)
219	fprintf (stream, "%c", uc);
220      else if (uc < 0x10000)
221	fprintf (stream, "\\u%c%c%c%c",
222		 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
223		 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
224      else
225	fprintf (stream, "\\U%c%c%c%c%c%c%c%c",
226		 hexdigit[(uc >> 28) & 0x0f], hexdigit[(uc >> 24) & 0x0f],
227		 hexdigit[(uc >> 20) & 0x0f], hexdigit[(uc >> 16) & 0x0f],
228		 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
229		 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
230    }
231  fprintf (stream, "\"");
232}
233
234
235/* Write C# code that returns the value for a message.  If the message
236   has plural forms, it is an expression of type System.String[], otherwise it
237   is an expression of type System.String.  */
238static void
239write_csharp_msgstr (FILE *stream, message_ty *mp)
240{
241  if (mp->msgid_plural != NULL)
242    {
243      bool first;
244      const char *p;
245
246      fprintf (stream, "new System.String[] { ");
247      for (p = mp->msgstr, first = true;
248	   p < mp->msgstr + mp->msgstr_len;
249	   p += strlen (p) + 1, first = false)
250	{
251	  if (!first)
252	    fprintf (stream, ", ");
253	  write_csharp_string (stream, p);
254	}
255      fprintf (stream, " }");
256    }
257  else
258    {
259      if (mp->msgstr_len != strlen (mp->msgstr) + 1)
260	abort ();
261
262      write_csharp_string (stream, mp->msgstr);
263    }
264}
265
266
267/* Tests whether a plural expression, evaluated according to the C rules,
268   can only produce the values 0 and 1.  */
269static bool
270is_expression_boolean (struct expression *exp)
271{
272  switch (exp->operation)
273    {
274    case var:
275    case mult:
276    case divide:
277    case module:
278    case plus:
279    case minus:
280      return false;
281    case lnot:
282    case less_than:
283    case greater_than:
284    case less_or_equal:
285    case greater_or_equal:
286    case equal:
287    case not_equal:
288    case land:
289    case lor:
290      return true;
291    case num:
292      return (exp->val.num == 0 || exp->val.num == 1);
293    case qmop:
294      return is_expression_boolean (exp->val.args[1])
295	     && is_expression_boolean (exp->val.args[2]);
296    default:
297      abort ();
298    }
299}
300
301
302/* Write C# code that evaluates a plural expression according to the C rules.
303   The variable is called 'n'.  */
304static void
305write_csharp_expression (FILE *stream, struct expression *exp, bool as_boolean)
306{
307  /* We use parentheses everywhere.  This frees us from tracking the priority
308     of arithmetic operators.  */
309  if (as_boolean)
310    {
311      /* Emit a C# expression of type 'bool'.  */
312      switch (exp->operation)
313	{
314	case num:
315	  fprintf (stream, "%s", exp->val.num ? "true" : "false");
316	  return;
317	case lnot:
318	  fprintf (stream, "(!");
319	  write_csharp_expression (stream, exp->val.args[0], true);
320	  fprintf (stream, ")");
321	  return;
322	case less_than:
323	  fprintf (stream, "(");
324	  write_csharp_expression (stream, exp->val.args[0], false);
325	  fprintf (stream, " < ");
326	  write_csharp_expression (stream, exp->val.args[1], false);
327	  fprintf (stream, ")");
328	  return;
329	case greater_than:
330	  fprintf (stream, "(");
331	  write_csharp_expression (stream, exp->val.args[0], false);
332	  fprintf (stream, " > ");
333	  write_csharp_expression (stream, exp->val.args[1], false);
334	  fprintf (stream, ")");
335	  return;
336	case less_or_equal:
337	  fprintf (stream, "(");
338	  write_csharp_expression (stream, exp->val.args[0], false);
339	  fprintf (stream, " <= ");
340	  write_csharp_expression (stream, exp->val.args[1], false);
341	  fprintf (stream, ")");
342	  return;
343	case greater_or_equal:
344	  fprintf (stream, "(");
345	  write_csharp_expression (stream, exp->val.args[0], false);
346	  fprintf (stream, " >= ");
347	  write_csharp_expression (stream, exp->val.args[1], false);
348	  fprintf (stream, ")");
349	  return;
350	case equal:
351	  fprintf (stream, "(");
352	  write_csharp_expression (stream, exp->val.args[0], false);
353	  fprintf (stream, " == ");
354	  write_csharp_expression (stream, exp->val.args[1], false);
355	  fprintf (stream, ")");
356	  return;
357	case not_equal:
358	  fprintf (stream, "(");
359	  write_csharp_expression (stream, exp->val.args[0], false);
360	  fprintf (stream, " != ");
361	  write_csharp_expression (stream, exp->val.args[1], false);
362	  fprintf (stream, ")");
363	  return;
364	case land:
365	  fprintf (stream, "(");
366	  write_csharp_expression (stream, exp->val.args[0], true);
367	  fprintf (stream, " && ");
368	  write_csharp_expression (stream, exp->val.args[1], true);
369	  fprintf (stream, ")");
370	  return;
371	case lor:
372	  fprintf (stream, "(");
373	  write_csharp_expression (stream, exp->val.args[0], true);
374	  fprintf (stream, " || ");
375	  write_csharp_expression (stream, exp->val.args[1], true);
376	  fprintf (stream, ")");
377	  return;
378	case qmop:
379	  if (is_expression_boolean (exp->val.args[1])
380	      && is_expression_boolean (exp->val.args[2]))
381	    {
382	      fprintf (stream, "(");
383	      write_csharp_expression (stream, exp->val.args[0], true);
384	      fprintf (stream, " ? ");
385	      write_csharp_expression (stream, exp->val.args[1], true);
386	      fprintf (stream, " : ");
387	      write_csharp_expression (stream, exp->val.args[2], true);
388	      fprintf (stream, ")");
389	      return;
390	    }
391	  /*FALLTHROUGH*/
392	case var:
393	case mult:
394	case divide:
395	case module:
396	case plus:
397	case minus:
398	  fprintf (stream, "(");
399	  write_csharp_expression (stream, exp, false);
400	  fprintf (stream, " != 0)");
401	  return;
402	default:
403	  abort ();
404	}
405    }
406  else
407    {
408      /* Emit a C# expression of type 'long'.  */
409      switch (exp->operation)
410	{
411	case var:
412	  fprintf (stream, "n");
413	  return;
414	case num:
415	  fprintf (stream, "%lu", exp->val.num);
416	  return;
417	case mult:
418	  fprintf (stream, "(");
419	  write_csharp_expression (stream, exp->val.args[0], false);
420	  fprintf (stream, " * ");
421	  write_csharp_expression (stream, exp->val.args[1], false);
422	  fprintf (stream, ")");
423	  return;
424	case divide:
425	  fprintf (stream, "(");
426	  write_csharp_expression (stream, exp->val.args[0], false);
427	  fprintf (stream, " / ");
428	  write_csharp_expression (stream, exp->val.args[1], false);
429	  fprintf (stream, ")");
430	  return;
431	case module:
432	  fprintf (stream, "(");
433	  write_csharp_expression (stream, exp->val.args[0], false);
434	  fprintf (stream, " %% ");
435	  write_csharp_expression (stream, exp->val.args[1], false);
436	  fprintf (stream, ")");
437	  return;
438	case plus:
439	  fprintf (stream, "(");
440	  write_csharp_expression (stream, exp->val.args[0], false);
441	  fprintf (stream, " + ");
442	  write_csharp_expression (stream, exp->val.args[1], false);
443	  fprintf (stream, ")");
444	  return;
445	case minus:
446	  fprintf (stream, "(");
447	  write_csharp_expression (stream, exp->val.args[0], false);
448	  fprintf (stream, " - ");
449	  write_csharp_expression (stream, exp->val.args[1], false);
450	  fprintf (stream, ")");
451	  return;
452	case qmop:
453	  fprintf (stream, "(");
454	  write_csharp_expression (stream, exp->val.args[0], true);
455	  fprintf (stream, " ? ");
456	  write_csharp_expression (stream, exp->val.args[1], false);
457	  fprintf (stream, " : ");
458	  write_csharp_expression (stream, exp->val.args[2], false);
459	  fprintf (stream, ")");
460	  return;
461	case lnot:
462	case less_than:
463	case greater_than:
464	case less_or_equal:
465	case greater_or_equal:
466	case equal:
467	case not_equal:
468	case land:
469	case lor:
470	  fprintf (stream, "(");
471	  write_csharp_expression (stream, exp, true);
472	  fprintf (stream, " ? 1 : 0)");
473	  return;
474	default:
475	  abort ();
476	}
477    }
478}
479
480
481/* Write the C# code for the GettextResourceSet subclass to the given stream.
482   Note that we use fully qualified class names and no "using" statements,
483   because applications can have their own classes called X.Y.Hashtable or
484   X.Y.String.  */
485static void
486write_csharp_code (FILE *stream, const char *class_name, message_list_ty *mlp)
487{
488  const char *last_dot;
489  const char *class_name_last_part;
490  unsigned int plurals;
491  size_t j;
492
493  fprintf (stream,
494	   "/* Automatically generated by GNU msgfmt.  Do not modify!  */\n");
495  /* We have to use a "using" statement here, to avoid a bug in the pnet-0.6.0
496     compiler.  */
497  fprintf (stream, "using GNU.Gettext;\n");
498  last_dot = strrchr (class_name, '.');
499  if (last_dot != NULL)
500    {
501      fprintf (stream, "namespace ");
502      fwrite (class_name, 1, last_dot - class_name, stream);
503      fprintf (stream, " {\n");
504      class_name_last_part = last_dot + 1;
505    }
506  else
507    class_name_last_part = class_name;
508  fprintf (stream, "public class %s : GettextResourceSet {\n",
509	   class_name_last_part);
510
511  /* Determine whether there are plural messages.  */
512  plurals = 0;
513  for (j = 0; j < mlp->nitems; j++)
514    if (mlp->item[j]->msgid_plural != NULL)
515      plurals++;
516
517  /* Emit the constructor.  */
518  fprintf (stream, "  public %s ()\n", class_name_last_part);
519  fprintf (stream, "    : base () {\n");
520  fprintf (stream, "  }\n");
521
522  /* Emit the ReadResources method.  */
523  fprintf (stream, "  protected override void ReadResources () {\n");
524  /* In some implementations, the ResourceSet constructor initializes Table
525     before calling ReadResources().  In other implementations, the
526     ReadResources() method is expected to initialize the Table.  */
527  fprintf (stream, "    if (Table == null)\n");
528  fprintf (stream, "      Table = new System.Collections.Hashtable();\n");
529  fprintf (stream, "    System.Collections.Hashtable t = Table;\n");
530  for (j = 0; j < mlp->nitems; j++)
531    {
532      fprintf (stream, "    t.Add(");
533      write_csharp_string (stream, mlp->item[j]->msgid);
534      fprintf (stream, ",");
535      write_csharp_msgstr (stream, mlp->item[j]);
536      fprintf (stream, ");\n");
537    }
538  fprintf (stream, "  }\n");
539
540  /* Emit the msgid_plural strings.  Only used by msgunfmt.  */
541  if (plurals)
542    {
543      fprintf (stream, "  public static System.Collections.Hashtable GetMsgidPluralTable () {\n");
544      fprintf (stream, "    System.Collections.Hashtable t = new System.Collections.Hashtable();\n");
545      for (j = 0; j < mlp->nitems; j++)
546	if (mlp->item[j]->msgid_plural != NULL)
547	  {
548	    fprintf (stream, "    t.Add(");
549	    write_csharp_string (stream, mlp->item[j]->msgid);
550	    fprintf (stream, ",");
551	    write_csharp_string (stream, mlp->item[j]->msgid_plural);
552	    fprintf (stream, ");\n");
553	  }
554      fprintf (stream, "    return t;\n");
555      fprintf (stream, "  }\n");
556    }
557
558  /* Emit the PluralEval function.  It is a subroutine for GetPluralString.  */
559  if (plurals)
560    {
561      message_ty *header_entry;
562      struct expression *plural;
563      unsigned long int nplurals;
564
565      header_entry = message_list_search (mlp, "");
566      extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
567				 &plural, &nplurals);
568
569      fprintf (stream, "  protected override long PluralEval (long n) {\n");
570      fprintf (stream, "    return ");
571      write_csharp_expression (stream, plural, false);
572      fprintf (stream, ";\n");
573      fprintf (stream, "  }\n");
574    }
575
576  /* Terminate the class.  */
577  fprintf (stream, "}\n");
578
579  if (last_dot != NULL)
580    /* Terminate the namespace.  */
581    fprintf (stream, "}\n");
582}
583
584
585/* Asynchronously cleaning up temporary files, when we receive any of the
586   usually occurring signals whose default action is to terminate the
587   program.  */
588
589static struct
590{
591  const char *tmpdir;
592  const char *file_name;
593} cleanup_list;
594
595/* The signal handler.  It gets called asynchronously.  */
596static void
597cleanup ()
598{
599  /* First cleanup the files in the subdirectory.  */
600  {
601    const char *filename = cleanup_list.file_name;
602
603    if (filename != NULL)
604      unlink (filename);
605  }
606
607  /* Then cleanup the main temporary directory.  */
608  {
609    const char *filename = cleanup_list.tmpdir;
610
611    if (filename != NULL)
612      rmdir (filename);
613  }
614}
615
616
617int
618msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding,
619			const char *resource_name, const char *locale_name,
620			const char *directory)
621{
622  int retval;
623  char *template;
624  char *tmpdir;
625  char *culture_name;
626  char *output_file;
627  char *class_name;
628  char *csharp_file_name;
629  FILE *csharp_file;
630  const char *gettextlibdir;
631  const char *csharp_sources[1];
632  const char *libdirs[1];
633  const char *libraries[1];
634
635  /* If no entry for this resource/domain, don't even create the file.  */
636  if (mlp->nitems == 0)
637    return 0;
638
639  retval = 1;
640
641  /* Convert the messages to Unicode.  */
642  iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
643
644  cleanup_list.tmpdir = NULL;
645  cleanup_list.file_name = NULL;
646  {
647    static bool cleanup_already_registered = false;
648    if (!cleanup_already_registered)
649      {
650	at_fatal_signal (&cleanup);
651	cleanup_already_registered = true;
652      }
653  }
654
655  /* Create a temporary directory where we can put the C# file.
656     A simple temporary file would also be possible but would require us to
657     define our own variant of mkstemp(): On one hand the functions mktemp(),
658     tmpnam(), tempnam() present a security risk, and on the other hand the
659     function mkstemp() doesn't allow to specify a fixed suffix of the file.
660     It is simpler to create a temporary directory.  */
661  template = (char *) xallocsa (PATH_MAX);
662  if (path_search (template, PATH_MAX, NULL, "msg", 1))
663    {
664      error (0, errno,
665	     _("cannot find a temporary directory, try setting $TMPDIR"));
666      goto quit1;
667    }
668  block_fatal_signals ();
669  tmpdir = mkdtemp (template);
670  cleanup_list.tmpdir = tmpdir;
671  unblock_fatal_signals ();
672  if (tmpdir == NULL)
673    {
674      error (0, errno,
675	     _("cannot create a temporary directory using template \"%s\""),
676	     template);
677      goto quit1;
678    }
679
680  /* Assign a default value to the resource name.  */
681  if (resource_name == NULL)
682    resource_name = "Messages";
683
684  /* Convert the locale name to a .NET specific culture name.  */
685  culture_name = xstrdup (locale_name);
686  {
687    char *p;
688    for (p = culture_name; *p != '\0'; p++)
689      if (*p == '_')
690	*p = '-';
691    if (strncmp (culture_name, "sr-CS", 5) == 0)
692      memcpy (culture_name, "sr-SP", 5);
693    p = strchr (culture_name, '@');
694    if (p != NULL)
695      {
696	if (strcmp (p, "@latin") == 0)
697	  strcpy (p, "-Latn");
698	else if (strcmp (p, "@cyrillic") == 0)
699	  strcpy (p, "-Cyrl");
700      }
701    if (strcmp (culture_name, "sr-SP") == 0)
702      {
703	free (culture_name);
704	culture_name = xstrdup ("sr-SP-Latn");
705      }
706    else if (strcmp (culture_name, "uz-UZ") == 0)
707      {
708	free (culture_name);
709	culture_name = xstrdup ("uz-UZ-Latn");
710      }
711  }
712
713
714  /* Compute the output file name.  This code must be kept consistent with
715     intl.cs, function GetSatelliteAssembly().  */
716  {
717    char *output_dir = concatenated_pathname (directory, culture_name, NULL);
718    struct stat statbuf;
719
720    /* Try to create the output directory if it does not yet exist.  */
721    if (stat (output_dir, &statbuf) < 0 && errno == ENOENT)
722      if (mkdir (output_dir, S_IRUSR | S_IWUSR | S_IXUSR
723			     | S_IRGRP | S_IWGRP | S_IXGRP
724			     | S_IROTH | S_IWOTH | S_IXOTH) < 0)
725	{
726	  error (0, errno, _("failed to create directory \"%s\""), output_dir);
727	  free (output_dir);
728	  goto quit3;
729	}
730
731    output_file =
732      concatenated_pathname (output_dir, resource_name, ".resources.dll");
733
734    free (output_dir);
735  }
736
737  /* Compute the class name.  This code must be kept consistent with intl.cs,
738     function InstantiateResourceSet().  */
739  {
740    char *class_name_part1 = construct_class_name (resource_name);
741    char *p;
742
743    class_name =
744      (char *) xmalloc (strlen (class_name_part1) + 1 + strlen (culture_name) + 1);
745    sprintf (class_name, "%s_%s", class_name_part1, culture_name);
746    for (p = class_name + strlen (class_name_part1) + 1; *p != '\0'; p++)
747      if (*p == '-')
748	*p = '_';
749    free (class_name_part1);
750  }
751
752  /* Compute the temporary C# file name.  It must end in ".cs", so that
753     the C# compiler recognizes that it is C# source code.  */
754  csharp_file_name = concatenated_pathname (tmpdir, "resset.cs", NULL);
755
756  /* Create the C# file.  */
757  cleanup_list.file_name = csharp_file_name;
758  csharp_file = fopen (csharp_file_name, "w");
759  if (csharp_file == NULL)
760    {
761      error (0, errno, _("failed to create \"%s\""), csharp_file_name);
762      goto quit4;
763    }
764
765  write_csharp_code (csharp_file, class_name, mlp);
766
767  if (fwriteerror (csharp_file))
768    {
769      error (0, errno, _("error while writing \"%s\" file"), csharp_file_name);
770      fclose (csharp_file);
771      goto quit5;
772    }
773
774  /* Make it possible to override the .dll location.  This is
775     necessary for running the testsuite before "make install".  */
776  gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR");
777  if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
778    gettextlibdir = relocate (LIBDIR);
779
780  /* Compile the C# file to a .dll file.  */
781  csharp_sources[0] = csharp_file_name;
782  libdirs[0] = gettextlibdir;
783  libraries[0] = "GNU.Gettext";
784  if (compile_csharp_class (csharp_sources, 1, libdirs, 1, libraries, 1,
785			    output_file, true, false, verbose))
786    {
787      error (0, 0, _("compilation of C# class failed, please try --verbose"));
788      goto quit5;
789    }
790
791  retval = 0;
792
793 quit5:
794  unlink (csharp_file_name);
795 quit4:
796  cleanup_list.file_name = NULL;
797  free (csharp_file_name);
798  free (class_name);
799  free (output_file);
800 quit3:
801  free (culture_name);
802  rmdir (tmpdir);
803 quit1:
804  cleanup_list.tmpdir = NULL;
805  freesa (template);
806  /* Here we could unregister the cleanup() handler.  */
807  return retval;
808}
809