1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 #include "fc_prehdrs.h"
19
20 #ifdef FREECIV_HAVE_SYS_TYPES_H
21 /* Under Mac OS X sys/types.h must be included before dirent.h */
22 #include <sys/types.h>
23 #endif
24
25 #include <dirent.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33
34 #ifdef HAVE_LOCALE_H
35 #include <locale.h>
36 #endif
37
38 #ifdef HAVE_PWD_H
39 #include <pwd.h>
40 #endif
41 #ifdef HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44 #ifdef WIN32_NATIVE
45 #include <windows.h>
46 #include <lmcons.h> /* UNLEN */
47 #include <shlobj.h>
48 #ifdef HAVE_DIRECT_H
49 #include <direct.h>
50 #endif /* HAVE_DIRECT_H */
51 #endif /* WIN32_NATIVE */
52
53 /* utility */
54 #include "astring.h"
55 #include "fciconv.h"
56 #include "fcintl.h"
57 #include "mem.h"
58 #include "rand.h"
59 #include "string_vector.h"
60
61 #include "shared.h"
62
63 /* If no default data path is defined use the default default one */
64 #ifndef DEFAULT_DATA_PATH
65 #define DEFAULT_DATA_PATH "." PATH_SEPARATOR \
66 "data" PATH_SEPARATOR \
67 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR DATASUBDIR
68 #endif
69 #ifndef DEFAULT_SAVE_PATH
70 #define DEFAULT_SAVE_PATH "." PATH_SEPARATOR \
71 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "saves"
72 #endif
73 #ifndef DEFAULT_SCENARIO_PATH
74 #define DEFAULT_SCENARIO_PATH \
75 "." PATH_SEPARATOR \
76 "data" DIR_SEPARATOR "scenarios" PATH_SEPARATOR \
77 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR DATASUBDIR DIR_SEPARATOR "scenarios" PATH_SEPARATOR \
78 "~" DIR_SEPARATOR ".freeciv" DIR_SEPARATOR "scenarios"
79 #endif /* DEFAULT_SCENARIO_PATH */
80
81 /* environment */
82 #ifndef FREECIV_PATH
83 #define FREECIV_PATH "FREECIV_PATH"
84 #endif
85 #ifndef FREECIV_DATA_PATH
86 #define FREECIV_DATA_PATH "FREECIV_DATA_PATH"
87 #endif
88 #ifndef FREECIV_SAVE_PATH
89 #define FREECIV_SAVE_PATH "FREECIV_SAVE_PATH"
90 #endif
91 #ifndef FREECIV_SCENARIO_PATH
92 #define FREECIV_SCENARIO_PATH "FREECIV_SCENARIO_PATH"
93 #endif
94
95 /* Both of these are stored in the local encoding. The grouping_sep must
96 * be converted to the internal encoding when it's used. */
97 static char *grouping = NULL;
98 static char *grouping_sep = NULL;
99
100 /* As well as base64 functions, this string is used for checking for
101 * 'safe' filenames, so should not contain / \ . */
102 static const char base64url[] =
103 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
104
105 static struct strvec *data_dir_names = NULL;
106 static struct strvec *save_dir_names = NULL;
107 static struct strvec *scenario_dir_names = NULL;
108
109 static char *mc_group = NULL;
110 static char *home_dir = NULL;
111
112 static bool depr_freeciv_path_warned = FALSE;
113
114 static struct astring realfile = ASTRING_INIT;
115
116 static int compare_file_mtime_ptrs(const struct fileinfo *const *ppa,
117 const struct fileinfo *const *ppb);
118
119
120 /***************************************************************************
121 An AND function for fc_tristate.
122 ***************************************************************************/
fc_tristate_and(enum fc_tristate one,enum fc_tristate two)123 enum fc_tristate fc_tristate_and(enum fc_tristate one, enum fc_tristate two)
124 {
125 if (TRI_NO == one || TRI_NO == two) {
126 return TRI_NO;
127 }
128
129 if (TRI_MAYBE == one || TRI_MAYBE == two) {
130 return TRI_MAYBE;
131 }
132
133 return TRI_YES;
134 }
135
136 /***************************************************************
137 Take a string containing multiple lines and create a copy where
138 each line is padded to the length of the longest line and centered.
139 We do not cope with tabs etc. Note that we're assuming that the
140 last line does _not_ end with a newline. The caller should
141 free() the result.
142
143 FIXME: This is only used in the Xaw client, and so probably does
144 not belong in common.
145 ***************************************************************/
create_centered_string(const char * s)146 char *create_centered_string(const char *s)
147 {
148 /* Points to the part of the source that we're looking at. */
149 const char *cp;
150
151 /* Points to the beginning of the line in the source that we're
152 * looking at. */
153 const char *cp0;
154
155 /* Points to the result. */
156 char *r;
157
158 /* Points to the part of the result that we're filling in right
159 now. */
160 char *rn;
161
162 int i;
163
164 int maxlen = 0;
165 int curlen = 0;
166 int nlines = 1;
167
168 for(cp=s; *cp != '\0'; cp++) {
169 if(*cp!='\n')
170 curlen++;
171 else {
172 if(maxlen<curlen)
173 maxlen=curlen;
174 curlen=0;
175 nlines++;
176 }
177 }
178 if(maxlen<curlen)
179 maxlen=curlen;
180
181 r=rn=fc_malloc(nlines*(maxlen+1));
182
183 curlen=0;
184 for(cp0=cp=s; *cp != '\0'; cp++) {
185 if(*cp!='\n')
186 curlen++;
187 else {
188 for(i=0; i<(maxlen-curlen)/2; i++)
189 *rn++=' ';
190 memcpy(rn, cp0, curlen);
191 rn+=curlen;
192 *rn++='\n';
193 curlen=0;
194 cp0=cp+1;
195 }
196 }
197 for(i=0; i<(maxlen-curlen)/2; i++)
198 *rn++=' ';
199 strcpy(rn, cp0);
200
201 return r;
202 }
203
204 /************************************************************************//**
205 An OR function for fc_tristate.
206 ****************************************************************************/
fc_tristate_or(enum fc_tristate one,enum fc_tristate two)207 enum fc_tristate fc_tristate_or(enum fc_tristate one, enum fc_tristate two)
208 {
209 if (TRI_YES == one || TRI_YES == two) {
210 return TRI_YES;
211 }
212
213 if (TRI_MAYBE == one || TRI_MAYBE == two) {
214 return TRI_MAYBE;
215 }
216
217 return TRI_NO;
218 }
219
220 /***************************************************************
221 Returns a statically allocated string containing a nicely-formatted
222 version of the given number according to the user's locale. (Only
223 works for numbers >= zero.) The number is given in scientific notation
224 as mantissa * 10^exponent.
225 ***************************************************************/
big_int_to_text(unsigned int mantissa,unsigned int exponent)226 const char *big_int_to_text(unsigned int mantissa, unsigned int exponent)
227 {
228 static char buf[64]; /* Note that we'll be filling this in right to left. */
229 char *grp = grouping;
230 char *ptr;
231 unsigned int cnt = 0;
232 char sep[64];
233 size_t seplen;
234
235 /* We have to convert the encoding here (rather than when the locale
236 * is initialized) because it can't be done before the charsets are
237 * initialized. */
238 local_to_internal_string_buffer(grouping_sep, sep, sizeof(sep));
239 seplen = strlen(sep);
240
241 #if 0 /* Not needed while the values are unsigned. */
242 fc_assert_ret_val(0 <= mantissa, NULL);
243 fc_assert_ret_val(0 <= exponent, NULL);
244 #endif
245
246 if (mantissa == 0) {
247 return "0";
248 }
249
250 /* We fill the string in backwards, starting from the right. So the first
251 * thing we do is terminate it. */
252 ptr = &buf[sizeof(buf)];
253 *(--ptr) = '\0';
254
255 while (mantissa != 0 && exponent >= 0) {
256 int dig;
257
258 if (ptr <= buf + seplen) {
259 /* Avoid a buffer overflow. */
260 fc_assert_ret_val(ptr > buf + seplen, NULL);
261 return ptr;
262 }
263
264 /* Add on another character. */
265 if (exponent > 0) {
266 dig = 0;
267 exponent--;
268 } else {
269 dig = mantissa % 10;
270 mantissa /= 10;
271 }
272 *(--ptr) = '0' + dig;
273
274 cnt++;
275 if (mantissa != 0 && cnt == *grp) {
276 /* Reached count of digits in group: insert separator and reset count. */
277 cnt = 0;
278 if (*grp == CHAR_MAX) {
279 /* This test is unlikely to be necessary since we would need at
280 least 421-bit ints to break the 127 digit barrier, but why not. */
281 break;
282 }
283 ptr -= seplen;
284 fc_assert_ret_val(ptr >= buf, NULL);
285 memcpy(ptr, sep, seplen);
286 if (*(grp + 1) != 0) {
287 /* Zero means to repeat the present group-size indefinitely. */
288 grp++;
289 }
290 }
291 }
292
293 return ptr;
294 }
295
296
297 /****************************************************************************
298 Return a prettily formatted string containing the given number.
299 ****************************************************************************/
int_to_text(unsigned int number)300 const char *int_to_text(unsigned int number)
301 {
302 return big_int_to_text(number, 0);
303 }
304
305 /****************************************************************************
306 Check whether or not the given char is a valid ascii character. The
307 character can be in any charset so long as it is a superset of ascii.
308 ****************************************************************************/
is_ascii(char ch)309 static bool is_ascii(char ch)
310 {
311 /* this works with both signed and unsigned char's. */
312 return ch >= ' ' && ch <= '~';
313 }
314
315 /****************************************************************************
316 Check if the name is safe security-wise. This is intended to be used to
317 make sure an untrusted filename is safe to be used.
318 ****************************************************************************/
is_safe_filename(const char * name)319 bool is_safe_filename(const char *name)
320 {
321 int i = 0;
322
323 /* must not be NULL or empty */
324 if (!name || *name == '\0') {
325 return FALSE;
326 }
327
328 for (; '\0' != name[i]; i++) {
329 if ('.' != name[i] && NULL == strchr(base64url, name[i])) {
330 return FALSE;
331 }
332 }
333
334 /* we don't allow the filename to ascend directories */
335 if (strstr(name, PARENT_DIR_OPERATOR)) {
336 return FALSE;
337 }
338
339 /* Otherwise, it is okay... */
340 return TRUE;
341 }
342
343 /***************************************************************
344 This is used in sundry places to make sure that names of cities,
345 players etc. do not contain yucky characters of various sorts.
346 Returns TRUE iff the name is acceptable.
347 FIXME: Not internationalised.
348 ***************************************************************/
is_ascii_name(const char * name)349 bool is_ascii_name(const char *name)
350 {
351 const char illegal_chars[] = {'|', '%', '"', ',', '*', '<', '>', '\0'};
352 int i, j;
353
354 /* must not be NULL or empty */
355 if (!name || *name == '\0') {
356 return FALSE;
357 }
358
359 /* must begin and end with some non-space character */
360 if ((*name == ' ') || (*(strchr(name, '\0') - 1) == ' ')) {
361 return FALSE;
362 }
363
364 /* must be composed entirely of printable ascii characters,
365 * and no illegal characters which can break ranking scripts. */
366 for (i = 0; name[i]; i++) {
367 if (!is_ascii(name[i])) {
368 return FALSE;
369 }
370 for (j = 0; illegal_chars[j]; j++) {
371 if (name[i] == illegal_chars[j]) {
372 return FALSE;
373 }
374 }
375 }
376
377 /* otherwise, it's okay... */
378 return TRUE;
379 }
380
381 /*************************************************************************
382 Check for valid base64url.
383 *************************************************************************/
is_base64url(const char * s)384 bool is_base64url(const char *s)
385 {
386 size_t i = 0;
387
388 /* must not be NULL or empty */
389 if (NULL == s || '\0' == *s) {
390 return FALSE;
391 }
392
393 for (; '\0' != s[i]; i++) {
394 if (NULL == strchr(base64url, s[i])) {
395 return FALSE;
396 }
397 }
398 return TRUE;
399 }
400
401 /*************************************************************************
402 generate a random string meeting criteria such as is_ascii_name(),
403 is_base64url(), and is_safe_filename().
404 *************************************************************************/
randomize_base64url_string(char * s,size_t n)405 void randomize_base64url_string(char *s, size_t n)
406 {
407 size_t i = 0;
408
409 /* must not be NULL or too short */
410 if (NULL == s || 1 > n) {
411 return;
412 }
413
414 for (; i < (n - 1); i++) {
415 s[i] = base64url[fc_rand(sizeof(base64url) - 1)];
416 }
417 s[i] = '\0';
418 }
419
420 /**************************************************************************
421 Compares two strings, in the collating order of the current locale,
422 given pointers to the two strings (i.e., given "char *"s).
423 Case-sensitive. Designed to be called from qsort().
424 **************************************************************************/
compare_strings(const void * first,const void * second)425 int compare_strings(const void *first, const void *second)
426 {
427 return fc_strcoll((const char *) first, (const char *) second);
428 }
429
430 /**************************************************************************
431 Compares two strings, in the collating order of the current locale,
432 given pointers to the two string pointers (i.e., given "char **"s).
433 Case-sensitive. Designed to be called from qsort().
434 **************************************************************************/
compare_strings_ptrs(const void * first,const void * second)435 int compare_strings_ptrs(const void *first, const void *second)
436 {
437 return fc_strcoll(*((const char **) first), *((const char **) second));
438 }
439
440 /**************************************************************************
441 Compares two strings, in the collating order of the current locale,
442 given pointers to the two string pointers. Case-sensitive.
443 Designed to be called from strvec_sort().
444 **************************************************************************/
compare_strings_strvec(const char * const * first,const char * const * second)445 int compare_strings_strvec(const char *const *first,
446 const char *const *second)
447 {
448 return fc_strcoll(*first, *second);
449 }
450
451 /***************************************************************************
452 Returns 's' incremented to first non-space character.
453 ***************************************************************************/
skip_leading_spaces(char * s)454 char *skip_leading_spaces(char *s)
455 {
456 fc_assert_ret_val(NULL != s, NULL);
457 while(*s != '\0' && fc_isspace(*s)) {
458 s++;
459 }
460 return s;
461 }
462
463 /***************************************************************************
464 Removes leading spaces in string pointed to by 's'.
465 Note 's' must point to writeable memory!
466 ***************************************************************************/
remove_leading_spaces(char * s)467 void remove_leading_spaces(char *s)
468 {
469 char *t;
470
471 fc_assert_ret(NULL != s);
472 t = skip_leading_spaces(s);
473 if (t != s) {
474 while (*t != '\0') {
475 *s++ = *t++;
476 }
477 *s = '\0';
478 }
479 }
480
481 /***************************************************************************
482 Terminates string pointed to by 's' to remove traling spaces;
483 Note 's' must point to writeable memory!
484 ***************************************************************************/
remove_trailing_spaces(char * s)485 void remove_trailing_spaces(char *s)
486 {
487 char *t;
488 size_t len;
489
490 fc_assert_ret(NULL != s);
491 len = strlen(s);
492 if (len > 0) {
493 t = s + len -1;
494 while(fc_isspace(*t)) {
495 *t = '\0';
496 if (t == s) {
497 break;
498 }
499 t--;
500 }
501 }
502 }
503
504 /***************************************************************************
505 Removes leading and trailing spaces in string pointed to by 's'.
506 Note 's' must point to writeable memory!
507 ***************************************************************************/
remove_leading_trailing_spaces(char * s)508 void remove_leading_trailing_spaces(char *s)
509 {
510 remove_leading_spaces(s);
511 remove_trailing_spaces(s);
512 }
513
514 /***************************************************************************
515 As remove_trailing_spaces(), for specified char.
516 ***************************************************************************/
remove_trailing_char(char * s,char trailing)517 static void remove_trailing_char(char *s, char trailing)
518 {
519 char *t;
520
521 fc_assert_ret(NULL != s);
522 t = s + strlen(s) -1;
523 while(t>=s && (*t) == trailing) {
524 *t = '\0';
525 t--;
526 }
527 }
528
529 /***************************************************************************
530 Returns pointer to '\0' at end of string 'str', and decrements
531 *nleft by the length of 'str'. This is intended to be useful to
532 allow strcat-ing without traversing the whole string each time,
533 while still keeping track of the buffer length.
534 Eg:
535 char buf[128];
536 int n = sizeof(buf);
537 char *p = buf;
538
539 fc_snprintf(p, n, "foo%p", p);
540 p = end_of_strn(p, &n);
541 fc_strlcpy(p, "yyy", n);
542 ***************************************************************************/
end_of_strn(char * str,int * nleft)543 char *end_of_strn(char *str, int *nleft)
544 {
545 int len = strlen(str);
546 *nleft -= len;
547 fc_assert_ret_val(0 < (*nleft), NULL); /* space for the terminating nul */
548 return str + len;
549 }
550
551 /**********************************************************************
552 Check the length of the given string. If the string is too long,
553 log errmsg, which should be a string in printf-format taking up to
554 two arguments: the string and the length.
555 **********************************************************************/
check_strlen(const char * str,size_t len,const char * errmsg)556 bool check_strlen(const char *str, size_t len, const char *errmsg)
557 {
558 fc_assert_ret_val_msg(strlen(str) < len, TRUE, errmsg, str, len);
559 return FALSE;
560 }
561
562 /**********************************************************************
563 Call check_strlen() on str and then strlcpy() it into buffer.
564 **********************************************************************/
loud_strlcpy(char * buffer,const char * str,size_t len,const char * errmsg)565 size_t loud_strlcpy(char *buffer, const char *str, size_t len,
566 const char *errmsg)
567 {
568 (void) check_strlen(str, len, errmsg);
569 return fc_strlcpy(buffer, str, len);
570 }
571
572 /****************************************************************************
573 Convert 'str' to its int reprentation if possible. 'pint' can be NULL,
574 then it will only test 'str' only contains an integer number.
575 ****************************************************************************/
str_to_int(const char * str,int * pint)576 bool str_to_int(const char *str, int *pint)
577 {
578 const char *start;
579
580 fc_assert_ret_val(NULL != str, FALSE);
581
582 while (fc_isspace(*str)) {
583 /* Skip leading spaces. */
584 str++;
585 }
586
587 start = str;
588 if ('-' == *str || '+' == *str) {
589 /* Handle sign. */
590 str++;
591 }
592 while (fc_isdigit(*str)) {
593 /* Digits. */
594 str++;
595 }
596
597 while (fc_isspace(*str)) {
598 /* Ignore trailing spaces. */
599 str++;
600 }
601
602 return ('\0' == *str && (NULL == pint || 1 == sscanf(start, "%d", pint)));
603 }
604
605 /****************************************************************************
606 Convert 'str' to it's unsigned int reprentation if possible. 'pint' can be NULL,
607 then it will only test 'str' only contains an unsigned integer number.
608 ****************************************************************************/
str_to_uint(const char * str,unsigned int * pint)609 bool str_to_uint(const char *str, unsigned int *pint)
610 {
611 const char *start;
612
613 fc_assert_ret_val(NULL != str, FALSE);
614
615 while (fc_isspace(*str)) {
616 /* Skip leading spaces. */
617 str++;
618 }
619
620 start = str;
621 if ('+' == *str) {
622 /* Handle sign. */
623 str++;
624 }
625 while (fc_isdigit(*str)) {
626 /* Digits. */
627 str++;
628 }
629
630 while (fc_isspace(*str)) {
631 /* Ignore trailing spaces. */
632 str++;
633 }
634
635 return ('\0' == *str && (NULL == pint || 1 == sscanf(start, "%u", pint)));
636 }
637
638 /****************************************************************************
639 Convert 'str' to it's float reprentation if possible. 'pfloat' can be NULL,
640 then it will only test 'str' only contains a floating point number.
641 ****************************************************************************/
str_to_float(const char * str,float * pfloat)642 bool str_to_float(const char *str, float *pfloat)
643 {
644 bool dot;
645 const char *start;
646
647 fc_assert_ret_val(NULL != str, FALSE);
648
649 while (fc_isspace(*str)) {
650 /* Skip leading spaces. */
651 str++;
652 }
653
654 start = str;
655
656 if ('-' == *str || '+' == *str) {
657 /* Handle sign. */
658 str++;
659 }
660 while (fc_isdigit(*str)) {
661 /* Digits. */
662 str++;
663 }
664
665 if (*str == '.') {
666 dot = TRUE;
667 str++;
668
669 while (fc_isdigit(*str)) {
670 /* Digits. */
671 str++;
672 }
673 } else {
674 dot = FALSE;
675 }
676
677 while (fc_isspace(*str)) {
678 /* Ignore trailing spaces. */
679 str++;
680 }
681
682 return ('\0' == *str && dot
683 && (NULL == pfloat || 1 == sscanf(start, "%f", pfloat)));
684 }
685
686 /***************************************************************************
687 Returns string which gives users home dir, as specified by $HOME.
688 Gets value once, and then caches result.
689 If $HOME is not set, give a log message and returns NULL.
690 Note the caller should not mess with the returned string.
691 ***************************************************************************/
user_home_dir(void)692 char *user_home_dir(void)
693 {
694 #ifdef AMIGA
695 return "PROGDIR:";
696 #else /* AMIGA */
697
698 if (home_dir == NULL) {
699 #ifdef FREECIV_MSWINDOWS
700
701 /* some documentation at:
702 * http://justcheckingonall.wordpress.com/2008/05/16/find-shell-folders-win32/
703 * http://archives.seul.org/or/cvs/Oct-2004/msg00082.html */
704
705 LPITEMIDLIST pidl;
706 LPMALLOC pMalloc;
707
708 if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl))) {
709 char *home_dir_in_local_encoding = fc_malloc(PATH_MAX);
710
711 if (SUCCEEDED(SHGetPathFromIDList(pidl, home_dir_in_local_encoding))) {
712 /* convert to internal encoding */
713 home_dir = local_to_internal_string_malloc(home_dir_in_local_encoding);
714 free(home_dir_in_local_encoding);
715
716 #ifdef DIR_SEPARATOR_IS_DEFAULT
717 /* replace backslashes with forward slashes */
718 {
719 char *c;
720
721 for (c = home_dir; *c != 0; c++) {
722 if (*c == '\\') {
723 *c = DIR_SEPARATOR_CHAR;
724 }
725 }
726 }
727 #endif /* DIR_SEPARATOR_IS_DEFAULT */
728 } else {
729 free(home_dir_in_local_encoding);
730 home_dir = NULL;
731 log_error("Could not find home directory "
732 "(SHGetPathFromIDList() failed).");
733 }
734
735 SHGetMalloc(&pMalloc);
736 if (pMalloc) {
737 pMalloc->lpVtbl->Free(pMalloc, pidl);
738 pMalloc->lpVtbl->Release(pMalloc);
739 }
740
741 } else {
742 log_error("Could not find home directory "
743 "(SHGetSpecialFolderLocation() failed).");
744 }
745
746 if (home_dir == NULL)
747 #endif /* FREECIV_MSWINDOWS */
748 {
749 char *env = getenv("HOME");
750
751 if (env) {
752 home_dir = fc_strdup(env);
753 log_verbose("HOME is %s", home_dir);
754 } else {
755 log_error("Could not find home directory (HOME is not set).");
756 home_dir = NULL;
757 }
758 }
759 }
760
761 return home_dir;
762 #endif /* AMIGA */
763 }
764
765 /***************************************************************************
766 Free user home directory information
767 ***************************************************************************/
free_user_home_dir(void)768 void free_user_home_dir(void)
769 {
770 if (home_dir != NULL) {
771 free(home_dir);
772 home_dir = NULL;
773 }
774 }
775
776 /***************************************************************************
777 Returns string which gives user's username, as specified by $USER or
778 as given in password file for this user's uid, or a made up name if
779 we can't get either of the above.
780 Gets value once, and then caches result.
781 Note the caller should not mess with returned string.
782 ***************************************************************************/
user_username(char * buf,size_t bufsz)783 char *user_username(char *buf, size_t bufsz)
784 {
785 /* This function uses a number of different methods to try to find a
786 * username. This username then has to be truncated to bufsz
787 * characters (including terminator) and checked for sanity. Note that
788 * truncating a sane name can leave you with an insane name under some
789 * charsets. */
790
791 /* If the environment variable $USER is present and sane, use it. */
792 {
793 char *env = getenv("USER");
794
795 if (env) {
796 fc_strlcpy(buf, env, bufsz);
797 if (is_ascii_name(buf)) {
798 log_verbose("USER username is %s", buf);
799 return buf;
800 }
801 }
802 }
803
804 #ifdef HAVE_GETPWUID
805 /* Otherwise if getpwuid() is available we can use it to find the true
806 * username. */
807 {
808 struct passwd *pwent = getpwuid(getuid());
809
810 if (pwent) {
811 fc_strlcpy(buf, pwent->pw_name, bufsz);
812 if (is_ascii_name(buf)) {
813 log_verbose("getpwuid username is %s", buf);
814 return buf;
815 }
816 }
817 }
818 #endif /* HAVE_GETPWUID */
819
820 #ifdef WIN32_NATIVE
821 /* On win32 the GetUserName function will give us the login name. */
822 {
823 char name[UNLEN + 1];
824 DWORD length = sizeof(name);
825
826 if (GetUserName(name, &length)) {
827 fc_strlcpy(buf, name, bufsz);
828 if (is_ascii_name(buf)) {
829 log_verbose("GetUserName username is %s", buf);
830 return buf;
831 }
832 }
833 }
834 #endif /* WIN32_NATIVE */
835
836 #ifdef ALWAYS_ROOT
837 fc_strlcpy(buf, "name", bufsz);
838 #else
839 fc_snprintf(buf, bufsz, "name%d", (int) getuid());
840 #endif
841 log_verbose("fake username is %s", buf);
842 fc_assert(is_ascii_name(buf));
843 return buf;
844 }
845
846 /***************************************************************************
847 Returns a list of directory paths, in the order in which they should
848 be searched. Base function for get_data_dirs(), get_save_dirs(),
849 get_scenario_dirs()
850 ***************************************************************************/
base_get_dirs(const char * dir_list)851 static struct strvec *base_get_dirs(const char *dir_list)
852 {
853 struct strvec *dirs = strvec_new();
854 char *path, *tok;
855
856 path = fc_strdup(dir_list); /* something we can strtok */
857 tok = strtok(path, PATH_SEPARATOR);
858 do {
859 int i; /* strlen(tok), or -1 as flag */
860
861 tok = skip_leading_spaces(tok);
862 remove_trailing_spaces(tok);
863 if (strcmp(tok, DIR_SEPARATOR) != 0) {
864 remove_trailing_char(tok, DIR_SEPARATOR_CHAR);
865 }
866
867 i = strlen(tok);
868 if (tok[0] == '~') {
869 if (i > 1 && tok[1] != DIR_SEPARATOR_CHAR) {
870 log_error("For \"%s\" in path cannot expand '~'"
871 " except as '~" DIR_SEPARATOR "'; ignoring", tok);
872 i = 0; /* skip this one */
873 } else {
874 char *home = user_home_dir();
875
876 if (!home) {
877 log_verbose("No HOME, skipping path component %s", tok);
878 i = 0;
879 } else {
880 int len = strlen(home) + i; /* +1 -1 */
881 char *tmp = fc_malloc(len);
882
883 fc_snprintf(tmp, len, "%s%s", home, tok + 1);
884 tok = tmp;
885 i = -1; /* flag to free tok below */
886 }
887 }
888 }
889
890 if (i != 0) {
891 /* We could check whether the directory exists and
892 * is readable etc? Don't currently. */
893 strvec_append(dirs, tok);
894 if (i == -1) {
895 free(tok);
896 tok = NULL;
897 }
898 }
899
900 tok = strtok(NULL, PATH_SEPARATOR);
901 } while(tok);
902
903 free(path);
904 return dirs;
905 }
906
907 /***************************************************************************
908 Free data dir name vectors.
909 ***************************************************************************/
free_data_dir_names(void)910 void free_data_dir_names(void)
911 {
912 if (data_dir_names != NULL) {
913 strvec_destroy(data_dir_names);
914 data_dir_names = NULL;
915 }
916 if (save_dir_names != NULL) {
917 strvec_destroy(save_dir_names);
918 save_dir_names = NULL;
919 }
920 if (scenario_dir_names != NULL) {
921 strvec_destroy(scenario_dir_names);
922 scenario_dir_names = NULL;
923 }
924 }
925
926 /***************************************************************************
927 Returns a list of data directory paths, in the order in which they should
928 be searched. These paths are specified internally or may be set as the
929 environment variable $FREECIV_DATA PATH (a separated list of directories,
930 where the separator itself is specified internally, platform-dependent).
931 FREECIV_PATH may also be consulted for backward compatibility.
932 '~' at the start of a component (provided followed by '/' or '\0') is
933 expanded as $HOME.
934
935 The returned pointer is static and shouldn't be modified, nor destroyed
936 by the user caller.
937 ***************************************************************************/
get_data_dirs(void)938 const struct strvec *get_data_dirs(void)
939 {
940 /* The first time this function is called it will search and
941 * allocate the directory listing. Subsequently we will already
942 * know the list and can just return it. */
943 if (NULL == data_dir_names) {
944 const char *path;
945
946 if ((path = getenv(FREECIV_DATA_PATH)) && '\0' == path[0]) {
947 /* TRANS: <FREECIV_DATA_PATH> configuration error */
948 log_error(_("\"%s\" is set but empty; trying \"%s\" instead."),
949 FREECIV_DATA_PATH, FREECIV_PATH);
950 path = NULL;
951 }
952 if (NULL == path && (path = getenv(FREECIV_PATH))) {
953 if (!depr_freeciv_path_warned) {
954 log_error(_("FREECIV_PATH is deprecated, and won't work in future versions."));
955 depr_freeciv_path_warned = TRUE;
956 }
957 if ('\0' == path[0]) {
958 /* TRANS: <FREECIV_PATH> configuration error */
959 log_error(_("\"%s\" is set but empty; using default \"%s\" "
960 "data directories instead."),
961 FREECIV_PATH, DEFAULT_DATA_PATH);
962 path = NULL;
963 }
964 }
965 data_dir_names = base_get_dirs(NULL != path ? path : DEFAULT_DATA_PATH);
966 strvec_remove_duplicate(data_dir_names, strcmp); /* Don't set a path both. */
967 strvec_iterate(data_dir_names, dirname) {
968 log_verbose("Data path component: %s", dirname);
969 } strvec_iterate_end;
970 }
971
972 return data_dir_names;
973 }
974
975 /***************************************************************************
976 Returns a list of save directory paths, in the order in which they should
977 be searched. These paths are specified internally or may be set as the
978 environment variable $FREECIV_SAVE_PATH (a separated list of directories,
979 where the separator itself is specified internally, platform-dependent).
980 FREECIV_PATH may also be consulted for backward compatibility.
981 '~' at the start of a component (provided followed by '/' or '\0') is
982 expanded as $HOME.
983
984 The returned pointer is static and shouldn't be modified, nor destroyed
985 by the user caller.
986 ***************************************************************************/
get_save_dirs(void)987 const struct strvec *get_save_dirs(void)
988 {
989 /* The first time this function is called it will search and
990 * allocate the directory listing. Subsequently we will already
991 * know the list and can just return it. */
992 if (NULL == save_dir_names) {
993 const char *path;
994 bool from_freeciv_path = FALSE;
995
996 if ((path = getenv(FREECIV_SAVE_PATH)) && '\0' == path[0]) {
997 /* TRANS: <FREECIV_SAVE_PATH> configuration error */
998 log_error(_("\"%s\" is set but empty; trying \"%s\" instead."),
999 FREECIV_SAVE_PATH, FREECIV_PATH);
1000 path = NULL;
1001 }
1002 if (NULL == path && (path = getenv(FREECIV_PATH))) {
1003 if (!depr_freeciv_path_warned) {
1004 log_error(_("FREECIV_PATH is deprecated, and won't work in future versions."));
1005 depr_freeciv_path_warned = TRUE;
1006 }
1007 if ('\0' == path[0]) {
1008 /* TRANS: <FREECIV_PATH> configuration error */
1009 log_error(_("\"%s\" is set but empty; using default \"%s\" "
1010 "save directories instead."),
1011 FREECIV_PATH, DEFAULT_SAVE_PATH);
1012 path = NULL;
1013 } else {
1014 from_freeciv_path = TRUE;
1015 }
1016 }
1017 save_dir_names = base_get_dirs(NULL != path ? path : DEFAULT_SAVE_PATH);
1018 if (from_freeciv_path) {
1019 /* Then also append a "/saves" suffix to every directory. */
1020 char buf[512];
1021 size_t i;
1022
1023 for (i = 0; i < strvec_size(save_dir_names); i++) {
1024 path = strvec_get(save_dir_names, i);
1025 fc_snprintf(buf, sizeof(buf), "%s/saves", path);
1026 strvec_insert(save_dir_names, ++i, buf);
1027 }
1028 }
1029 strvec_remove_duplicate(save_dir_names, strcmp); /* Don't set a path both. */
1030 strvec_iterate(save_dir_names, dirname) {
1031 log_verbose("Save path component: %s", dirname);
1032 } strvec_iterate_end;
1033 }
1034
1035 return save_dir_names;
1036 }
1037
1038 /***************************************************************************
1039 Returns a list of scenario directory paths, in the order in which they
1040 should be searched. These paths are specified internally or may be set
1041 as the environment variable $FREECIV_SCENARIO_PATH (a separated list of
1042 directories, where the separator itself is specified internally,
1043 platform-dependent). FREECIV_PATH may also be consulted for backward
1044 compatibility. '~' at the start of a component (provided followed
1045 by '/' or '\0') is expanded as $HOME.
1046
1047 The returned pointer is static and shouldn't be modified, nor destroyed
1048 by the user caller.
1049 ***************************************************************************/
get_scenario_dirs(void)1050 const struct strvec *get_scenario_dirs(void)
1051 {
1052 /* The first time this function is called it will search and
1053 * allocate the directory listing. Subsequently we will already
1054 * know the list and can just return it. */
1055 if (NULL == scenario_dir_names) {
1056 const char *path;
1057 bool from_freeciv_path = FALSE;
1058
1059 if ((path = getenv(FREECIV_SCENARIO_PATH)) && '\0' == path[0]) {
1060 /* TRANS: <FREECIV_SCENARIO_PATH> configuration error */
1061 log_error(_("\"%s\" is set but empty; trying \"%s\" instead."),
1062 FREECIV_SCENARIO_PATH, FREECIV_PATH);
1063 path = NULL;
1064 }
1065 if (NULL == path && (path = getenv(FREECIV_PATH))) {
1066 if (!depr_freeciv_path_warned) {
1067 log_error(_("FREECIV_PATH is deprecated, and won't work in future versions."));
1068 depr_freeciv_path_warned = TRUE;
1069 }
1070 if ('\0' == path[0]) {
1071 /* TRANS: <FREECIV_PATH> configuration error */
1072 log_error( _("\"%s\" is set but empty; using default \"%s\" "
1073 "scenario directories instead."),
1074 FREECIV_PATH, DEFAULT_SCENARIO_PATH);
1075 path = NULL;
1076 } else {
1077 from_freeciv_path = TRUE;
1078 }
1079 }
1080 scenario_dir_names = base_get_dirs(NULL != path ? path : DEFAULT_SCENARIO_PATH);
1081 if (from_freeciv_path) {
1082 /* Then also append subdirs every directory. */
1083 const char *subdirs[] = {
1084 "scenarios", "scenario", NULL
1085 };
1086 char buf[512];
1087 const char **subdir;
1088 size_t i;
1089
1090 for (i = 0; i < strvec_size(scenario_dir_names); i++) {
1091 path = strvec_get(scenario_dir_names, i);
1092 for (subdir = subdirs; NULL != *subdir; subdir++) {
1093 fc_snprintf(buf, sizeof(buf), "%s/%s", path, *subdir);
1094 strvec_insert(scenario_dir_names, ++i, buf);
1095 }
1096 }
1097 }
1098 strvec_remove_duplicate(scenario_dir_names, strcmp); /* Don't set a path both. */
1099 strvec_iterate(scenario_dir_names, dirname) {
1100 log_verbose("Scenario path component: %s", dirname);
1101 } strvec_iterate_end;
1102 }
1103
1104 return scenario_dir_names;
1105 }
1106
1107 /***************************************************************************
1108 Returns a string vector storing the filenames in the data directories
1109 matching the given suffix.
1110
1111 The list is allocated when the function is called; it should either
1112 be stored permanently or destroyed (with strvec_destroy()).
1113
1114 The suffixes are removed from the filenames before the list is
1115 returned.
1116 ***************************************************************************/
fileinfolist(const struct strvec * dirs,const char * suffix)1117 struct strvec *fileinfolist(const struct strvec *dirs, const char *suffix)
1118 {
1119 struct strvec *files = strvec_new();
1120 size_t suffix_len = strlen(suffix);
1121
1122 fc_assert_ret_val(!strchr(suffix, DIR_SEPARATOR_CHAR), NULL);
1123
1124 if (NULL == dirs) {
1125 return files;
1126 }
1127
1128 /* First assemble a full list of names. */
1129 strvec_iterate(dirs, dirname) {
1130 DIR *dir;
1131 struct dirent *entry;
1132
1133 /* Open the directory for reading. */
1134 dir = fc_opendir(dirname);
1135 if (!dir) {
1136 if (errno == ENOENT) {
1137 log_verbose("Skipping non-existing data directory %s.",
1138 dirname);
1139 } else {
1140 /* TRANS: "...: <externally translated error string>."*/
1141 log_error(_("Could not read data directory %s: %s."),
1142 dirname, fc_strerror(fc_get_errno()));
1143 }
1144 continue;
1145 }
1146
1147 /* Scan all entries in the directory. */
1148 while ((entry = readdir(dir))) {
1149 size_t len = strlen(entry->d_name);
1150
1151 /* Make sure the file name matches. */
1152 if (len > suffix_len
1153 && strcmp(suffix, entry->d_name + len - suffix_len) == 0) {
1154 /* Strdup the entry so we can safely write to it. */
1155 char *match = fc_strdup(entry->d_name);
1156
1157 /* Clip the suffix. */
1158 match[len - suffix_len] = '\0';
1159
1160 strvec_append(files, match);
1161 free(match);
1162 }
1163 }
1164
1165 closedir(dir);
1166 } strvec_iterate_end;
1167
1168 /* Sort the list and remove duplications. */
1169 strvec_remove_duplicate(files, strcmp);
1170 strvec_sort(files, compare_strings_strvec);
1171
1172 return files;
1173 }
1174
1175 /***************************************************************************
1176 Returns a filename to access the specified file from a
1177 directory by searching all specified directories for the file.
1178
1179 If the specified 'filename' is NULL, the returned string contains
1180 the effective path. (But this should probably only be used for
1181 debug output.)
1182
1183 Returns NULL if the specified filename cannot be found in any of the
1184 data directories. (A file is considered "found" if it can be
1185 read-opened.) The returned pointer points to static memory, so this
1186 function can only supply one filename at a time. Don't free that
1187 pointer.
1188
1189 TODO: Make this re-entrant
1190 ***************************************************************************/
fileinfoname(const struct strvec * dirs,const char * filename)1191 const char *fileinfoname(const struct strvec *dirs, const char *filename)
1192 {
1193 #ifndef DIR_SEPARATOR_IS_DEFAULT
1194 char fnbuf[filename != NULL ? strlen(filename) + 1 : 1];
1195 int i;
1196 #else /* DIR_SEPARATOR_IS_DEFAULT */
1197 const char *fnbuf = filename;
1198 #endif /* DIR_SEPARATOR_IS_DEFAULT */
1199
1200 if (NULL == dirs) {
1201 return NULL;
1202 }
1203
1204 if (!filename) {
1205 bool first = TRUE;
1206
1207 astr_clear(&realfile);
1208 strvec_iterate(dirs, dirname) {
1209 if (first) {
1210 astr_add(&realfile, "%s%s", PATH_SEPARATOR, dirname);
1211 first = FALSE;
1212 } else {
1213 astr_add(&realfile, "%s", dirname);
1214 }
1215 } strvec_iterate_end;
1216
1217 return astr_str(&realfile);
1218 }
1219
1220 #ifndef DIR_SEPARATOR_IS_DEFAULT
1221 for (i = 0; filename[i] != '\0'; i++) {
1222 if (filename[i] == '/') {
1223 fnbuf[i] = DIR_SEPARATOR_CHAR;
1224 } else {
1225 fnbuf[i] = filename[i];
1226 }
1227 }
1228 fnbuf[i] = '\0';
1229 #endif /* DIR_SEPARATOR_IS_DEFAULT */
1230
1231 strvec_iterate(dirs, dirname) {
1232 struct stat buf; /* see if we can open the file or directory */
1233
1234 astr_set(&realfile, "%s" DIR_SEPARATOR "%s", dirname, fnbuf);
1235 if (fc_stat(astr_str(&realfile), &buf) == 0) {
1236 return astr_str(&realfile);
1237 }
1238 } strvec_iterate_end;
1239
1240 log_verbose("Could not find readable file \"%s\" in data path.", filename);
1241
1242 return NULL;
1243 }
1244
1245 /**************************************************************************
1246 Free resources allocated for fileinfoname service
1247 **************************************************************************/
free_fileinfo_data(void)1248 void free_fileinfo_data(void)
1249 {
1250 astr_free(&realfile);
1251 }
1252
1253 /**************************************************************************
1254 Destroys the file info structure.
1255 **************************************************************************/
fileinfo_destroy(struct fileinfo * pfile)1256 static void fileinfo_destroy(struct fileinfo *pfile)
1257 {
1258 free(pfile->name);
1259 free(pfile->fullname);
1260 free(pfile);
1261 }
1262
1263 /**************************************************************************
1264 Compare modification times.
1265 **************************************************************************/
compare_file_mtime_ptrs(const struct fileinfo * const * ppa,const struct fileinfo * const * ppb)1266 static int compare_file_mtime_ptrs(const struct fileinfo *const *ppa,
1267 const struct fileinfo *const *ppb)
1268 {
1269 time_t a = (*ppa)->mtime;
1270 time_t b = (*ppb)->mtime;
1271
1272 return ((a < b) ? 1 : (a > b) ? -1 : 0);
1273 }
1274
1275 /**************************************************************************
1276 Compare names.
1277 **************************************************************************/
compare_file_name_ptrs(const struct fileinfo * const * ppa,const struct fileinfo * const * ppb)1278 static int compare_file_name_ptrs(const struct fileinfo *const *ppa,
1279 const struct fileinfo *const *ppb)
1280 {
1281 return fc_strcoll((*ppa)->name, (*ppb)->name);
1282 }
1283
1284 /**************************************************************************
1285 Compare names.
1286 **************************************************************************/
compare_fileinfo_name(const struct fileinfo * pa,const struct fileinfo * pb)1287 static bool compare_fileinfo_name(const struct fileinfo *pa,
1288 const struct fileinfo *pb)
1289 {
1290 return 0 == fc_strcoll(pa->name, pb->name);
1291 }
1292
1293 /**************************************************************************
1294 Search for filenames with the "infix" substring in the "subpath"
1295 subdirectory of the data path.
1296 "nodups" removes duplicate names.
1297 The returned list will be sorted by name first and modification time
1298 second. Returned "name"s will be truncated starting at the "infix"
1299 substring. The returned list must be freed with fileinfo_list_destroy().
1300 **************************************************************************/
fileinfolist_infix(const struct strvec * dirs,const char * infix,bool nodups)1301 struct fileinfo_list *fileinfolist_infix(const struct strvec *dirs,
1302 const char *infix, bool nodups)
1303 {
1304 struct fileinfo_list *res;
1305
1306 if (NULL == dirs) {
1307 return NULL;
1308 }
1309
1310 res = fileinfo_list_new_full(fileinfo_destroy);
1311
1312 /* First assemble a full list of names. */
1313 strvec_iterate(dirs, dirname) {
1314 DIR *dir;
1315 struct dirent *entry;
1316
1317 /* Open the directory for reading. */
1318 dir = fc_opendir(dirname);
1319 if (!dir) {
1320 continue;
1321 }
1322
1323 /* Scan all entries in the directory. */
1324 while ((entry = readdir(dir))) {
1325 struct fileinfo *file;
1326 char *ptr;
1327 /* Strdup the entry so we can safely write to it. */
1328 char *filename = fc_strdup(entry->d_name);
1329
1330 /* Make sure the file name matches. */
1331 if ((ptr = strstr(filename, infix))) {
1332 struct stat buf;
1333 char *fullname;
1334 size_t len = strlen(dirname) + strlen(filename) + 2;
1335
1336 fullname = fc_malloc(len);
1337 fc_snprintf(fullname, len, "%s" DIR_SEPARATOR "%s", dirname, filename);
1338
1339 if (fc_stat(fullname, &buf) == 0) {
1340 file = fc_malloc(sizeof(*file));
1341
1342 /* Clip the suffix. */
1343 *ptr = '\0';
1344
1345 file->name = filename;
1346 file->fullname = fullname;
1347 file->mtime = buf.st_mtime;
1348
1349 fileinfo_list_append(res, file);
1350 } else {
1351 free(fullname);
1352 free(filename);
1353 }
1354 } else {
1355 free(filename);
1356 }
1357 }
1358
1359 closedir(dir);
1360 } strvec_iterate_end;
1361
1362 /* Sort the list by name. */
1363 fileinfo_list_sort(res, compare_file_name_ptrs);
1364
1365 if (nodups) {
1366 fileinfo_list_unique_full(res, compare_fileinfo_name);
1367 }
1368
1369 /* Sort the list by last modification time. */
1370 fileinfo_list_sort(res, compare_file_mtime_ptrs);
1371
1372 return res;
1373 }
1374
1375 /***************************************************************************
1376 Language environmental variable (with emulation).
1377 ***************************************************************************/
setup_langname(void)1378 char *setup_langname(void)
1379 {
1380 char *langname = NULL;
1381
1382 #ifdef ENABLE_NLS
1383 langname = getenv("LANG");
1384
1385 #ifdef WIN32_NATIVE
1386 /* set LANG by hand if it is not set */
1387 if (!langname) {
1388 switch (PRIMARYLANGID(GetUserDefaultLangID())) {
1389 case LANG_ARABIC:
1390 langname = "ar";
1391 break;
1392 case LANG_CATALAN:
1393 langname = "ca";
1394 break;
1395 case LANG_CZECH:
1396 langname = "cs";
1397 break;
1398 case LANG_DANISH:
1399 langname = "da";
1400 break;
1401 case LANG_GERMAN:
1402 langname = "de";
1403 break;
1404 case LANG_GREEK:
1405 langname = "el";
1406 break;
1407 case LANG_ENGLISH:
1408 switch (SUBLANGID(GetUserDefaultLangID())) {
1409 case SUBLANG_ENGLISH_UK:
1410 langname = "en_GB";
1411 break;
1412 default:
1413 langname = "en";
1414 break;
1415 }
1416 break;
1417 case LANG_SPANISH:
1418 langname = "es";
1419 break;
1420 case LANG_ESTONIAN:
1421 langname = "et";
1422 break;
1423 case LANG_FARSI:
1424 langname = "fa";
1425 break;
1426 case LANG_FINNISH:
1427 langname = "fi";
1428 break;
1429 case LANG_FRENCH:
1430 langname = "fr";
1431 break;
1432 case LANG_HEBREW:
1433 langname = "he";
1434 break;
1435 case LANG_HUNGARIAN:
1436 langname = "hu";
1437 break;
1438 case LANG_ITALIAN:
1439 langname = "it";
1440 break;
1441 case LANG_JAPANESE:
1442 langname = "ja";
1443 break;
1444 case LANG_KOREAN:
1445 langname = "ko";
1446 break;
1447 case LANG_LITHUANIAN:
1448 langname = "lt";
1449 break;
1450 case LANG_DUTCH:
1451 langname = "nl";
1452 break;
1453 case LANG_NORWEGIAN:
1454 langname = "nb";
1455 break;
1456 case LANG_POLISH:
1457 langname = "pl";
1458 break;
1459 case LANG_PORTUGUESE:
1460 switch (SUBLANGID(GetUserDefaultLangID())) {
1461 case SUBLANG_PORTUGUESE_BRAZILIAN:
1462 langname = "pt_BR";
1463 break;
1464 default:
1465 langname = "pt";
1466 break;
1467 }
1468 break;
1469 case LANG_ROMANIAN:
1470 langname = "ro";
1471 break;
1472 case LANG_RUSSIAN:
1473 langname = "ru";
1474 break;
1475 case LANG_SWEDISH:
1476 langname = "sv";
1477 break;
1478 case LANG_TURKISH:
1479 langname = "tr";
1480 break;
1481 case LANG_UKRAINIAN:
1482 langname = "uk";
1483 break;
1484 case LANG_CHINESE:
1485 langname = "zh_CN";
1486 break;
1487 }
1488
1489 if (langname != NULL) {
1490 static char envstr[40];
1491
1492 fc_snprintf(envstr, sizeof(envstr), "LANG=%s", langname);
1493 putenv(envstr);
1494 }
1495 }
1496 #endif /* WIN32_NATIVE */
1497 #endif /* ENABLE_NLS */
1498
1499 return langname;
1500 }
1501
1502 /***************************************************************************
1503 Setup for Native Language Support, if configured to use it.
1504 (Call this only once, or it may leak memory.)
1505 ***************************************************************************/
init_nls(void)1506 void init_nls(void)
1507 {
1508 /*
1509 * Setup the cached locale numeric formatting information. Defaults
1510 * are as appropriate for the US.
1511 */
1512 grouping = fc_strdup("\3");
1513 grouping_sep = fc_strdup(",");
1514
1515 #ifdef ENABLE_NLS
1516
1517 #ifdef WIN32_NATIVE
1518 setup_langname(); /* Makes sure LANG env variable has been set */
1519 #endif /* WIN32_NATIVE */
1520
1521 (void) setlocale(LC_ALL, "");
1522 (void) bindtextdomain(PACKAGE, get_locale_dir());
1523 (void) textdomain(PACKAGE);
1524
1525 /* Don't touch the defaults when LC_NUMERIC == "C".
1526 This is intended to cater to the common case where:
1527 1) The user is from North America. ;-)
1528 2) The user has not set the proper environment variables.
1529 (Most applications are (unfortunately) US-centric
1530 by default, so why bother?)
1531 This would result in the "C" locale being used, with grouping ""
1532 and thousands_sep "", where we really want "\3" and ",". */
1533
1534 if (strcmp(setlocale(LC_NUMERIC, NULL), "C") != 0) {
1535 struct lconv *lc = localeconv();
1536
1537 if (lc->grouping[0] == '\0') {
1538 /* This actually indicates no grouping at all. */
1539 char *m = malloc(sizeof(char));
1540 *m = CHAR_MAX;
1541 grouping = m;
1542 } else {
1543 size_t len;
1544 for (len = 0;
1545 lc->grouping[len] != '\0' && lc->grouping[len] != CHAR_MAX; len++) {
1546 /* nothing */
1547 }
1548 len++;
1549 free(grouping);
1550 grouping = fc_malloc(len);
1551 memcpy(grouping, lc->grouping, len);
1552 }
1553 free(grouping_sep);
1554 grouping_sep = fc_strdup(lc->thousands_sep);
1555 }
1556
1557 {
1558 char *autocap_opt_in[] = { "fi", NULL };
1559 int i;
1560 bool ac_enabled = FALSE;
1561
1562 char *lang = getenv("LANG");
1563
1564 if (lang != NULL && lang[0] != '\0' && lang[1] != '\0') {
1565 for (i = 0; autocap_opt_in[i] != NULL && !ac_enabled; i++) {
1566 if (lang[0] == autocap_opt_in[i][0]
1567 && lang[1] == autocap_opt_in[i][1]) {
1568 ac_enabled = TRUE;
1569 capitalization_opt_in();
1570 }
1571 }
1572 }
1573 }
1574
1575 #endif /* ENABLE_NLS */
1576 }
1577
1578 /***************************************************************************
1579 Free memory allocated by Native Language Support
1580 ***************************************************************************/
free_nls(void)1581 void free_nls(void)
1582 {
1583 free(grouping);
1584 grouping = NULL;
1585 free(grouping_sep);
1586 grouping_sep = NULL;
1587 }
1588
1589 /***************************************************************************
1590 If we have root privileges, die with an error.
1591 (Eg, for security reasons.)
1592 Param argv0 should be argv[0] or similar; fallback is
1593 used instead if argv0 is NULL.
1594 But don't die on systems where the user is always root...
1595 (a general test for this would be better).
1596 Doesn't use log_*() because gets called before logging is setup.
1597 ***************************************************************************/
dont_run_as_root(const char * argv0,const char * fallback)1598 void dont_run_as_root(const char *argv0, const char *fallback)
1599 {
1600 #if (defined(ALWAYS_ROOT) || defined(__EMX__) || defined(__BEOS__))
1601 return;
1602 #else
1603 if (getuid()==0 || geteuid()==0) {
1604 fc_fprintf(stderr,
1605 _("%s: Fatal error: you're trying to run me as superuser!\n"),
1606 (argv0 ? argv0 : fallback ? fallback : "freeciv"));
1607 fc_fprintf(stderr, _("Use a non-privileged account instead.\n"));
1608 exit(EXIT_FAILURE);
1609 }
1610 #endif /* ALWAYS_ROOT */
1611 }
1612
1613 /***************************************************************************
1614 Return a description string of the result.
1615 In English, form of description is suitable to substitute in, eg:
1616 prefix is <description>
1617 (N.B.: The description is always in English, but they have all been marked
1618 for translation. If you want a localized version, use _() on the return.)
1619 ***************************************************************************/
m_pre_description(enum m_pre_result result)1620 const char *m_pre_description(enum m_pre_result result)
1621 {
1622 static const char * const descriptions[] = {
1623 N_("exact match"),
1624 N_("only match"),
1625 N_("ambiguous"),
1626 N_("empty"),
1627 N_("too long"),
1628 N_("non-match")
1629 };
1630 fc_assert_ret_val(result >= 0 && result < ARRAY_SIZE(descriptions), NULL);
1631 return descriptions[result];
1632 }
1633
1634 /***************************************************************************
1635 See match_prefix_full().
1636 ***************************************************************************/
match_prefix(m_pre_accessor_fn_t accessor_fn,size_t n_names,size_t max_len_name,m_pre_strncmp_fn_t cmp_fn,m_strlen_fn_t len_fn,const char * prefix,int * ind_result)1637 enum m_pre_result match_prefix(m_pre_accessor_fn_t accessor_fn,
1638 size_t n_names,
1639 size_t max_len_name,
1640 m_pre_strncmp_fn_t cmp_fn,
1641 m_strlen_fn_t len_fn,
1642 const char *prefix,
1643 int *ind_result)
1644 {
1645 return match_prefix_full(accessor_fn, n_names, max_len_name, cmp_fn,
1646 len_fn, prefix, ind_result, NULL, 0, NULL);
1647 }
1648
1649 /***************************************************************************
1650 Given n names, with maximum length max_len_name, accessed by
1651 accessor_fn(0) to accessor_fn(n-1), look for matching prefix
1652 according to given comparison function.
1653 Returns type of match or fail, and for return <= M_PRE_AMBIGUOUS
1654 sets *ind_result with matching index (or for ambiguous, first match).
1655 If max_len_name==0, treat as no maximum.
1656 If the int array 'matches' is non-NULL, up to 'max_matches' ambiguous
1657 matching names indices will be inserted into it. If 'pnum_matches' is
1658 non-NULL, it will be set to the number of indices inserted into 'matches'.
1659 ***************************************************************************/
match_prefix_full(m_pre_accessor_fn_t accessor_fn,size_t n_names,size_t max_len_name,m_pre_strncmp_fn_t cmp_fn,m_strlen_fn_t len_fn,const char * prefix,int * ind_result,int * matches,int max_matches,int * pnum_matches)1660 enum m_pre_result match_prefix_full(m_pre_accessor_fn_t accessor_fn,
1661 size_t n_names,
1662 size_t max_len_name,
1663 m_pre_strncmp_fn_t cmp_fn,
1664 m_strlen_fn_t len_fn,
1665 const char *prefix,
1666 int *ind_result,
1667 int *matches,
1668 int max_matches,
1669 int *pnum_matches)
1670 {
1671 int i, len, nmatches;
1672
1673 if (len_fn == NULL) {
1674 len = strlen(prefix);
1675 } else {
1676 len = len_fn(prefix);
1677 }
1678 if (len == 0) {
1679 return M_PRE_EMPTY;
1680 }
1681 if (len > max_len_name && max_len_name > 0) {
1682 return M_PRE_LONG;
1683 }
1684
1685 nmatches = 0;
1686 for(i=0; i<n_names; i++) {
1687 const char *name = accessor_fn(i);
1688 if (cmp_fn(name, prefix, len)==0) {
1689 if (strlen(name) == len) {
1690 *ind_result = i;
1691 return M_PRE_EXACT;
1692 }
1693 if (nmatches==0) {
1694 *ind_result = i; /* first match */
1695 }
1696 if (matches != NULL && nmatches < max_matches) {
1697 matches[nmatches] = i;
1698 }
1699 nmatches++;
1700 }
1701 }
1702
1703 if (nmatches == 1) {
1704 return M_PRE_ONLY;
1705 } else if (nmatches > 1) {
1706 if (pnum_matches != NULL) {
1707 *pnum_matches = MIN(max_matches, nmatches);
1708 }
1709 return M_PRE_AMBIGUOUS;
1710 } else {
1711 return M_PRE_FAIL;
1712 }
1713 }
1714
1715 /***************************************************************************
1716 Returns string which gives the multicast group IP address for finding
1717 servers on the LAN, as specified by $FREECIV_MULTICAST_GROUP.
1718 Gets value once, and then caches result.
1719 ***************************************************************************/
get_multicast_group(bool ipv6_preferred)1720 char *get_multicast_group(bool ipv6_preferred)
1721 {
1722 static char *default_multicast_group_ipv4 = "225.1.1.1";
1723 #ifdef FREECIV_IPV6_SUPPORT
1724 /* TODO: Get useful group (this is node local) */
1725 static char *default_multicast_group_ipv6 = "FF31::8000:15B4";
1726 #endif /* IPv6 support */
1727
1728 if (mc_group == NULL) {
1729 char *env = getenv("FREECIV_MULTICAST_GROUP");
1730
1731 if (env) {
1732 mc_group = fc_strdup(env);
1733 } else {
1734 #ifdef FREECIV_IPV6_SUPPORT
1735 if (ipv6_preferred) {
1736 mc_group = fc_strdup(default_multicast_group_ipv6);
1737 } else
1738 #endif /* IPv6 support */
1739 {
1740 mc_group = fc_strdup(default_multicast_group_ipv4);
1741 }
1742 }
1743 }
1744
1745 return mc_group;
1746 }
1747
1748 /***************************************************************************
1749 Free multicast group resources
1750 ***************************************************************************/
free_multicast_group(void)1751 void free_multicast_group(void)
1752 {
1753 if (mc_group != NULL) {
1754 free(mc_group);
1755 mc_group = NULL;
1756 }
1757 }
1758
1759 /***************************************************************************
1760 Interpret ~/ in filename as home dir
1761 New path is returned in buf of size buf_size
1762
1763 This may fail if the path is too long. It is better to use
1764 interpret_tilde_alloc.
1765 ***************************************************************************/
interpret_tilde(char * buf,size_t buf_size,const char * filename)1766 void interpret_tilde(char* buf, size_t buf_size, const char* filename)
1767 {
1768 if (filename[0] == '~' && filename[1] == DIR_SEPARATOR_CHAR) {
1769 fc_snprintf(buf, buf_size, "%s" DIR_SEPARATOR "%s", user_home_dir(), filename + 2);
1770 } else if (filename[0] == '~' && filename[1] == '\0') {
1771 strncpy(buf, user_home_dir(), buf_size);
1772 } else {
1773 strncpy(buf, filename, buf_size);
1774 }
1775 }
1776
1777 /***************************************************************************
1778 Interpret ~/ in filename as home dir
1779
1780 The new path is returned in buf, as a newly allocated buffer. The new
1781 path will always be allocated and written, even if there is no ~ present.
1782 ***************************************************************************/
interpret_tilde_alloc(const char * filename)1783 char *interpret_tilde_alloc(const char* filename)
1784 {
1785 if (filename[0] == '~' && filename[1] == DIR_SEPARATOR_CHAR) {
1786 const char *home = user_home_dir();
1787 size_t sz;
1788 char *buf;
1789
1790 filename += 2; /* Skip past "~/" */
1791 sz = strlen(home) + strlen(filename) + 2;
1792 buf = fc_malloc(sz);
1793 fc_snprintf(buf, sz, "%s/%s", home, filename);
1794 return buf;
1795 } else if (filename[0] == '~' && filename[1] == '\0') {
1796 return fc_strdup(user_home_dir());
1797 } else {
1798 return fc_strdup(filename);
1799 }
1800 }
1801
1802 /**************************************************************************
1803 Return a pointer to the start of the file basename in filepath.
1804 If the string contains no dir separator, it is returned itself.
1805 **************************************************************************/
skip_to_basename(char * filepath)1806 char *skip_to_basename(char *filepath)
1807 {
1808 int j;
1809 fc_assert_ret_val(NULL != filepath, NULL);
1810
1811 for (j = strlen(filepath); j >= 0; j--) {
1812 if (filepath[j] == DIR_SEPARATOR_CHAR) {
1813 return &filepath[j+1];
1814 }
1815 }
1816 return filepath;
1817 }
1818
1819 /**************************************************************************
1820 If the directory "pathname" does not exist, recursively create all
1821 directories until it does.
1822 **************************************************************************/
make_dir(const char * pathname)1823 bool make_dir(const char *pathname)
1824 {
1825 char *dir;
1826 char *path = NULL;
1827
1828 path = interpret_tilde_alloc(pathname);
1829 dir = path;
1830 do {
1831 dir = strchr(dir, DIR_SEPARATOR_CHAR);
1832 /* We set the current / with 0, and restore it afterwards */
1833 if (dir) {
1834 *dir = '\0';
1835 }
1836
1837 #ifdef WIN32_NATIVE
1838 #ifdef HAVE__MKDIR
1839 /* Prefer _mkdir() in Windows even if mkdir() would seem to be available -
1840 * chances are that it's wrong kind of mkdir().
1841 * TODO: Make a configure check for mkdir() that also makes sure that it
1842 * takes two parameters, and prefer such proper mkdir() here. */
1843 {
1844 char *path_in_local_encoding = internal_to_local_string_malloc(path);
1845
1846 _mkdir(path_in_local_encoding);
1847 free(path_in_local_encoding);
1848 }
1849 #else /* HAVE__MKDIR */
1850 mkdir(path, 0755);
1851 #endif /* HAVE__MKDIR */
1852 #else /* WIN32_NATIVE */
1853 mkdir(path, 0755);
1854 #endif /* WIN32_NATIVE */
1855
1856 if (dir) {
1857 *dir = DIR_SEPARATOR_CHAR;
1858 dir++;
1859 }
1860 } while (dir);
1861
1862 free(path);
1863 return TRUE;
1864 }
1865
1866 /**************************************************************************
1867 Returns TRUE if the filename's path is absolute.
1868 **************************************************************************/
path_is_absolute(const char * filename)1869 bool path_is_absolute(const char *filename)
1870 {
1871 if (!filename) {
1872 return FALSE;
1873 }
1874
1875 #ifdef WIN32_NATIVE
1876 if (strchr(filename, ':')) {
1877 return TRUE;
1878 }
1879 #else /* WIN32_NATIVE */
1880 if (filename[0] == '/') {
1881 return TRUE;
1882 }
1883 #endif /* WIN32_NATIVE */
1884
1885 return FALSE;
1886 }
1887
1888 /**************************************************************************
1889 Scan in a word or set of words from start to but not including
1890 any of the given delimiters. The buf pointer will point past delimiter,
1891 or be set to NULL if there is nothing there. Removes excess white
1892 space.
1893
1894 This function should be safe, and dest will contain "\0" and
1895 *buf == NULL on failure. We always fail gently.
1896
1897 Due to the way the scanning is performed, looking for a space
1898 will give you the first word even if it comes before multiple
1899 spaces.
1900
1901 Returns delimiter found.
1902
1903 Pass in NULL for dest and -1 for size to just skip ahead. Note that if
1904 nothing is found, dest will contain the whole string, minus leading and
1905 trailing whitespace. You can scan for "" to conveniently grab the
1906 remainder of a string.
1907 **************************************************************************/
scanin(const char ** buf,char * delimiters,char * dest,int size)1908 char scanin(const char **buf, char *delimiters, char *dest, int size)
1909 {
1910 char *ptr, found = '?';
1911
1912 if (*buf == NULL || strlen(*buf) == 0 || size == 0) {
1913 if (dest) {
1914 dest[0] = '\0';
1915 }
1916 *buf = NULL;
1917 return '\0';
1918 }
1919
1920 if (dest) {
1921 strncpy(dest, *buf, size-1);
1922 dest[size-1] = '\0';
1923 remove_leading_trailing_spaces(dest);
1924 ptr = strpbrk(dest, delimiters);
1925 } else {
1926 /* Just skip ahead. */
1927 ptr = strpbrk(*buf, delimiters);
1928 }
1929 if (ptr != NULL) {
1930 found = *ptr;
1931 if (dest) {
1932 *ptr = '\0';
1933 }
1934 if (dest) {
1935 remove_leading_trailing_spaces(dest);
1936 }
1937 *buf = strpbrk(*buf, delimiters);
1938 if (*buf != NULL) {
1939 (*buf)++; /* skip delimiter */
1940 } else {
1941 }
1942 } else {
1943 *buf = NULL;
1944 }
1945
1946 return found;
1947 }
1948
1949 /**************************************************************************
1950 Convenience function to nicely format a time_t seconds value in to a
1951 string with hours, minutes, etc.
1952 **************************************************************************/
format_time_duration(time_t t,char * buf,int maxlen)1953 void format_time_duration(time_t t, char *buf, int maxlen)
1954 {
1955 int seconds, minutes, hours, days;
1956 bool space = FALSE;
1957
1958 seconds = t % 60;
1959 minutes = (t / 60) % 60;
1960 hours = (t / (60 * 60)) % 24;
1961 days = t / (60 * 60 * 24);
1962
1963 if (maxlen <= 0) {
1964 return;
1965 }
1966
1967 buf[0] = '\0';
1968
1969 if (days > 0) {
1970 cat_snprintf(buf, maxlen, "%d %s", days, PL_("day", "days", days));
1971 space = TRUE;
1972 }
1973 if (hours > 0) {
1974 cat_snprintf(buf, maxlen, "%s%d %s",
1975 space ? " " : "", hours, PL_("hour", "hours", hours));
1976 space = TRUE;
1977 }
1978 if (minutes > 0) {
1979 cat_snprintf(buf, maxlen, "%s%d %s",
1980 space ? " " : "",
1981 minutes, PL_("minute", "minutes", minutes));
1982 space = TRUE;
1983 }
1984 if (seconds > 0) {
1985 cat_snprintf(buf, maxlen, "%s%d %s",
1986 space ? " " : "",
1987 seconds, PL_("second", "seconds", seconds));
1988 }
1989 }
1990
1991 /************************************************************************
1992 Randomize the elements of an array using the Fisher-Yates shuffle.
1993
1994 see: http://benpfaff.org/writings/clc/shuffle.html
1995 ************************************************************************/
array_shuffle(int * array,int n)1996 void array_shuffle(int *array, int n)
1997 {
1998 if (n > 1 && array != NULL) {
1999 int i, j, t;
2000 for (i = 0; i < n - 1; i++) {
2001 j = i + fc_rand(n - i);
2002 t = array[j];
2003 array[j] = array[i];
2004 array[i] = t;
2005 }
2006 }
2007 }
2008
2009 /****************************************************************************
2010 Test an asterisk in the pattern against test. Returns TRUE if test fit the
2011 pattern. May be recursive, as it calls wildcard_fit_string() itself (if
2012 many asterisks).
2013 ****************************************************************************/
wildcard_asterisk_fit(const char * pattern,const char * test)2014 static bool wildcard_asterisk_fit(const char *pattern, const char *test)
2015 {
2016 char jump_to;
2017
2018 /* Jump over the leading asterisks. */
2019 pattern++;
2020 while (TRUE) {
2021 switch (*pattern) {
2022 case '\0':
2023 /* It is a leading asterisk. */
2024 return TRUE;
2025 case '*':
2026 pattern++;
2027 continue;
2028 case '?':
2029 if ('\0' == *test) {
2030 return FALSE;
2031 }
2032 test++;
2033 pattern++;
2034 continue;
2035 }
2036
2037 break;
2038 }
2039
2040 if ('[' != *pattern) {
2041 if ('\\' == *pattern) {
2042 jump_to = *(pattern + 1);
2043 } else {
2044 jump_to = *pattern;
2045 }
2046 } else {
2047 jump_to = '\0';
2048 }
2049
2050 while ('\0' != *test) {
2051 if ('\0' != jump_to) {
2052 /* Jump to next matching charather. */
2053 test = strchr(test, jump_to);
2054 if (NULL == test) {
2055 /* No match. */
2056 return FALSE;
2057 }
2058 }
2059
2060 if (wildcard_fit_string(pattern, test)) {
2061 return TRUE;
2062 }
2063
2064 (test)++;
2065 }
2066
2067 return FALSE;
2068 }
2069
2070 /****************************************************************************
2071 Test a range in the pattern against test. Returns TRUE if **test fit the
2072 first range in *pattern.
2073 ****************************************************************************/
wildcard_range_fit(const char ** pattern,const char ** test)2074 static bool wildcard_range_fit(const char **pattern, const char **test)
2075 {
2076 const char *start = (*pattern + 1);
2077 char testc;
2078 bool negation;
2079
2080 if ('\0' == **test) {
2081 /* Need one character. */
2082 return FALSE;
2083 }
2084
2085 /* Find the end of the pattern. */
2086 while (TRUE) {
2087 *pattern = strchr(*pattern, ']');
2088 if (NULL == *pattern) {
2089 /* Wildcard format error. */
2090 return FALSE;
2091 } else if (*(*pattern - 1) != '\\') {
2092 /* This is the end. */
2093 break;
2094 } else {
2095 /* Try again. */
2096 (*pattern)++;
2097 }
2098 }
2099
2100 if ('!' == *start) {
2101 negation = TRUE;
2102 start++;
2103 } else {
2104 negation = FALSE;
2105 }
2106 testc = **test;
2107 (*test)++;
2108 (*pattern)++;
2109
2110 for (; start < *pattern; start++) {
2111 if ('-' == *start || '!' == *start) {
2112 /* Wildcard format error. */
2113 return FALSE;
2114 } else if (start < *pattern - 2 && '-' == *(start + 1)) {
2115 /* Case range. */
2116 if (*start <= testc && testc <= *(start + 2)) {
2117 return !negation;
2118 }
2119 start += 2;
2120 } else if (*start == testc) {
2121 /* Single character. */
2122 return !negation;
2123 }
2124 }
2125
2126 return negation;
2127 }
2128
2129 /****************************************************************************
2130 Returns TRUE if test fit the pattern. The pattern can contain special
2131 characters:
2132 * '*': to specify a substitute for any zero or more characters.
2133 * '?': to specify a substitute for any one character.
2134 * '[...]': to specify a range of characters:
2135 * '!': at the begenning of the range means that the matching result
2136 will be inverted
2137 * 'A-Z': means any character between 'A' and 'Z'.
2138 * 'agr': means 'a', 'g' or 'r'.
2139 ****************************************************************************/
wildcard_fit_string(const char * pattern,const char * test)2140 bool wildcard_fit_string(const char *pattern, const char *test)
2141 {
2142 while (TRUE) {
2143 switch (*pattern) {
2144 case '\0':
2145 /* '\0' != test. */
2146 return '\0' == *test;
2147 case '*':
2148 return wildcard_asterisk_fit(pattern, test); /* Maybe recursive. */
2149 case '[':
2150 if (!wildcard_range_fit(&pattern, &test)) {
2151 return FALSE;
2152 }
2153 continue;
2154 case '?':
2155 if ('\0' == *test) {
2156 return FALSE;
2157 }
2158 break;
2159 case '\\':
2160 pattern++;
2161 fc__fallthrough; /* No break */
2162 default:
2163 if (*pattern != *test) {
2164 return FALSE;
2165 }
2166 break;
2167 }
2168 pattern++;
2169 test++;
2170 }
2171
2172 return FALSE;
2173 }
2174
2175 /****************************************************************************
2176 Print a string with a custom format. sequences is a pointer to an array of
2177 sequences, probably defined with CF_*_SEQ(). sequences_num is the number of
2178 the sequences, or -1 in the case the array is terminated with CF_END.
2179
2180 Example:
2181 static const struct cf_sequence sequences[] = {
2182 CF_INT_SEQ('y', 2010)
2183 };
2184 char buf[256];
2185
2186 fc_vsnprintcf(buf, sizeof(buf), "%y %+06y", sequences, 1);
2187 // This will print "2010 +02010" into buf.
2188 ****************************************************************************/
fc_vsnprintcf(char * buf,size_t buf_len,const char * format,const struct cf_sequence * sequences,size_t sequences_num)2189 int fc_vsnprintcf(char *buf, size_t buf_len, const char *format,
2190 const struct cf_sequence *sequences, size_t sequences_num)
2191 {
2192 const struct cf_sequence *pseq;
2193 char cformat[32];
2194 const char *f = format;
2195 char *const max = buf + buf_len - 1;
2196 char *b = buf, *c;
2197 const char *const cmax = cformat + sizeof(cformat) - 2;
2198 int i, j;
2199
2200 if ((size_t) -1 == sequences_num) {
2201 /* Find the number of sequences. */
2202 sequences_num = 0;
2203 for (pseq = sequences; CF_LAST != pseq->type; pseq++) {
2204 sequences_num++;
2205 }
2206 }
2207
2208 while ('\0' != *f) {
2209 if ('%' == *f) {
2210 /* Sequence. */
2211
2212 f++;
2213 if ('%' == *f) {
2214 /* Double '%'. */
2215 *b++ = '%';
2216 f++;
2217 continue;
2218 }
2219
2220 /* Make format. */
2221 c = cformat;
2222 *c++ = '%';
2223 for (; !fc_isalpha(*f) && '\0' != *f && '%' != *f && cmax > c; f++) {
2224 *c++ = *f;
2225 }
2226
2227 if (!fc_isalpha(*f)) {
2228 /* Beginning of a new sequence, end of the format, or too long
2229 * sequence. */
2230 *c = '\0';
2231 j = fc_snprintf(b, max - b + 1, "%s", cformat);
2232 if (-1 == j) {
2233 return -1;
2234 }
2235 b += j;
2236 continue;
2237 }
2238
2239 for (i = 0, pseq = sequences; i < sequences_num; i++, pseq++) {
2240 if (pseq->letter == *f) {
2241 j = -2;
2242 switch (pseq->type) {
2243 case CF_BOOLEAN:
2244 *c++ = 's';
2245 *c = '\0';
2246 j = fc_snprintf(b, max - b + 1, cformat,
2247 pseq->bool_value ? "TRUE" : "FALSE");
2248 break;
2249 case CF_TRANS_BOOLEAN:
2250 *c++ = 's';
2251 *c = '\0';
2252 j = fc_snprintf(b, max - b + 1, cformat,
2253 pseq->bool_value ? _("TRUE") : _("FALSE"));
2254 break;
2255 case CF_CHARACTER:
2256 *c++ = 'c';
2257 *c = '\0';
2258 j = fc_snprintf(b, max - b + 1, cformat, pseq->char_value);
2259 break;
2260 case CF_INTEGER:
2261 *c++ = 'd';
2262 *c = '\0';
2263 j = fc_snprintf(b, max - b + 1, cformat, pseq->int_value);
2264 break;
2265 case CF_HEXA:
2266 *c++ = 'x';
2267 *c = '\0';
2268 j = fc_snprintf(b, max - b + 1, cformat, pseq->int_value);
2269 break;
2270 case CF_FLOAT:
2271 *c++ = 'f';
2272 *c = '\0';
2273 j = fc_snprintf(b, max - b + 1, cformat, pseq->float_value);
2274 break;
2275 case CF_POINTER:
2276 *c++ = 'p';
2277 *c = '\0';
2278 j = fc_snprintf(b, max - b + 1, cformat, pseq->ptr_value);
2279 break;
2280 case CF_STRING:
2281 *c++ = 's';
2282 *c = '\0';
2283 j = fc_snprintf(b, max - b + 1, cformat, pseq->str_value);
2284 break;
2285 case CF_LAST:
2286 break;
2287 };
2288 if (-2 == j) {
2289 log_error("Error: unsupported sequence type: %d.", pseq->type);
2290 break;
2291 }
2292 if (-1 == j) {
2293 /* Full! */
2294 return -1;
2295 }
2296 f++;
2297 b += j;
2298 break;
2299 }
2300 }
2301 if (i >= sequences_num) {
2302 /* Format not supported. */
2303 *c = '\0';
2304 j = fc_snprintf(b, max - b + 1, "%s%c", cformat, *f);
2305 if (-1 == j) {
2306 return -1;
2307 }
2308 f++;
2309 b += j;
2310 }
2311 } else {
2312 /* Not a sequence. */
2313 *b++ = *f++;
2314 }
2315 if (max <= b) {
2316 /* Too long. */
2317 *max = '\0';
2318 return -1;
2319 }
2320 }
2321 *b = '\0';
2322 return b - buf;
2323 }
2324
2325 /****************************************************************************
2326 Print a string with a custom format. The additional arguments are a suite
2327 of cf_*_seq() finished by cf_end(). This return the number of printed
2328 characters (excluding the last '\0') or -1 if the buffer is full.
2329
2330 Example:
2331 char buf[256];
2332
2333 fc_snprintcf(buf, sizeof(buf), "%y %+06y",
2334 cf_int_seq('y', 2010), cf_end());
2335 // This will print "2010 +02010" into buf.
2336 ****************************************************************************/
fc_snprintcf(char * buf,size_t buf_len,const char * format,...)2337 int fc_snprintcf(char *buf, size_t buf_len, const char *format, ...)
2338 {
2339 struct cf_sequence sequences[16];
2340 size_t sequences_num = 0;
2341 va_list args;
2342
2343 /* Collect sequence array. */
2344 va_start(args, format);
2345 do {
2346 sequences[sequences_num] = va_arg(args, struct cf_sequence);
2347 if (CF_LAST == sequences[sequences_num].type) {
2348 break;
2349 } else {
2350 sequences_num++;
2351 }
2352 } while (ARRAY_SIZE(sequences) > sequences_num);
2353
2354 if (ARRAY_SIZE(sequences) <= sequences_num
2355 && CF_LAST != va_arg(args, struct cf_sequence).type) {
2356 log_error("Too many custom sequences. Maybe did you forget cf_end() "
2357 "at the end of the arguments?");
2358 buf[0] = '\0';
2359 va_end(args);
2360 return -1;
2361 }
2362 va_end(args);
2363
2364 return fc_vsnprintcf(buf, buf_len, format, sequences, sequences_num);
2365 }
2366
2367 /****************************************************************************
2368 Extract the sequences of a format. Returns the number of extracted
2369 escapes.
2370 ****************************************************************************/
extract_escapes(const char * format,char * escapes,size_t max_escapes)2371 static size_t extract_escapes(const char *format, char *escapes,
2372 size_t max_escapes)
2373 {
2374 static const char format_escapes[] = {
2375 '*', 'd', 'i', 'o', 'u', 'x', 'X', 'e', 'E', 'f',
2376 'F', 'g', 'G', 'a', 'A', 'c', 's', 'p', 'n', '\0'
2377 };
2378 bool reordered = FALSE;
2379 size_t num = 0;
2380 int idx = 0;
2381
2382 memset(escapes, 0, max_escapes);
2383 format = strchr(format, '%');
2384 while (NULL != format) {
2385 format++;
2386 if ('%' == *format) {
2387 /* Double, not a sequence. */
2388 continue;
2389 } else if (fc_isdigit(*format)) {
2390 const char *start = format;
2391
2392 do {
2393 format++;
2394 } while (fc_isdigit(*format));
2395 if ('$' == *format) {
2396 /* Strings are reordered. */
2397 sscanf(start, "%d", &idx);
2398 reordered = TRUE;
2399 }
2400 }
2401
2402 while ('\0' != *format
2403 && NULL == strchr(format_escapes, *format)) {
2404 format++;
2405 }
2406 escapes[idx] = *format;
2407
2408 /* Increase the read count. */
2409 if (reordered) {
2410 if (idx > num) {
2411 num = idx;
2412 }
2413 } else {
2414 idx++;
2415 num++;
2416 }
2417
2418 if ('*' != *format) {
2419 format = strchr(format, '%');
2420 } /* else we didn't have found the real sequence. */
2421 }
2422 return num;
2423 }
2424
2425 /****************************************************************************
2426 Returns TRUE iff both formats are compatible (if 'format1' can be used
2427 instead 'format2' and reciprocally).
2428 ****************************************************************************/
formats_match(const char * format1,const char * format2)2429 bool formats_match(const char *format1, const char *format2)
2430 {
2431 char format1_escapes[256], format2_escapes[256];
2432 size_t format1_escapes_num = extract_escapes(format1, format1_escapes,
2433 sizeof(format1_escapes));
2434 size_t format2_escapes_num = extract_escapes(format2, format2_escapes,
2435 sizeof(format2_escapes));
2436
2437 return (format1_escapes_num == format2_escapes_num
2438 && 0 == memcmp(format1_escapes, format2_escapes,
2439 format1_escapes_num));
2440 }
2441