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