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