1 /*
2 This file is part of SLRN.
3
4 Copyright (c) 1994, 1999, 2007-2016 John E. Davis <jed@jedsoft.org>
5 Copyright (c) 2002-2006 Thomas Schultz <tststs@gmx.de>
6
7 This program is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2 of the License, or (at your option)
10 any later version.
11
12 This program is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 more details.
16
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21 #include "config.h"
22 #include "slrnfeat.h"
23
24 #include <stdio.h>
25 #include <string.h>
26 #include <errno.h>
27 #ifdef HAVE_STDLIB_H
28 # include <stdlib.h>
29 #endif
30
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34
35 #ifndef VMS
36 # include <sys/types.h>
37 # include <sys/stat.h>
38 #else
39 # include "vms.h"
40 #endif
41
42 #ifdef __WIN32__
43 # include <windows.h>
44 #endif
45
46 #include <slang.h>
47 #include "jdmacros.h"
48
49 #include "util.h"
50 #include "ttymsg.h"
51 #include "snprintf.h"
52 #include "slrn.h"
53 #include "strutil.h"
54 #include "common.h"
55
slrn_charset_strlen(const char * str,char * cset)56 size_t slrn_charset_strlen (const char *str, char *cset)
57 {
58 if ((cset != NULL) && !slrn_case_strcmp(cset,"utf-8"))
59 return SLutf8_strlen ((SLuchar_Type *)str, 1);
60 else
61 return strlen (str);
62 }
63
64 /* Find out how many characters (columns) a string would use on screen.
65 * If len>=0, only the first len bytes are examined.
66 */
slrn_screen_strlen(const char * s,const char * smax)67 int slrn_screen_strlen (const char *s, const char *smax) /*{{{*/
68 {
69 if (smax == NULL)
70 smax = s + strlen (s);
71
72 #if SLANG_VERSION >= 20000
73 if (Slrn_UTF8_Mode)
74 {
75 return SLsmg_strwidth ((SLuchar_Type *) s, (SLuchar_Type *) smax);
76 }
77 else
78 #endif
79 {
80 int retval = 0;
81 while (s < smax)
82 {
83 unsigned char ch= (unsigned char) *s++;
84
85 if ((ch == '\t') && (SLsmg_Tab_Width > 0))
86 {
87 retval += SLsmg_Tab_Width;
88 retval -= retval % SLsmg_Tab_Width;
89 }
90 else if (((ch >= ' ') && (ch < 127))
91 || (ch >= (unsigned char) SLsmg_Display_Eight_Bit))
92 retval++;
93 else
94 {
95 retval += 2; /* ^X */
96 if (ch & 0x80) retval += 2; /* <XX> */
97 }
98 }
99 return retval;
100 }
101 }
102 /*}}}*/
103
104 #if defined(IBMPC_SYSTEM)
slrn_os2_convert_path(char * path)105 void slrn_os2_convert_path (char *path)
106 {
107 char ch;
108 while ((ch = *path) != 0)
109 {
110 if (ch == '/') *path = SLRN_PATH_SLASH_CHAR;
111 path++;
112 }
113 }
114 #endif
115
116 #ifdef __CYGWIN__
117 /* return values:
118 * 0 no error
119 * -1 misplaced or too many colons
120 * -2 outpath too short
121 */
slrn_cygwin_convert_path(char * inpath,char * outpath,size_t n)122 int slrn_cygwin_convert_path (char *inpath, char *outpath, size_t n)
123 {
124 unsigned int outlen;
125 char *p;
126
127 /*
128 ** first, a quick sanity check to look for invalid formats
129 */
130 p = strrchr (inpath, ':'); /* we'll re-use 'p' later on */
131 if (p && (p != inpath+1))
132 return -1;
133
134 /*
135 ** let's do some size checking
136 */
137 outlen = strlen (inpath) + 1;
138 if (p)
139 outlen += 9; /* "/cygdrive/c" (11) replaces "c:" (2) ==> 9 */
140
141 if (n < outlen)
142 return -2;
143
144 /*
145 ** paths starting with C:, (or D:, etc) should be converted to the
146 ** Cygwin native format /cygdrive/c (or /cygdrive/d, etc) while copying
147 ** source (inpath) to destination (outpath)
148 */
149 if (p)
150 {
151 strcpy (outpath, "/cygdrive/");
152 strncat (outpath, inpath, 1);
153 strcat (outpath, p+1); /* safe */
154 }
155 else
156 strcpy (outpath, inpath); /* safe */
157
158 /*
159 ** go through outpath character by character and change all backslashes
160 ** to forward slashes
161 */
162 p = outpath;
163 while (*p)
164 {
165 if (*p == '\\')
166 *p = '/';
167 p++;
168 }
169
170 /*
171 ** normal return
172 */
173 return 0;
174 }
175 #endif
176
177 #ifdef SLRN_USE_OS2_FAT
slrn_os2_make_fat(char * file,size_t n,char * name,char * ext)178 void slrn_os2_make_fat (char *file, size_t n, char *name, char *ext)
179 {
180 static char drive[3] = " :";
181 char fsys[5];
182
183 slrn_strncpy (file, name, n);
184 if (isalpha(file[0]) && (file[1] == ':'))
185 drive[0] = file[0];
186 else
187 drive[0] = _getdrive();
188
189 if ((0 == _filesys (drive, fsys, sizeof (fsys)))
190 && (0 == stricmp (fsys, "FAT")))
191 {
192 /* FAT */
193 _remext (file); /* Remove the extension */
194 }
195
196 if (strlen (file) + strlen (ext) < n)
197 strcat (file, ext); /* safe */
198 }
199 #endif
200
fixup_path(char * path)201 static void fixup_path (char *path) /*{{{*/
202 {
203 #ifndef VMS
204 unsigned int len;
205
206 len = strlen (path);
207 if (len == 0) return;
208 # ifdef IBMPC_SYSTEM
209 slrn_os2_convert_path (path);
210 # endif
211 if (path[len - 1] == SLRN_PATH_SLASH_CHAR) return;
212 path[len] = SLRN_PATH_SLASH_CHAR;
213 path[len + 1] = 0;
214 #endif
215 }
216
217 /*}}}*/
218
219 /* dir and file could be the same in which case this performs a strcat.
220 * If name looks like an absolute path, it will be returned.
221 */
slrn_dircat(char * dir,char * name,char * file,size_t n)222 int slrn_dircat (char *dir, char *name, char *file, size_t n)
223 {
224 unsigned int len = 0;
225 #ifdef __CYGWIN__
226 char convdir [SLRN_MAX_PATH_LEN];
227 #endif
228
229 if (name != NULL)
230 {
231 if (slrn_is_absolute_path (name))
232 {
233 #ifdef __CYGWIN__
234 if (slrn_cygwin_convert_path (name, file, n))
235 #endif
236 slrn_strncpy (file, name, n);
237 #if defined(IBMPC_SYSTEM) && !defined(__CYGWIN__)
238 slrn_os2_convert_path (file);
239 #endif
240 return 0;
241 }
242
243 len = strlen (name);
244 }
245
246 if (dir != NULL) len += strlen (dir);
247
248 len += 2; /* for / and \0 */
249 if (len > n)
250 {
251 slrn_error (_("File name too long."));
252 return -1;
253 }
254
255 if (dir != NULL)
256 {
257 if (dir != file) strcpy (file, dir); /* safe */
258 fixup_path (file);
259 }
260 else *file = 0;
261
262 if (name != NULL) strcat (file, name); /* safe */
263 #ifdef __CYGWIN__
264 if ((0 == slrn_cygwin_convert_path (file, convdir, sizeof (convdir))) &&
265 (strlen (convdir) < n))
266 strcpy (file, convdir); /* safe */
267 else
268 slrn_error ("Cygwin conversion of %s failed.", file);
269 #else
270 # if defined(IBMPC_SYSTEM)
271 slrn_os2_convert_path (file);
272 # endif
273 #endif
274 return 0;
275 }
276
slrn_free_argc_argv_list(unsigned int argc,char ** argv)277 void slrn_free_argc_argv_list (unsigned int argc, char **argv)
278 {
279 while (argc)
280 {
281 argc--;
282 slrn_free (argv[argc]);
283 }
284 }
285
slrn_fix_regexp(char * pat)286 char *slrn_fix_regexp (char *pat) /*{{{*/
287 {
288 static char newpat[256];
289 char *p, ch;
290 unsigned int len;
291
292 len = 1; /* For ^ */
293 p = pat;
294 while (*p != 0)
295 {
296 if ((*p == '.') || (*p == '*') || (*p == '+')) len++;
297 len++;
298 p++;
299 }
300 len++; /* for $ */
301 len++; /* for \0 */
302
303 if (len > sizeof(newpat))
304 slrn_exit_error (_("Pattern too long for buffer"));
305
306 p = newpat;
307
308 *p++ = '^';
309 while ((ch = *pat++) != 0)
310 {
311 if ((ch == '.') || (ch == '+'))
312 *p++ = '\\';
313 else if (ch == '*')
314 *p++ = '.';
315
316 *p++ = ch;
317 }
318
319 if (*(p - 1) != '$')
320 *p++ = '$';
321
322 *p = 0;
323
324 return newpat;
325 }
326
327 /*}}}*/
328
slrn_is_absolute_path(char * path)329 int slrn_is_absolute_path (char *path)
330 {
331 if (path == NULL)
332 return 0;
333
334 if (*path == SLRN_PATH_SLASH_CHAR)
335 return 1;
336 #if defined(IBMPC_SYSTEM)
337 if (*path == '/')
338 return 1;
339 if (*path && (path[1] == ':'))
340 return 1;
341 #endif
342 return 0;
343 }
344
345 /* This is like slrn_dircat except that any dots in name can get mapped to
346 * slashes. It also mallocs space for the resulting file.
347 */
slrn_spool_dircat(char * root,char * name,int map_dots)348 char *slrn_spool_dircat (char *root, char *name, int map_dots)
349 {
350 char *spool_group, *p, ch;
351 #ifdef __CYGWIN__
352 char *convdir;
353 #endif
354 unsigned int len;
355
356 len = strlen (root);
357
358 spool_group = SLmalloc (strlen (name) + len + 2);
359 if (spool_group == NULL)
360 {
361 slrn_exit_error (_("Out of memory."));
362 }
363
364 strcpy (spool_group, root); /* safe */
365
366 p = spool_group + len;
367 if (len && (*(p - 1) != SLRN_PATH_SLASH_CHAR))
368 *p++ = SLRN_PATH_SLASH_CHAR;
369
370 strcpy (p, name); /* safe */
371
372 if (map_dots) while ((ch = *p) != 0)
373 {
374 if (ch == '.') *p = SLRN_PATH_SLASH_CHAR;
375 p++;
376 }
377 #ifdef __CYGWIN__
378 len = strlen (spool_group) + 9;
379 if (NULL == (convdir = SLmalloc (len)))
380 slrn_exit_error (_("Out of memory."));
381 if (0 == slrn_cygwin_convert_path (spool_group, convdir, len))
382 {
383 slrn_free (spool_group);
384 spool_group = convdir;
385 }
386 else
387 slrn_error (_("Cygwin conversion of %s failed."), spool_group);
388 #endif
389 #if defined(IBMPC_SYSTEM)
390 slrn_os2_convert_path (spool_group);
391 #endif
392 return spool_group;
393 }
394
slrn_delete_file(char * f)395 int slrn_delete_file (char *f) /*{{{*/
396 {
397 #ifdef VMS
398 return delete(f);
399 #else
400 return unlink(f);
401 #endif
402 }
403
404 /*}}}*/
405
slrn_fclose(FILE * fp)406 int slrn_fclose (FILE *fp) /*{{{*/
407 {
408 if (0 == fclose (fp)) return 0;
409 slrn_error (_("Error closing file. File system full? (errno = %d)"), errno);
410 return -1;
411 }
412
413 /*}}}*/
414
slrn_file_exists(char * file)415 int slrn_file_exists (char *file) /*{{{*/
416 {
417 struct stat st;
418 int m;
419
420 #ifdef _S_IFDIR
421 # ifndef S_IFDIR
422 # define S_IFDIR _S_IFDIR
423 # endif
424 #endif
425
426 #ifndef S_ISDIR
427 # ifdef S_IFDIR
428 # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
429 # else
430 # define S_ISDIR(m) 0
431 # endif
432 #endif
433
434 if (file == NULL)
435 return -1;
436
437 if (stat(file, &st) < 0) return 0;
438 m = st.st_mode;
439
440 if (S_ISDIR(m)) return (2);
441 return 1;
442 }
443
444 /*}}}*/
445
slrn_file_size(char * file)446 int slrn_file_size (char *file) /*{{{*/
447 {
448 struct stat st;
449 int m;
450
451 if (file == NULL)
452 return 0;
453
454 if (stat(file, &st) < 0) return -1;
455 m = st.st_mode;
456
457 if (S_ISDIR(m)) return -1;
458 return (int)st.st_size;
459 }
460 /*}}}*/
461
slrn_basename(char * file)462 char *slrn_basename (char *file)
463 {
464 char *f;
465 #ifdef VMS
466 f = slrn_strbyte (file, ']');
467 if (f != NULL) return f + 1;
468 return file;
469 #else
470
471 while (NULL != (f = slrn_strbyte (file, SLRN_PATH_SLASH_CHAR)))
472 file = f + 1;
473
474 return file;
475 #endif
476 }
477
slrn_mkdir(char * dir)478 int slrn_mkdir (char *dir) /*{{{*/
479 {
480 #if defined(__MINGW32__)
481 # define MKDIR(x,y) mkdir(x)
482 #else
483 # define MKDIR(x,y) mkdir(x,y)
484 #endif
485 return MKDIR (dir, 0777);
486 }
487 /*}}}*/
488
file_eqs(char * a,char * b)489 static int file_eqs (char *a, char *b)
490 {
491 #ifdef REAL_UNIX_SYSTEM
492 struct stat st_a, st_b;
493 #endif
494
495 if (0 == strcmp (a, b))
496 return 1;
497
498 #ifndef REAL_UNIX_SYSTEM
499 return 0;
500 #else
501 if (-1 == stat (a, &st_a))
502 return 0;
503 if (-1 == stat (b, &st_b))
504 return 0;
505
506 return ((st_a.st_ino == st_b.st_ino)
507 && (st_a.st_dev == st_b.st_dev));
508 #endif
509 }
510
slrn_copy_file(char * infile,char * outfile)511 int slrn_copy_file (char *infile, char *outfile)
512 {
513 FILE *in, *out;
514 int ch;
515 int ret;
516
517 if ((infile == NULL) || (outfile == NULL))
518 return -1;
519
520 if (file_eqs (infile, outfile))
521 return 0;
522
523 if (NULL == (in = fopen (infile, "rb")))
524 {
525 slrn_error (_("Error opening %s"), infile);
526 return -1;
527 }
528
529 if (NULL == (out = fopen (outfile, "wb")))
530 {
531 fclose (in);
532 slrn_error (_("Error opening %s"), outfile);
533 return -1;
534 }
535
536 ret = 0;
537 while (EOF != (ch = getc (in)))
538 {
539 if (EOF == putc (ch, out))
540 {
541 slrn_error (_("Write Error: %s"), outfile);
542 ret = -1;
543 break;
544 }
545 }
546
547 fclose (in);
548 if (-1 == slrn_fclose (out))
549 ret = -1;
550
551 return ret;
552 }
553
slrn_move_file(char * infile,char * outfile)554 int slrn_move_file (char *infile, char *outfile)
555 {
556 if ((infile == NULL) || (outfile == NULL))
557 return -1;
558
559 if (file_eqs (infile, outfile))
560 return 0;
561
562 (void) slrn_delete_file (outfile);
563 if (-1 == rename (infile, outfile))
564 {
565 if (-1 == slrn_copy_file (infile, outfile))
566 return -1;
567 slrn_delete_file (infile);
568 }
569 return 0;
570 }
571
572 /* Some functions to handle file backups correctly ... */
573
574 /* Make and return a malloc'ed filename by adding an OS-specific suffix
575 * that flags backup files. */
slrn_make_backup_filename(char * filename)576 char *slrn_make_backup_filename (char *filename)
577 {
578 #ifdef SLRN_USE_OS2_FAT
579 unsigned int len = strlen(filename)+5;
580 char *retval = slrn_safe_malloc (len);
581 slrn_os2_make_fat (retval, len, filename, ".bak");
582 return retval;
583 #else
584 unsigned int len;
585 char *suffix;
586 char *retval;
587 # ifdef VMS
588 suffix = "-bak";
589 # else
590 suffix = "~";
591 # endif
592 len = 1 + strlen (suffix) + strlen (filename);
593 retval = slrn_safe_malloc (len);
594 (void) sprintf (retval, "%s%s", filename, suffix);
595 return retval;
596 #endif
597 }
598
599 /* Creates a backup of the given file, copying it if necessary (i.e. it is
600 * a softlink or there are multiple hardlinks on it) - after that, filename
601 * may or may no longer denote an existing file.
602 * Returns 0 on success, -1 on errors. */
slrn_create_backup(char * filename)603 int slrn_create_backup (char *filename)
604 {
605 char *backup_file = slrn_make_backup_filename (filename);
606 int retval = -1, do_copy = 0;
607 #ifdef __unix__
608 struct stat st;
609
610 if (0 == lstat (filename, &st))
611 {
612 do_copy = (st.st_nlink > 1)
613 # ifdef S_ISLNK
614 || (S_ISLNK(st.st_mode))
615 # endif
616 ;
617 }
618 #endif /* __unix__ */
619
620 if (slrn_file_exists(filename))
621 {
622 if (!do_copy)
623 retval = rename (filename, backup_file);
624 if (retval == -1)
625 retval = slrn_copy_file (filename, backup_file);
626 }
627
628 SLfree (backup_file);
629
630 return retval;
631 }
632
633 /* Tries to delete the backup of the given file, ignoring all errors. */
slrn_delete_backup(char * filename)634 void slrn_delete_backup (char *filename)
635 {
636 char *backup_file = slrn_make_backup_filename (filename);
637 (void) slrn_delete_file (backup_file);
638 SLfree (backup_file);
639 }
640
641 /* Tries to restore the given file from the backup, copying it if necessary
642 * (same condition as above). */
slrn_restore_backup(char * filename)643 int slrn_restore_backup (char *filename)
644 {
645 char *backup_file = slrn_make_backup_filename (filename);
646 int retval = -1, do_copy = 0;
647 #ifdef __unix__
648 struct stat st;
649
650 if (0 == lstat (filename, &st))
651 {
652 do_copy = (st.st_nlink > 1)
653 # ifdef S_ISLNK
654 || (S_ISLNK(st.st_mode))
655 # endif
656 ;
657 }
658 #endif /* __unix__ */
659
660 if (!do_copy)
661 retval = rename (backup_file, filename);
662 if (retval == -1)
663 {
664 retval = slrn_copy_file (backup_file, filename);
665 if (retval == 0)
666 (void) slrn_delete_file (backup_file);
667 }
668
669 SLfree (backup_file);
670
671 return retval;
672 }
673
slrn_sleep(unsigned int len)674 unsigned int slrn_sleep (unsigned int len)
675 {
676 #ifdef __WIN32__
677 if (len)
678 Sleep (len*1000);
679 return 0;
680 #else
681 return sleep (len);
682 #endif
683 }
684
685 /* Writes strings to stderr. If the _first_ character in a string is '\n',
686 * it gets replaced with '\r\n' on IBMPC_SYSTEM
687 * Note: We need this because gettext does not like '\r'. Sigh. */
slrn_va_stderr_strcat(const char * str,va_list args)688 void slrn_va_stderr_strcat (const char *str, va_list args) /*{{{*/
689 {
690 const char *p = str;
691 while (p != NULL)
692 {
693 #ifdef IBMPC_SYSTEM
694 if (*p == '\n')
695 fputc ('\r', stderr);
696 #endif
697 fputs (p, stderr);
698 p = va_arg (args, const char *);
699 }
700 }
701 /*}}}*/
702
slrn_stderr_strcat(const char * str,...)703 void slrn_stderr_strcat (const char *str, ...) /*{{{*/
704 {
705 va_list ag;
706 va_start (ag, str);
707 slrn_va_stderr_strcat (str, ag);
708 va_end (ag);
709 }
710 /*}}}*/
711