1 /*
2  * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  *
19  * This file incorporates work covered by the following copyright and
20  * permission notice:
21  *
22  *  Copyright 2000 Red Hat, Inc.
23  *
24  * This library is free software; you can redistribute it and/or
25  * modify it under the terms of the GNU General Public
26  * License as published by the Free Software Foundation; either
27  * version 3 of the License, or (at your option) any later version.
28  *
29  * This library is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
32  * General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License
35  * along with this library; if not, see <http://www.gnu.org/licenses/>.
36  */
37 
38 #include "zrythm-config.h"
39 
40 #define _XOPEN_SOURCE 500
41 #include <stdio.h>
42 #include <ftw.h>
43 #include <unistd.h>
44 
45 #ifdef _WOE32
46 #include <windows.h>
47 #endif
48 
49 #include <stdlib.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <time.h>
53 #include <unistd.h>
54 
55 #include "utils/file.h"
56 #include "utils/io.h"
57 #include "utils/objects.h"
58 #include "utils/string.h"
59 #include "utils/system.h"
60 #include "zrythm.h"
61 
62 #include <glib/gstdio.h>
63 #include <glib/gi18n.h>
64 #include <gtk/gtk.h>
65 
66 #if defined (__APPLE__) && defined (INSTALLER_VER)
67 #include "CoreFoundation/CoreFoundation.h"
68 #include <unistd.h>
69 #include <libgen.h>
70 #endif
71 
72 /**
73  * Gets directory part of filename. MUST be freed.
74  *
75  * @param filename Filename containing directory.
76  */
77 char *
io_get_dir(const char * filename)78 io_get_dir (const char * filename)
79 {
80   return g_path_get_dirname (filename);
81 }
82 
83 /**
84  * Makes directory if doesn't exist.
85  *
86  * @return 0 if the directory exists or was
87  *   successfully created, -1 if error was occurred
88  *   and errno is set.
89  */
90 int
io_mkdir(const char * dir)91 io_mkdir (const char * dir)
92 {
93   g_message ("Creating directory: %s", dir);
94   struct stat st = {0};
95   if (stat(dir, &st) == -1)
96     {
97       return g_mkdir_with_parents (dir, 0700);
98     }
99   return 0;
100 }
101 
102 /**
103  * Returns file extension or NULL.
104  */
105 const char *
io_file_get_ext(const char * filename)106 io_file_get_ext (const char * filename)
107 {
108   const char * dot = strrchr (filename, '.');
109   if (!dot || dot == filename)
110     return "";
111 
112   return dot + 1;
113 }
114 
115 /**
116  * Creates the file if doesn't exist
117  */
118 FILE *
io_touch_file(const char * filename)119 io_touch_file (const char * filename)
120 {
121   return fopen(filename, "ab+");
122 }
123 
124 /**
125  * Strips extensions from given filename.
126  *
127  * MUST be freed.
128  */
129 char *
io_file_strip_ext(const char * filename)130 io_file_strip_ext (const char * filename)
131 {
132   /* if last char is a dot, return the string
133    * without the dot */
134   size_t len = strlen (filename);
135   if (filename[len - 1] == '.')
136     {
137       return g_strndup (filename, len - 1);
138     }
139 
140   const char * dot = io_file_get_ext (filename);
141 
142   /* if no dot, return filename */
143   if (string_is_equal (dot, ""))
144     {
145       return g_strdup (filename);
146     }
147 
148   long size = (dot - filename) - 1;
149   char * new_str =
150     g_strndup (filename, (gsize) size);
151 
152   return new_str;
153 }
154 
155 /**
156  * Strips path from given filename.
157  *
158  * MUST be freed.
159  */
160 char *
io_path_get_basename_without_ext(const char * filename)161 io_path_get_basename_without_ext (
162   const char * filename)
163 {
164   char * basename = g_path_get_basename (filename);
165   char * no_ext =
166     io_file_strip_ext (basename);
167   g_free (basename);
168 
169   return no_ext;
170 }
171 
172 char *
io_path_get_parent_dir(const char * path)173 io_path_get_parent_dir (
174   const char * path)
175 {
176 #ifdef _WOE32
177 #define PATH_SEP "\\\\"
178 #define ROOT_REGEX "[A-Z]:" PATH_SEP
179 #else
180 #define PATH_SEP "/"
181 #define ROOT_REGEX "/"
182 #endif
183   char regex[] =
184     "(" ROOT_REGEX ".*)" PATH_SEP "[^" PATH_SEP "]+";
185   char * parent =
186     string_get_regex_group (path, regex, 1);
187 #if 0
188   g_message ("[%s]\npath: %s\nregex: %s\nparent: %s",
189     __func__, path, regex, parent);
190 #endif
191 
192   if (!parent)
193     {
194       strcpy (
195         regex, "(" ROOT_REGEX ")[^" PATH_SEP "]*");
196       parent =
197         string_get_regex_group (path, regex, 1);
198 #if 0
199       g_message ("path: %s\nregex: %s\nparent: %s",
200         path, regex, parent);
201 #endif
202     }
203 
204 #undef PATH_SEP
205 #undef ROOT_REGEX
206 
207   return parent;
208 }
209 
210 char *
io_file_get_creation_datetime(const char * filename)211 io_file_get_creation_datetime (const char * filename)
212 {
213   /* TODO */
214   return NULL;
215 }
216 
217 char *
io_file_get_last_modified_datetime(const char * filename)218 io_file_get_last_modified_datetime (
219   const char * filename)
220 {
221   struct stat result;
222   struct tm *nowtm;
223   char tmbuf[64];
224   if (stat (filename, &result)==0)
225     {
226       nowtm = localtime (&result.st_mtime);
227       strftime(tmbuf, sizeof (tmbuf), "%Y-%m-%d %H:%M:%S", nowtm);
228       char * mod = g_strdup (tmbuf);
229       return mod;
230     }
231   g_message ("Failed to get last modified for %s",
232              filename);
233   return NULL;
234 }
235 
236 /**
237  * Removes the given file.
238  */
239 int
io_remove(const char * path)240 io_remove (
241   const char * path)
242 {
243   if (ZRYTHM)
244     {
245       g_message ("Removing %s...", path);
246     }
247   return g_remove (path);
248 }
249 
250 static int
unlink_cb(const char * fpath,const struct stat * sb,int typeflag,struct FTW * ftwbuf)251 unlink_cb (
252   const char *fpath,
253   const struct stat *sb,
254   int typeflag,
255   struct FTW *ftwbuf)
256 {
257     int rv = remove(fpath);
258 
259     if (rv)
260         perror(fpath);
261 
262     return rv;
263 }
264 
265 /**
266  * Removes a dir, optionally forcing deletion.
267  *
268  * For safety reasons, this only accepts an
269  * absolute path with length greater than 20 if
270  * forced.
271  */
272 int
io_rmdir(const char * path,bool force)273 io_rmdir (
274   const char * path,
275   bool         force)
276 {
277   if (force)
278     {
279       g_return_val_if_fail (
280         g_path_is_absolute (path) &&
281         strlen (path) > 20, -1);
282       nftw (
283         path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
284     }
285 
286   g_message ("Removing %s", path);
287   return g_rmdir (path);
288 }
289 
290 /**
291  * Appends files to the given array from the given
292  * dir if they end in the given string.
293  *
294  * @param end_string If empty, appends all files.
295  */
296 static void
append_files_from_dir_ending_in(char *** files,int * num_files,const int recursive,const char * _dir,const char * end_string)297 append_files_from_dir_ending_in (
298   char ***     files,
299   int *        num_files,
300   const int    recursive,
301   const char * _dir,
302   const char * end_string)
303 {
304   GDir *dir;
305   GError *error = NULL;
306   const gchar *filename;
307   char * full_path;
308 
309   dir = g_dir_open (_dir, 0, &error);
310   if (error)
311     {
312       if (ZRYTHM_TESTING)
313         {
314           /* this is needed because
315            * g_test_expect_message() doesn't work
316            * with below */
317           g_warn_if_reached ();
318         }
319       else
320         {
321           g_warning (
322             "Failed opening directory %s: %s",
323             _dir, error->message);
324         }
325       return;
326     }
327 
328   while ((filename = g_dir_read_name (dir)))
329     {
330       full_path =
331         g_build_filename (
332           _dir, filename, NULL);
333 
334       /* recurse if necessary */
335       if (recursive &&
336           g_file_test (
337             full_path, G_FILE_TEST_IS_DIR))
338         {
339           append_files_from_dir_ending_in (
340             files, num_files, recursive, full_path,
341             end_string);
342         }
343 
344       if (!end_string ||
345           (end_string &&
346              g_str_has_suffix (
347                full_path, end_string)))
348         {
349           *files =
350             realloc (
351               *files,
352               sizeof (char *) *
353                 (size_t) (*num_files + 2));
354           (*files)[(*num_files)] =
355             g_strdup (full_path);
356           (*num_files)++;
357         }
358 
359       g_free (full_path);
360     }
361   g_dir_close (dir);
362 
363   /* NULL terminate */
364   (*files)[*num_files] = NULL;
365 }
366 
367 void
io_copy_dir(const char * destdir_str,const char * srcdir_str,bool follow_symlinks,bool recursive)368 io_copy_dir (
369   const char * destdir_str,
370   const char * srcdir_str,
371   bool         follow_symlinks,
372   bool         recursive)
373 {
374   GDir * srcdir;
375   GError *error = NULL;
376   const gchar *filename;
377 
378    srcdir = g_dir_open (srcdir_str, 0, &error);
379   if (error)
380     {
381       if (ZRYTHM_TESTING)
382         {
383           /* this is needed because
384            * g_test_expect_message() doesn't work
385            * with below */
386           g_warn_if_reached ();
387         }
388       else
389         {
390           g_warning (
391             "Failed opening directory %s: %s",
392             srcdir_str, error->message);
393         }
394       return;
395     }
396 
397   io_mkdir (destdir_str);
398 
399   while ((filename = g_dir_read_name (srcdir)))
400     {
401       char * src_full_path =
402         g_build_filename (
403           srcdir_str, filename, NULL);
404       char * dest_full_path =
405         g_build_filename (
406           destdir_str, filename, NULL);
407 
408       bool is_dir =
409         g_file_test (
410           src_full_path, G_FILE_TEST_IS_DIR);
411 
412       /* recurse if necessary */
413       if (recursive && is_dir)
414         {
415           io_copy_dir (
416             dest_full_path, src_full_path,
417             follow_symlinks, recursive);
418         }
419       /* otherwise if not dir, copy file */
420       else if (!is_dir)
421         {
422           GFile * src_file =
423             g_file_new_for_path (src_full_path);
424           GFile * dest_file =
425             g_file_new_for_path (dest_full_path);
426           g_return_if_fail (src_file && dest_file);
427           error = NULL;
428           GFileCopyFlags flags =
429             G_FILE_COPY_OVERWRITE;
430           if (!follow_symlinks)
431             {
432               flags |=
433                 G_FILE_COPY_NOFOLLOW_SYMLINKS;
434             }
435           bool ret =
436             g_file_copy (
437               src_file, dest_file, flags,
438               NULL, NULL, NULL, &error);
439           if (!ret)
440             {
441               g_warning (
442                 "Failed copying file %s to %s: %s",
443                 src_full_path, dest_full_path,
444                 error->message);
445               return;
446             }
447 
448           g_object_unref (src_file);
449           g_object_unref (dest_file);
450         }
451 
452       g_free (src_full_path);
453       g_free (dest_full_path);
454     }
455   g_dir_close (srcdir);
456 }
457 
458 /**
459  * Returns a list of the files in the given
460  * directory.
461  *
462  * @param dir The directory to look for.
463  * @param allow_empty Whether to allow returning
464  *   an empty array that has only NULL, otherwise
465  *   return NULL if empty.
466  *
467  * @return a NULL terminated array of strings that
468  *   must be free'd with g_strfreev() or NULL.
469  */
470 char **
io_get_files_in_dir_ending_in(const char * _dir,const int recursive,const char * end_string,bool allow_empty)471 io_get_files_in_dir_ending_in (
472   const char * _dir,
473   const int    recursive,
474   const char * end_string,
475   bool         allow_empty)
476 {
477   char ** arr = object_new_n (1, char *);
478   int count = 0;
479 
480   append_files_from_dir_ending_in (
481     &arr, &count, recursive, _dir, end_string);
482 
483   if (count == 0 && !allow_empty)
484     {
485       free (arr);
486       return NULL;
487     }
488 
489   return arr;
490 }
491 
492 /**
493  * Returns a newly allocated path that is either
494  * a copy of the original path if the path does
495  * not exist, or the original path appended with
496  * (n), where n is a number.
497  *
498  * Example: "myfile" -> "myfile (1)"
499  */
500 char *
io_get_next_available_filepath(const char * filepath)501 io_get_next_available_filepath (
502   const char * filepath)
503 {
504   int i = 1;
505   char * file_without_ext =
506     io_file_strip_ext (filepath);
507   const char * file_ext = io_file_get_ext (filepath);
508   char * new_path = g_strdup (filepath);
509   while (file_exists (new_path))
510     {
511       if (g_file_test (
512             new_path, G_FILE_TEST_IS_DIR))
513         {
514           g_free (new_path);
515           new_path =
516             g_strdup_printf (
517               "%s (%d)", filepath, i++);
518         }
519       else
520         {
521           g_free (new_path);
522           new_path =
523             g_strdup_printf (
524               "%s (%d).%s",
525               file_without_ext, i++,
526               file_ext);
527         }
528     }
529   g_free (file_without_ext);
530 
531   return new_path;
532 }
533 
534 /* fallback for glib < 2.58 */
535 #if !defined (GLIB_SUBPROJECT) && \
536   !defined (HAVE_G_CANONICALIZE_FILENAME)
537 /**
538  * g_canonicalize_filename:
539  * @filename: (type filename): the name of the file
540  * @relative_to: (type filename) (nullable): the relative directory, or %NULL
541  * to use the current working directory
542  *
543  * Gets the canonical file name from @filename. All triple slashes are turned into
544  * single slashes, and all `..` and `.`s resolved against @relative_to.
545  *
546  * Symlinks are not followed, and the returned path is guaranteed to be absolute.
547  *
548  * If @filename is an absolute path, @relative_to is ignored. Otherwise,
549  * @relative_to will be prepended to @filename to make it absolute. @relative_to
550  * must be an absolute path, or %NULL. If @relative_to is %NULL, it'll fallback
551  * to g_get_current_dir().
552  *
553  * This function never fails, and will canonicalize file paths even if they don't
554  * exist.
555  *
556  * No file system I/O is done.
557  *
558  * Returns: (type filename) (transfer full): a newly allocated string with the
559  * canonical file path
560  * Since: 2.58
561  */
562 static gchar *
g_canonicalize_filename(const gchar * filename,const gchar * relative_to)563 g_canonicalize_filename (const gchar *filename,
564                          const gchar *relative_to)
565 {
566   gchar *canon, *start, *p, *q;
567   guint i;
568 
569   g_return_val_if_fail (relative_to == NULL || g_path_is_absolute (relative_to), NULL);
570 
571   if (!g_path_is_absolute (filename))
572     {
573       gchar *cwd_allocated = NULL;
574       const gchar  *cwd;
575 
576       if (relative_to != NULL)
577         cwd = relative_to;
578       else
579         cwd = cwd_allocated = g_get_current_dir ();
580 
581       canon = g_build_filename (cwd, filename, NULL);
582       g_free (cwd_allocated);
583     }
584   else
585     {
586       canon = g_strdup (filename);
587     }
588 
589   start = (char *)g_path_skip_root (canon);
590 
591   if (start == NULL)
592     {
593       /* This shouldn't really happen, as g_get_current_dir() should
594          return an absolute pathname, but bug 573843 shows this is
595          not always happening */
596       g_free (canon);
597       return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL);
598     }
599 
600   /* POSIX allows double slashes at the start to
601    * mean something special (as does windows too).
602    * So, "//" != "/", but more than two slashes
603    * is treated as "/".
604    */
605   i = 0;
606   for (p = start - 1;
607        (p >= canon) &&
608          G_IS_DIR_SEPARATOR (*p);
609        p--)
610     i++;
611   if (i > 2)
612     {
613       i -= 1;
614       start -= i;
615       memmove (start, start+i, strlen (start+i) + 1);
616     }
617 
618   /* Make sure we're using the canonical dir separator */
619   p++;
620   while (p < start && G_IS_DIR_SEPARATOR (*p))
621     *p++ = G_DIR_SEPARATOR;
622 
623   p = start;
624   while (*p != 0)
625     {
626       if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1])))
627         {
628           memmove (p, p+1, strlen (p+1)+1);
629         }
630       else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2])))
631         {
632           q = p + 2;
633           /* Skip previous separator */
634           p = p - 2;
635           if (p < start)
636             p = start;
637           while (p > start && !G_IS_DIR_SEPARATOR (*p))
638             p--;
639           if (G_IS_DIR_SEPARATOR (*p))
640             *p++ = G_DIR_SEPARATOR;
641           memmove (p, q, strlen (q)+1);
642         }
643       else
644         {
645           /* Skip until next separator */
646           while (*p != 0 && !G_IS_DIR_SEPARATOR (*p))
647             p++;
648 
649           if (*p != 0)
650             {
651               /* Canonicalize one separator */
652               *p++ = G_DIR_SEPARATOR;
653             }
654         }
655 
656       /* Remove additional separators */
657       q = p;
658       while (*q && G_IS_DIR_SEPARATOR (*q))
659         q++;
660 
661       if (p != q)
662         memmove (p, q, strlen (q) + 1);
663     }
664 
665   /* Remove trailing slashes */
666   if (p > start && G_IS_DIR_SEPARATOR (*(p-1)))
667     *(p-1) = 0;
668 
669   return canon;
670 }
671 #endif
672 
673 /**
674  * Opens the given directory using the default
675  * program.
676  */
677 void
io_open_directory(const char * path)678 io_open_directory (
679   const char * path)
680 {
681   g_return_if_fail (
682     g_file_test (path, G_FILE_TEST_IS_DIR));
683 
684   char command[800];
685 #ifdef _WOE32
686   char * canonical_path =
687     g_canonicalize_filename (path, NULL);
688   char * new_path =
689     string_replace (
690       canonical_path, "\\", "\\\\");
691   g_free (canonical_path);
692   sprintf (
693     command, OPEN_DIR_CMD " \"%s\"",
694     new_path);
695   g_free (new_path);
696 #else
697   sprintf (
698     command, OPEN_DIR_CMD " \"%s\"",
699     path);
700 #endif
701   system (command);
702   g_message ("executed: %s", command);
703 }
704 
705 /**
706  * Returns a clone of the given string after
707  * removing forbidden characters.
708  */
709 void
io_escape_dir_name(char * dest,const char * dir)710 io_escape_dir_name (
711   char *       dest,
712   const char * dir)
713 {
714   int len = strlen (dir);
715   strcpy (dest, dir);
716   for (int i = len - 1; i >= 0; i--)
717     {
718       if (dest[i] == '/' ||
719           dest[i] == '>' ||
720           dest[i] == '<' ||
721           dest[i] == '|' ||
722           dest[i] == ':' ||
723           dest[i] == '&' ||
724           dest[i] == '(' ||
725           dest[i] == ')' ||
726           dest[i] == ';' ||
727           dest[i] == '\\')
728         {
729           for (int j = i; j < len; j++)
730             {
731               dest[j] = dest[j + 1];
732             }
733         }
734     }
735 }
736 
737 #ifdef _WOE32
738 char *
io_get_registry_string_val(const char * path)739 io_get_registry_string_val (
740   const char * path)
741 {
742   char value[8192];
743   DWORD BufferSize = 8192;
744   char prefix[500];
745   sprintf (
746     prefix, "Software\\%s\\%s\\Settings",
747     PROGRAM_NAME, PROGRAM_NAME);
748   RegGetValue (
749     HKEY_LOCAL_MACHINE, prefix, path,
750     RRF_RT_ANY, NULL, (PVOID) &value, &BufferSize);
751   g_message ("reg value: %s", value);
752   return g_strdup (value);
753 }
754 #endif
755 
756 #if defined (__APPLE__) && defined (INSTALLER_VER)
757 /**
758  * Gets the bundle path on MacOS.
759  *
760  * @return Non-zero on fail.
761  */
762 int
io_get_bundle_path(char * bundle_path)763 io_get_bundle_path (
764   char * bundle_path)
765 {
766   CFBundleRef bundle =
767     CFBundleGetMainBundle ();
768   CFURLRef bundleURL =
769     CFBundleCopyBundleURL (bundle);
770   Boolean success =
771     CFURLGetFileSystemRepresentation (
772       bundleURL, TRUE,
773       (UInt8 *) bundle_path, PATH_MAX);
774   g_return_val_if_fail (success, -1);
775   CFRelease (bundleURL);
776   g_message ("bundle path: %s", bundle_path);
777 
778   return 0;
779 }
780 #endif
781