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