1 /* XQF - Quake server browser and launcher
2 * Copyright (C) 1998-2000 Roman Pozlevich <roma@botik.ru>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17 */
18
19 #include "gnuconfig.h"
20
21 #include <sys/types.h> /* getpwnam, readdir, dirent */
22 #include <stdio.h> /* FILE, putc */
23 #include <string.h> /* strlen, strncpy, strcmp, strspn, strcspn, strchr */
24 #include <ctype.h> /* isspace */
25 #include <pwd.h> /* getpwnam */
26 #include <stdlib.h> /* strtol */
27 #include <dirent.h> /* readdir, dirent */
28 #include <signal.h> /* sigaction, sigemptyset */
29 #include <ctype.h> /* tolower */
30 #include <sys/stat.h> /* stat */
31 #include <unistd.h> /* access */
32 #include <fcntl.h> /* fcntl */
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36
37
38 #include <gtk/gtk.h>
39
40 #include "utils.h"
41 #include "debug.h"
42 #include "i18n.h"
43
44 static char* _find_file_in_path(const char* files, gboolean relative);
45 static char* _find_file_in_path_list(char** binaries, gboolean relative);
46
strtosh(const char * str)47 short strtosh (const char *str) {
48 long tmp;
49
50 tmp = strtol (str, NULL, 10);
51 return CLAMP (tmp, G_MINSHORT, G_MAXSHORT);
52 }
53
54
strtoush(const char * str)55 unsigned short strtoush (const char *str) {
56 long tmp;
57
58 tmp = strtol (str, NULL, 10);
59 return CLAMP (tmp + G_MINSHORT, G_MINSHORT, G_MAXSHORT) - G_MINSHORT;
60 }
61
62
strdup_strip(const char * str)63 char *strdup_strip (const char *str) {
64 const char *start;
65 const char *end;
66 char *res;
67
68 if (!str) {
69 return NULL;
70 }
71
72 for (start = str; *start && isspace (*start); start++);
73
74 if (!strlen(str)) {
75 return NULL;
76 }
77
78 for (end = str + strlen (str) - 1; end >= start && isspace (*end); end--);
79
80 if (start > end) {
81 return NULL;
82 }
83
84 res = g_malloc (end - start + 1 + 1);
85 strncpy (res, start, end - start + 1);
86 res[end - start + 1] = '\0';
87
88 return res;
89 }
90
91
92 // concatenate dir and file, insert slash if necessary
93 // returned string must be freed manually
file_in_dir(const char * dir,const char * file)94 char *file_in_dir (const char *dir, const char *file) {
95 char *res, *tmp;
96 int need_slash = 0;
97
98 if (!dir || dir[0] == '\0') /* dir "" is current dir */
99 return (file)? g_strdup (file) : NULL;
100
101 if (!file) {
102 return g_strdup (dir);
103 }
104
105 if (dir[strlen (dir) - 1] != G_DIR_SEPARATOR) {
106 need_slash = 1;
107 }
108
109 tmp = res = g_malloc0 (strlen (dir) + strlen (file) + need_slash + 1);
110
111 strcpy (tmp, dir);
112 tmp += strlen (dir);
113 if (need_slash) {
114 *tmp++ = G_DIR_SEPARATOR;
115 }
116
117 strcpy (tmp, file);
118
119 return res;
120 }
121
122
str_isempty(const char * str)123 int str_isempty (const char *str) {
124 if (!str) {
125 return TRUE;
126 }
127
128 for (; *str; str++) {
129 if (!isspace (*str)) {
130 return FALSE;
131 }
132 }
133
134 return TRUE;
135 }
136
137
138 #define MAXNAMELEN 256
139
expand_tilde(const char * path)140 char *expand_tilde (const char *path) {
141 char *res = NULL;
142 char *slash;
143 char name[MAXNAMELEN];
144 int namelen;
145 struct passwd *pwd;
146
147 if (path) {
148 if (path[0] == '~') {
149 if (path[1] == '\0') {
150 res = g_strdup (g_get_home_dir ());
151 }
152 else if (path[1] == G_DIR_SEPARATOR) {
153 res = file_in_dir (g_get_home_dir (), path + 2);
154 }
155 else {
156 slash = strchr (&path[1], G_DIR_SEPARATOR);
157 if (slash) {
158 namelen = slash - &path[1];
159 }
160 else {
161 namelen = strlen (&path[1]);
162 }
163
164 if (namelen > MAXNAMELEN) {
165 namelen = MAXNAMELEN - 1;
166 }
167
168 strncpy (name, &path[1], namelen);
169 name[namelen] = '\0';
170
171 pwd = getpwnam (name);
172 if (pwd) {
173 res = file_in_dir (pwd->pw_dir, path + 2 + namelen);
174 }
175 else {
176 res = g_strdup (path);
177 }
178 }
179 }
180 else {
181 res = g_strdup (path);
182 }
183 }
184
185 #ifdef DEBUG
186 fprintf (stderr, "Tilde expansion: %s -> %s\n", path, res);
187 #endif
188
189 return res;
190 }
191
192
193 /*
194 * Directory Scaning
195 */
196
197
dir_to_list(const char * dirname,char * (* filter)(const char *,const char *))198 GList *dir_to_list (const char *dirname,
199 char * (*filter) (const char *, const char *)) {
200 DIR *directory;
201 struct dirent *dirent_ptr;
202 GList *list = NULL;
203 char *str;
204
205 if (!dirname) {
206 return NULL;
207 }
208
209 directory = opendir (dirname);
210 if (directory == NULL) {
211 return NULL;
212 }
213
214 while ((dirent_ptr = readdir (directory)) != NULL) {
215 if (filter) {
216 str = filter (dirname, dirent_ptr->d_name);
217 }
218 else {
219 str = g_strdup (dirent_ptr->d_name);
220 }
221
222 if (str) {
223 list = g_list_prepend (list, str);
224 }
225 }
226
227 closedir (directory);
228
229 return g_list_sort (list, (GCompareFunc) strcmp);
230 }
231
232
233 /*
234 * Lists
235 */
236
237
merge_sorted_string_lists(GList * list1,GList * list2)238 GList *merge_sorted_string_lists (GList *list1, GList *list2) {
239 GList *res = NULL;
240 GList *ptr1;
241 GList *ptr2;
242 int cmpres;
243
244 ptr1 = list1;
245 ptr2 = list2;
246
247 while (ptr1 && ptr2) {
248 cmpres = strcmp ((char *) ptr1->data, (char *) ptr2->data);
249
250 if (cmpres == 0) {
251 res = g_list_prepend (res, ptr1->data);
252 g_free (ptr2->data);
253 ptr1 = ptr1->next;
254 ptr2 = ptr2->next;
255 } else {
256 if (cmpres < 0) {
257 res = g_list_prepend (res, ptr1->data);
258 ptr1 = ptr1->next;
259 }
260 else {
261 res = g_list_prepend (res, ptr2->data);
262 ptr2 = ptr2->next;
263 }
264 }
265 }
266
267 while (ptr1) {
268 res = g_list_prepend (res, ptr1->data);
269 ptr1 = ptr1->next;
270 }
271
272 while (ptr2) {
273 res = g_list_prepend (res, ptr2->data);
274 ptr2 = ptr2->next;
275 }
276
277 res = g_list_reverse (res);
278 g_list_free (list1);
279 g_list_free (list2);
280 return res;
281 }
282
283
unique_strings(GSList * strings)284 GSList *unique_strings (GSList *strings) {
285 GSList *result = NULL;
286 GSList *tmp;
287
288 while (strings) {
289 for (tmp = result; tmp; tmp = tmp->next) {
290 if (strcmp ((char *) tmp->data, (char *) strings->data)) {
291 result = g_slist_prepend (result, strings->data);
292 }
293 }
294 strings = strings->next;
295 }
296
297 return result;
298 }
299
300 // build GList from array of char*
createGListfromchar(char * strings[])301 GList* createGListfromchar(char* strings[]) {
302 GList *list = NULL;
303 char** ptr = NULL;
304 for (ptr=strings;ptr&&*ptr;ptr++) {
305 list = g_list_append (list, *ptr);
306 }
307
308 return list;
309 }
310
311
312 /*
313 * Signals
314 */
315
316
on_sig(int signum,void (* func)(int signum))317 void on_sig (int signum, void (*func) (int signum)) {
318 struct sigaction action;
319
320 action.sa_handler = func;
321 sigemptyset (&action.sa_mask);
322 action.sa_flags = (signum == SIGCHLD)?
323 SA_RESTART | SA_NOCLDSTOP : SA_RESTART;
324 sigaction (signum, &action, NULL);
325 }
326
327
ignore_sigpipe(void)328 void ignore_sigpipe (void) {
329 on_sig (SIGPIPE, SIG_IGN);
330 }
331
332
333 /*
334 * String Output
335 */
336
print_dq_string(FILE * f,const char * ptr)337 void print_dq_string (FILE *f, const char *ptr) {
338
339 if (!f) {
340 return;
341 }
342
343 putc ('\"', f);
344
345 while (ptr && *ptr) {
346 switch (*ptr) {
347
348 case '\n':
349 putc ('\\', f); putc ('n', f);
350 break;
351
352 case '\t':
353 putc ('\\', f); putc ('t', f);
354 break;
355
356 case '\r':
357 putc ('\\', f); putc ('r', f);
358 break;
359
360 case '\b':
361 putc ('\\', f); putc ('b', f);
362 break;
363
364 case '\f':
365 putc ('\\', f); putc ('f', f);
366 break;
367
368 case '\\':
369 case '\'':
370 case '\"':
371 putc ('\\', f); putc (*ptr, f);
372 break;
373
374 default:
375 if (*ptr >= 0x20 && *ptr != 0x7F) {
376 putc (*ptr, f);
377 }
378 else {
379 fprintf (f, "\\%03o", *ptr);
380 }
381 break;
382
383 }
384
385 ptr++;
386 }
387
388 putc ('\"', f);
389 }
390
391
lowcasestrstr(const char * str,const char * substr)392 char *lowcasestrstr (const char *str, const char *substr) {
393 int slen;
394 int sublen;
395 const char *end;
396 int i;
397
398 if (!str || !substr) {
399 return NULL;
400 }
401
402 slen = strlen (str);
403 sublen = strlen (substr);
404
405 if (slen < sublen) {
406 return NULL;
407 }
408
409 end = &str[slen - sublen + 1];
410
411 while (str < end) {
412 for (i = 0; i < sublen; i++) {
413 if (tolower (substr[i]) != tolower (str[i])) {
414 goto loop;
415 }
416 }
417 return (char *) str;
418
419 loop:
420 str++;
421 }
422
423 return NULL;
424 }
425
426
427 /*
428 * Parse string to tokens
429 */
430
tokenize(char * str,char * token[],int max,const char * dlm)431 int tokenize (char *str, char *token[], int max, const char *dlm) {
432 int num = 0;
433
434 while (str && num < max) {
435 str += strspn (str, dlm);
436
437 if (*str == '\0') {
438 break;
439 }
440
441 token[num++] = str;
442
443 if (num == max) {
444 break;
445 }
446
447 str += strcspn (str, dlm);
448
449 if (*str == '\0') {
450 break;
451 }
452
453 *str++ = '\0';
454 }
455
456 return num;
457 }
458
459
safe_tokenize(const char * str,char * token[],int max,const char * dlm)460 int safe_tokenize (const char *str, char *token[], int max, const char *dlm) {
461 int num = 0;
462
463 while (str && num < max) {
464 str += strspn (str, dlm);
465
466 if (*str == '\0') {
467 break;
468 }
469
470 token[num++] = (char *) str;
471 str += strcspn (str, dlm);
472 }
473
474 return num;
475 }
476
477
tokenize_bychar(char * str,char * token[],int max,char dlm)478 int tokenize_bychar (char *str, char *token[], int max, char dlm) {
479 int num = 0;
480
481 if (!str || str[0] == '\0') {
482 return 0;
483 }
484
485 while (str && num < max) {
486 token[num++] = str;
487 str = strchr (str, dlm);
488 if (str) {
489 *str++ = '\0';
490 }
491 }
492
493 return num;
494 }
495
496
hostname_is_valid(const char * hostname)497 int hostname_is_valid (const char *hostname) {
498 const char *ptr;
499
500 if (!hostname) {
501 return FALSE;
502 }
503
504 for (ptr = hostname; *ptr; ptr++) {
505 if (*ptr != '.' && *ptr != '-' &&
506 (*ptr < '0' || *ptr > '9') &&
507 (*ptr < 'a' || *ptr > 'z') &&
508 (*ptr < 'A' || *ptr > 'Z')) {
509 #ifdef DEBUG
510 fprintf (stderr, "Host name is not valid: %s\n", hostname);
511 #endif
512 return FALSE;
513 }
514 }
515 return TRUE;
516 }
517
518
find_server_setting_for_key(char * key,char ** info_ptr)519 char* find_server_setting_for_key (char *key, char **info_ptr){
520 char **ptr;
521
522 if (key == NULL || info_ptr == NULL) {
523 return (NULL);
524 }
525
526 for (ptr = info_ptr; ptr && *ptr; ptr += 2) {
527 if (strcasecmp (*ptr, key) == 0){
528 debug(3, "find_server_setting_for_key() -- Found key '%s' with value '%s'", key, *(ptr+1));
529 return (*(ptr+1));
530 } else {
531 debug(8, "Key '%s' w/val '%s' did not match '%s'", *ptr, *(ptr+1), key);
532 }
533 }
534 return (NULL);
535 }
536
537 // returns true if str is "true", false otherwise
str2bool(const char * str)538 int str2bool(const char* str) {
539 if (!str) {
540 return FALSE;
541 }
542
543 if (!strcasecmp(str,"true")) {
544 return TRUE;
545 }
546
547 return FALSE;
548 }
549
550 // return "false" if i == 0, "true" otherwise
bool2str(int i)551 const char* bool2str(int i) {
552 if (!i) {
553 return "false";
554 }
555 return "true";
556 }
557
558 /** find executable file in $PATH. file is a colon seperated list of
559 * executables to search for. return name of first found file, NULL otherwise
560 * must be freed manually
561 */
find_file_in_path(const char * files)562 char* find_file_in_path(const char* files) {
563 return _find_file_in_path(files, FALSE);
564 }
565
find_file_in_path_relative(const char * files)566 char* find_file_in_path_relative(const char* files) {
567 return _find_file_in_path(files, TRUE);
568 }
569
find_file_in_path_list(char ** files)570 char* find_file_in_path_list(char** files) {
571 return _find_file_in_path_list(files, FALSE);
572 }
573
find_file_in_path_list_relative(char ** files)574 char* find_file_in_path_list_relative(char** files) {
575 return _find_file_in_path_list(files, TRUE);
576 }
577
578 /**
579 @param binaries NULL terminated list of strings
580 */
_find_file_in_path_list(char ** binaries,gboolean relative)581 static char* _find_file_in_path_list(char** binaries, gboolean relative) {
582 char* path = NULL;
583 int i = 0, j = 0;
584 char** directories = NULL;
585 char* found = NULL;
586
587 path = getenv("PATH");
588
589 if (!binaries) {
590 return NULL;
591 }
592 if (!path) {
593 return NULL;
594 }
595
596 directories = g_strsplit(path,":",0);
597
598 for (i=0; binaries[i] && !found; i++) {
599 // debug(0,"search for %s",binaries[i]);
600 for (j=0; directories[j] && !found; j++) {
601 // debug(0,"search in %s",directories[j]);
602 char *file = file_in_dir (directories[j], binaries[i]);
603 if (!file) {
604 continue;
605 }
606
607 if (!access(file,X_OK)) {
608 // If directory name is blank, don't add a / - happens if
609 // a complete path was passed
610 if (!relative && strlen(directories[j])) {
611 found = g_strconcat(directories[j],"/",binaries[i],NULL);
612 }
613 else {
614 found = g_strdup(binaries[i]);
615 }
616 debug(3,"found %s in %s",binaries[i],directories[j]);
617 }
618 g_free(file);
619 }
620 }
621
622 g_strfreev(directories);
623
624 return found;
625 }
626
_find_file_in_path(const char * files,gboolean relative)627 static char* _find_file_in_path(const char* files, gboolean relative) {
628 char** binaries = NULL;
629 char* found = NULL;
630
631 binaries = g_strsplit(files,":",0);
632
633 found = _find_file_in_path_list(binaries, relative);
634
635 g_strfreev(binaries);
636
637 return found;
638 }
639
640 /** find a directory inside another, prefer matching case otherwise search case insensitive
641 * @param basegamedir where to search
642 * @param game directory to search for
643 * @param match_result output: 0 = not found, 1 = exact match, 2 = differnet case match
644 * @returns name of found directory or NULL, must be freed manually
645 */
find_game_dir(const char * basegamedir,const char * game,int * match_result)646 char *find_game_dir (const char *basegamedir, const char *game, int *match_result) {
647 DIR *dp = NULL;
648 struct stat buf = {0};
649 char *path = NULL;
650 char* ret = NULL;
651 int result = 0;
652
653 if (!game || !basegamedir) {
654 return NULL;
655 }
656
657 debug(3, "Looking for subdir %s in %s", game, basegamedir);
658
659 // Look for exact match
660 path = file_in_dir (basegamedir, game);
661 if (!stat (path, &buf) && S_ISDIR(buf.st_mode)) {
662 debug(3, "Found exact match for subdir %s in %s", game, basegamedir);
663 result = 1; // Exact match
664 ret = g_strdup(game);
665 }
666 g_free (path);
667
668 // Did not find exact match, perform search
669 if (!ret) {
670 debug(3, "Did not find exact match for subdir %s in %s", game, basegamedir);
671 debug(3, "Searching for subdir %s in %s ignoring case", game, basegamedir);
672
673 dp = opendir (basegamedir);
674 if (!dp) {
675 debug(3, "Could not open base directory %s!", basegamedir);
676 }
677 else {
678 struct dirent *ep;
679 while ((ep = readdir (dp))) {
680 char* name = ep->d_name;
681
682 if (!strcmp(name, ".") || !strcmp(name,"..")) {
683 continue;
684 }
685
686 path = file_in_dir(basegamedir, name);
687
688 if (stat(path, &buf) == -1) {
689 g_free(path);
690 continue;
691 }
692
693 g_free(path);
694
695 if (!S_ISDIR(buf.st_mode)) {
696 continue;
697 }
698
699 if (!g_ascii_strcasecmp(name, game)) {
700 debug(3, "Found subdir %s in %s that matches %s", ep->d_name, basegamedir, game);
701 result = 2; // Different case match
702 ret = g_strdup (name);
703 break;
704 }
705 }
706 closedir (dp);
707 }
708 }
709
710 if (!ret) {
711 debug(3, "Could not find any match for subdir %s in %s.",game, basegamedir);
712 }
713
714 if (match_result) {
715 *match_result = result;
716 }
717
718 return ret;
719 }
720
721 /** sort list and remove duplicates
722 * @param list list to sort
723 * @compare_func function to use for comparing
724 * @unref_func function to call for each deleted entry
725 * @return sorted list without duplicates
726 */
slist_sort_remove_dups(GSList * list,GCompareFunc compare_func,void (* unref_func)(void *))727 GSList* slist_sort_remove_dups(GSList* list, GCompareFunc compare_func, void (*unref_func)(void*)) {
728 int i;
729 GSList* serverlist = NULL;
730 GSList* serverlistnext = NULL;
731
732 if (!list) {
733 return NULL;
734 }
735
736 list = serverlist = g_slist_sort (list, compare_func);
737
738 i = 0;
739 while (serverlist) {
740 serverlistnext=serverlist->next;
741 if (!serverlistnext) {
742 // last element, quit loop
743 serverlist = serverlistnext;
744 }
745 else if (!compare_func(serverlist->data,serverlistnext->data)) {
746 GSList* dup = serverlistnext;
747 serverlistnext = g_slist_remove_link(serverlistnext, dup);
748 serverlist->next = serverlistnext;
749 if (unref_func) unref_func(dup->data);
750 g_slist_free_1(dup);
751 }
752 else {
753 serverlist= serverlistnext;
754 }
755 i++;
756 }
757 debug(2,"number of servers %d",i);
758
759 return list;
760 }
761
762 /** \brief determine directory for game binary
763 *
764 * Extracts the path from path using the following rules:
765 * - If path is a symlink:
766 * - If pointed to file contains '..', just stop, otherwise strip filename and
767 * store as directory
768 * - If there is no /'s in the pointed to file, use the original path instead and
769 * strip filename and store as directory
770 *
771 * - If path is not a symlink:
772 * - strip filename and store as directory
773 *
774 * Path can be either a file or a directory
775 *
776 * Examples:
777 *
778 * path: /usr/bin/quake2 symlink to /games/quake2/quake2
779 * result: /games/quake2/
780 *
781 * path: /usr/bin/quake symlink to ../../games/quake2/quake2
782 * result: (stops - leaves as-is)
783 *
784 * path: /games/quake2/quake2
785 * result: /games/quake2/
786 *
787 * path: quake2
788 * result: search $PATH for binary then use above rules
789 *
790 * path: ~/bin/quake2
791 * result: expand tilde then use above rules
792 *
793 * @returns game direcory or NULL. must be freed
794 */
795
resolve_path(const char * path)796 char* resolve_path(const char* path) {
797
798 struct stat statbuf;
799 int length = 0;
800 char buf[256];
801 char *ptr = NULL;
802 char *dir = NULL;
803 char* tmp = NULL;
804
805 if (!path || !*path) {
806 return NULL;
807 }
808
809 if (path[0] == '~') {
810 tmp = expand_tilde(path);
811 path = tmp;
812 }
813 else if (path[0] != '/') {
814 tmp = find_file_in_path(path);
815 if (!tmp) {
816 debug(3, "%s not found in $PATH", path);
817 return NULL;
818 }
819 path = tmp;
820 }
821
822 if (lstat(path, &statbuf) == -1) {
823 debug(3, "lstat on %s failed", path);
824 g_free(tmp);
825 return NULL;
826 }
827
828 if (S_ISLNK(statbuf.st_mode) == 1) {
829 // Grab directory from sym link of cmd_entry
830
831 debug(3, "path is a sym link");
832
833 length = readlink (path, buf, sizeof(buf) - 1);
834
835 if (length > 0) {
836 buf[length]='\0';
837
838 if (buf[length-1] == '/') {
839 buf[length-1] = '\0';
840 }
841
842 ptr = strrchr(buf, '/');
843
844 if (ptr) { // contains a / so pull from symlink
845 if (!strstr(buf,"..")) { // don't bother if it's got any ..'s in it
846 dir = g_strndup(buf, ptr-buf+1);
847 }
848 }
849 else { // no / so pull from path instead
850 ptr = strrchr(path, '/');
851 if (ptr) { // contains a /
852 dir = g_strndup(path, ptr-path+1);
853 }
854 }
855 }
856 }
857 else {
858 // Grab directory from cmd_entry
859
860 debug(3,"path is NOT a sym link");
861
862 ptr = strrchr(path, '/');
863
864 if (ptr) // contains a /
865 {
866 gboolean dir_is_path = FALSE;
867 char* PATH = getenv("PATH");
868
869 // ignore direcory if it is in $PATH. It doesn't make sense to suggest
870 // /usr/bin as game direcory
871 if (PATH) {
872 int i;
873 char** directories = g_strsplit(PATH,":",0);
874 char* basedir = g_strndup(path, ptr-path);
875
876 for (i=0; directories[i]; ++i) {
877 if (!strcmp(directories[i], basedir)) {
878 dir_is_path = TRUE;
879 break;
880 }
881 }
882
883 g_strfreev(directories);
884 g_free(basedir);
885 }
886
887 if (!dir_is_path) {
888 dir = tmp;
889 tmp = NULL;
890 }
891 else {
892 debug(3, "found directory %s in $PATH, ignoring", tmp);
893 }
894 }
895 }
896
897 g_free(tmp);
898
899 return dir;
900 }
901
902 /** workaround to prevent gcc from complaining about %c as suggested by "man strftime" */
my_strftime(char * s,size_t max,const char * fmt,const struct tm * tm)903 static inline size_t my_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) {
904 return strftime(s, max, fmt, tm);
905 }
906
907 // return locale's string representation of t. must be freed manually
timet2string(const time_t * t)908 char* timet2string(const time_t* t) {
909 enum { timebuf_len = 128 };
910 char timebuf[timebuf_len] = {0};
911 struct tm tm_s;
912 char* str;
913
914 gmtime_r(t,&tm_s);
915 if (!my_strftime(timebuf,timebuf_len,"%c",&tm_s)) {
916 // error converting time to string representation, shouldn't happen
917 str=_("<error>");
918 }
919 else {
920 str = timebuf;
921 }
922
923 return strdup(str);
924 }
925
set_nonblock(int fd)926 int set_nonblock (int fd) {
927 int flags;
928
929 flags = fcntl (fd, F_GETFL, 0);
930 if (flags < 0 || fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) {
931 return -1;
932 }
933 return 0;
934 }
935
936 // TODO: code is generic enough to move into separate file
937
938 #if !defined(RCON_STANDALONE)
start_prog_and_return_fd(char * const argv[],pid_t * pid)939 int start_prog_and_return_fd(char *const argv[], pid_t *pid) {
940 int pipefds[2];
941
942 *pid = -1;
943
944 if (pipe (pipefds) < 0) {
945 xqf_error("error creating pipe: %s",strerror(errno));
946 return -1;
947 }
948 *pid = fork();
949 if (*pid < (pid_t) 0) {
950 xqf_error("fork failed: %s",strerror(errno));
951 return -1;
952 }
953
954 if (*pid == 0) // child
955 {
956 close(1);
957 // close(2);
958 close (pipefds[0]);
959 dup2 (pipefds[1], 1);
960 // dup2 (pipefds[1], 2);
961 close (pipefds[1]);
962
963 close_fds(-1);
964
965 debug(3,"child about to exec %s", argv[0]);
966
967 execvp (argv[0], argv);
968
969 xqf_error("failed to exec %s: %s",argv[0],strerror(errno));
970
971 _exit (1);
972 }
973
974 close (pipefds[1]);
975
976 if (set_nonblock(pipefds[0]) == -1) {
977 close (pipefds[1]);
978 if (*pid > 0) {
979 kill(*pid,SIGTERM);
980 }
981 xqf_error("fcntl failed: %s", strerror(errno));
982 return -1;
983 }
984
985 return pipefds[0];
986 }
987
external_program_close_input(struct external_program_connection * conn)988 void external_program_close_input(struct external_program_connection* conn) {
989 if (!conn) {
990 return;
991 }
992 gdk_input_remove(conn->tag);
993 close(conn->fd);
994 if (conn->pid > 0) kill(conn->pid,SIGTERM);
995 if (conn->do_quit) gtk_main_quit();
996 }
997
external_program_input_callback(struct external_program_connection * conn,int fd,GIOCondition condition)998 void external_program_input_callback(struct external_program_connection* conn, int fd, GIOCondition condition) {
999 int bytes;
1000 char* sol; // start of line pointer
1001 char* eol; // end of line pointer
1002
1003 if (!conn) {
1004 return;
1005 }
1006
1007 if (conn->pos >= conn->bufsize) {
1008 xqf_error("line %d too long",conn->linenr+1);
1009 external_program_close_input(conn);
1010 return;
1011 }
1012
1013 bytes = read (fd, conn->buf + conn->pos, conn->bufsize - conn->pos);
1014
1015 if (bytes < 0) {
1016 if (errno == EAGAIN || errno == EWOULDBLOCK) {
1017 return;
1018 }
1019
1020 xqf_error("Error reading from child: %s",g_strerror(errno));
1021 external_program_close_input(conn);
1022 return;
1023 }
1024 if (bytes == 0) { /* EOF */
1025 external_program_close_input(conn);
1026 return;
1027 }
1028
1029
1030 sol = conn->buf;
1031 eol = conn->buf + conn->pos;
1032 conn->pos += bytes;
1033 bytes = conn->pos;
1034
1035 // buffer can contain multiple lines
1036 for (;(eol = memchr(eol,'\n',bytes-(eol-sol))) != NULL;bytes-=eol-sol+1,sol= ++eol) {
1037 *eol = '\0';
1038 // debug(0,"%4d, line(%4d,%4d-%4d)>%s<",bytes, eol-sol,sol-conn->buf,eol-conn->buf,sol);
1039 ++conn->linenr;
1040 conn->current_line = sol;
1041 if (conn->linefunc) (conn->linefunc)(conn);
1042 }
1043 // sol now points to begin of next line, if any
1044
1045 if (sol-conn->buf) {
1046 if (bytes) {
1047 memmove(conn->buf,sol,bytes);
1048 }
1049 conn->pos = bytes;
1050 }
1051 }
1052
external_program_foreach_line(char * argv[],void (* linefunc)(struct external_program_connection * conn),gpointer data)1053 int external_program_foreach_line(char* argv[], void (*linefunc)(struct external_program_connection* conn), gpointer data) {
1054 struct external_program_connection conn = {0};
1055
1056 conn.fd = start_prog_and_return_fd(argv,&conn.pid);
1057
1058 if (conn.fd<0||conn.pid<=0) {
1059 return FALSE;
1060 }
1061
1062 conn.bufsize = 1024;
1063 conn.buf = g_new0(char,conn.bufsize);
1064 conn.result = FALSE;
1065 conn.do_quit = TRUE;
1066 conn.linenr = 0;
1067 conn.linefunc = linefunc;
1068 conn.data = data;
1069
1070 conn.tag = gdk_input_add (conn.fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
1071 (GdkInputFunction) external_program_input_callback, &conn);
1072
1073 gtk_main();
1074
1075 g_free(conn.buf);
1076
1077 return conn.result;
1078 }
1079
_run_program_sync(const char * argv[],void (* child_callback)(void *),gpointer data)1080 int _run_program_sync(const char* argv[], void(*child_callback)(void*), gpointer data) {
1081 int status = -1;
1082 pid_t pid;
1083
1084 pid = fork();
1085 if (pid == 0) {
1086 if (child_callback) {
1087 child_callback(data);
1088 }
1089 execvp(argv[0],(void*)argv);
1090 _exit(EXIT_FAILURE);
1091 }
1092 else if (pid > 0) {
1093 waitpid(pid,&status,0);
1094
1095 if (WIFEXITED(status)) {
1096 debug(3,"%s exited normally", argv[0]);
1097 }
1098 else {
1099 debug(3,"%s exited with status %d", argv[0], WEXITSTATUS(status));
1100 }
1101
1102 if (WIFSIGNALED(status)) {
1103 debug(3,"%s was killed by signal %d", argv[0], WTERMSIG(status));
1104 }
1105 }
1106
1107 return status;
1108 }
1109
run_program_sync(const char * argv[])1110 int run_program_sync(const char* argv[]) {
1111 return _run_program_sync(argv, NULL, NULL);
1112 }
1113
run_program_sync_callback(const char * argv[],void (* child_callback)(void *),gpointer data)1114 int run_program_sync_callback(const char* argv[], void(*child_callback)(void*), gpointer data) {
1115 return _run_program_sync(argv, child_callback, data);
1116 }
1117
copy_file(const char * src,const char * dest)1118 const char* copy_file(const char* src, const char* dest) {
1119 char buf[4*4096];
1120 const char* msg = NULL;
1121 int fdin = -1, fdout = -1;
1122 int ret;
1123
1124 fdin = open(src, O_RDONLY);
1125 if (fdin == -1) {
1126 msg = _("Can't open source file for reading");
1127 goto error_out;
1128 }
1129
1130 fdout = open(dest, O_CREAT|O_WRONLY|O_TRUNC, 0777);
1131 if (fdout == -1) {
1132 msg = _("Can't open destination file for writing");
1133 goto error_out;
1134 }
1135
1136 while ((ret = read(fdin, buf, sizeof(buf))) > 0) {
1137 if (write(fdout, buf, ret) == -1) {
1138 msg = _("write error on destination file");
1139 goto error_out;
1140 }
1141 }
1142
1143 if (ret == -1) {
1144 msg = _("read error on source file");
1145 goto error_out;
1146 }
1147
1148 error_out:
1149 if (fdin != -1) {
1150 close(fdin);
1151 }
1152 if (fdout != -1) {
1153 close(fdout);
1154 }
1155
1156 if (msg) {
1157 unlink(dest);
1158 }
1159
1160 return msg;
1161 }
1162
load_file_mem(const char * name,size_t * size)1163 char* load_file_mem(const char* name, size_t* size) {
1164 char* buf = NULL;
1165 struct stat stb;
1166 int fd;
1167
1168 fd = open(name, O_RDONLY);
1169 if (fd == -1) {
1170 goto out;
1171 }
1172
1173 if (fstat(fd, &stb) == -1) {
1174 goto out;
1175 }
1176
1177 buf = g_malloc(stb.st_size);
1178
1179 if (read(fd, buf, stb.st_size) != stb.st_size) {
1180 g_free(buf);
1181 buf = NULL;
1182 goto out;
1183 }
1184
1185 out:
1186 if (fd != -1) {
1187 close(fd);
1188 }
1189
1190 if (buf) {
1191 *size = stb.st_size;
1192 }
1193
1194 return buf;
1195 }
1196 #endif // ! RCON_STANDALONE
1197
close_fds(int exclude)1198 void close_fds(int exclude) {
1199 unsigned i;
1200 int maxfd = sysconf(_SC_OPEN_MAX);
1201 for (i = 3; i < maxfd; ++i) {
1202 if (i == exclude) {
1203 continue;
1204 }
1205 close(i);
1206 }
1207 }
1208