1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "path.h"
21
22 #ifndef _WIN32
23 #include <pwd.h> /* getpwnam() */
24 #endif
25 #include <unistd.h>
26
27 #include <assert.h> /* assert() */
28 #ifdef _WIN32
29 #include <ctype.h>
30 #endif
31 #include <errno.h> /* errno */
32 #include <stddef.h> /* NULL size_t */
33 #include <stdio.h> /* snprintf() */
34 #include <stdlib.h> /* malloc() free() */
35 #include <string.h> /* memset() strcat() strcmp() strcasecmp() strdup()
36 strncmp() strncasecmp() strncat() strchr() strcpy()
37 strlen() strrchr() */
38
39 #include "../cfg/config.h"
40 #include "../compat/fs_limits.h"
41 #include "../compat/os.h"
42 #include "../int/path_env.h"
43 #include "env.h"
44 #include "fs.h"
45 #include "str.h"
46 #include "utils.h"
47
48 static int skip_dotdir_if_any(const char *path[], int has_parent);
49 static char * try_replace_tilde(const char path[]);
50 static char * find_ext_dot(const char path[]);
51
52 void
chosp(char path[])53 chosp(char path[])
54 {
55 size_t len;
56
57 if(path[0] == '\0')
58 {
59 return;
60 }
61
62 len = strlen(path);
63 #ifndef _WIN32
64 if(path[len - 1] == '/')
65 #else
66 if(path[len - 1] == '/' || path[len - 1] == '\\')
67 #endif
68 {
69 path[len - 1] = '\0';
70 }
71 }
72
73 int
ends_with_slash(const char * str)74 ends_with_slash(const char *str)
75 {
76 return str[0] != '\0' && str[strlen(str) - 1] == '/';
77 }
78
79 int
path_starts_with(const char path[],const char prefix[])80 path_starts_with(const char path[], const char prefix[])
81 {
82 size_t len = strlen(prefix);
83
84 /* Special case for "/", because it doesn't fit general pattern (slash
85 * shouldn't be stripped or we get empty prefix). */
86 if(prefix[0] == '/' && prefix[1] == '\0')
87 {
88 return (path[0] == '/');
89 }
90
91 if(len > 0U && prefix[len - 1] == '/')
92 {
93 --len;
94 }
95
96 return strnoscmp(path, prefix, len) == 0
97 && (path[len] == '\0' || path[len] == '/');
98 }
99
100 int
paths_are_equal(const char s[],const char t[])101 paths_are_equal(const char s[], const char t[])
102 {
103 /* Some additional space is allocated for adding slashes. */
104 char s_can[strlen(s) + 8];
105 char t_can[strlen(t) + 8];
106
107 canonicalize_path(s, s_can, sizeof(s_can));
108 canonicalize_path(t, t_can, sizeof(t_can));
109
110 return stroscmp(s_can, t_can) == 0;
111 }
112
113 void
canonicalize_path(const char directory[],char buf[],size_t buf_size)114 canonicalize_path(const char directory[], char buf[], size_t buf_size)
115 {
116 /* Source string pointer. */
117 const char *p = directory;
118 /* Destination string pointer. */
119 char *q = buf - 1;
120
121 memset(buf, '\0', buf_size);
122
123 #ifdef _WIN32
124 /* Handle first component of a UNC path. */
125 if(p[0] == '/' && p[1] == '/' && p[2] != '/')
126 {
127 strcpy(buf, "//");
128 q = buf + 1;
129 p += 2;
130 while(*p != '\0' && *p != '/')
131 {
132 *++q = *p++;
133 }
134 buf = q + 1;
135 }
136 #endif
137
138 while(*p != '\0' && (size_t)((q + 1) - buf) < buf_size - 1U)
139 {
140 const int prev_dir_present = (q != buf - 1 && *q == '/');
141 if(skip_dotdir_if_any(&p, prev_dir_present))
142 {
143 /* skip_dotdir_if_any() function did all the job for us. */
144 }
145 else if(prev_dir_present &&
146 (strncmp(p, "../", 3) == 0 || strcmp(p, "..") == 0) &&
147 !(ends_with(buf, "/../") || strcmp(buf, "../") == 0))
148 {
149 /* Remove the last path component added. */
150 #ifdef _WIN32
151 /* Special handling of Windows disk name. */
152 if(*(q - 1) != ':')
153 #endif
154 {
155 ++p;
156 --q;
157 while(q >= buf && *q != '/')
158 {
159 --q;
160 }
161 }
162 }
163 else if(*p == '/')
164 {
165 /* Don't add more than one slash between path components. And don't add
166 * leading slash if initial path is relative. */
167 if(!prev_dir_present && !(q == buf - 1 && *directory != '/'))
168 {
169 *++q = '/';
170 }
171 }
172 else
173 {
174 /* Copy current path component till the end (slash or end of
175 * line/buffer). */
176 *++q = *p;
177 while(p[1] != '\0' && p[1] != '/' &&
178 (size_t)((q + 1) - buf) < buf_size - 1U)
179 {
180 *++q = *++p;
181 }
182 }
183
184 ++p;
185 }
186
187 /* If initial path is relative and we ended up with an empty path, produce
188 * "./". */
189 if(*directory != '/' && q < buf && (size_t)((q + 1) - buf) < buf_size - 2U)
190 {
191 *++q = '.';
192 }
193
194 if((*directory != '\0' && q < buf) || (q >= buf && *q != '/'))
195 {
196 *++q = '/';
197 }
198
199 *++q = '\0';
200 }
201
202 /* Checks whether *path begins with current directory component ('./') and moves
203 * *path to the last character of such component (to slash if present) or to the
204 * previous of the last character depending on has_parent and following
205 * contents of the path. When has_parent is zero the function normalizes
206 * '\.\.\.+/?' prefix on Windows to '\./?'. Returns non-zero if a path
207 * component was fully skipped. */
208 static int
skip_dotdir_if_any(const char * path[],int has_parent)209 skip_dotdir_if_any(const char *path[], int has_parent)
210 {
211 const char *tail;
212
213 size_t dot_count = 0U;
214 while((*path)[dot_count] == '.')
215 {
216 ++dot_count;
217 }
218
219 #ifndef _WIN32
220 if(dot_count != 1U)
221 #else
222 if(dot_count == 0U || dot_count == 2U)
223 #endif
224 {
225 return 0;
226 }
227
228 tail = &(*path)[dot_count];
229 if(tail[0] == '/' || tail[0] == '\0')
230 {
231 const int cut_off = (has_parent || (tail[0] == '/' && tail[1] == '.'));
232 if(!cut_off)
233 {
234 --dot_count;
235 }
236
237 /* Possibly step back one character to do not fall out of the string. */
238 *path += dot_count - ((*path)[dot_count] == '\0' ? 1 : 0);
239 return cut_off;
240 }
241 return 0;
242 }
243
244 const char *
make_rel_path(const char path[],const char base[])245 make_rel_path(const char path[], const char base[])
246 {
247 static char buf[PATH_MAX + 1];
248
249 #ifdef _WIN32
250 if(path[1] == ':' && base[1] == ':' && path[0] != base[0])
251 {
252 canonicalize_path(path, buf, sizeof(buf));
253 return buf;
254 }
255 #endif
256
257 /* Attempt to resolve all symbolic links in paths, this should give both the
258 * shortest and valid relative path. */
259 char path_real[PATH_MAX + 1], base_real[PATH_MAX + 1];
260 if(os_realpath(path, path_real) == path_real)
261 {
262 path = path_real;
263 }
264 if(os_realpath(base, base_real) == base_real)
265 {
266 base = base_real;
267 }
268
269 const char *p = path, *b = base;
270 int i;
271 int nslashes;
272
273 while(p[0] != '\0' && p[1] != '\0' && b[0] != '\0' && b[1] != '\0')
274 {
275 const char *op = p, *ob = b;
276 if((p = strchr(p + 1, '/')) == NULL)
277 p = path + strlen(path);
278 if((b = strchr(b + 1, '/')) == NULL)
279 b = base + strlen(base);
280 if(p - path != b - base || strnoscmp(path, base, p - path) != 0)
281 {
282 p = op;
283 b = ob;
284 break;
285 }
286 }
287
288 canonicalize_path(b, buf, sizeof(buf));
289 chosp(buf);
290
291 nslashes = 0;
292 for(i = 0; buf[i] != '\0'; i++)
293 if(buf[i] == '/')
294 nslashes++;
295
296 buf[0] = '\0';
297 while(nslashes-- > 0)
298 strcat(buf, "../");
299 if(*p == '/')
300 p++;
301 if(*p != '\0')
302 {
303 canonicalize_path(p, buf + strlen(buf), sizeof(buf) - strlen(buf));
304 }
305 chosp(buf);
306
307 if(buf[0] == '\0')
308 strcpy(buf, ".");
309
310 return buf;
311 }
312
313 int
is_path_absolute(const char * path)314 is_path_absolute(const char *path)
315 {
316 #ifdef _WIN32
317 if(isalpha(path[0]) && path[1] == ':')
318 return 1;
319 if(path[0] == '/' && path[1] == '/')
320 return 1;
321 #endif
322 return (path[0] == '/');
323 }
324
325 int
is_root_dir(const char * path)326 is_root_dir(const char *path)
327 {
328 #ifdef _WIN32
329 if(isalpha(path[0]) && stroscmp(path + 1, ":/") == 0)
330 return 1;
331
332 if(path[0] == '/' && path[1] == '/' && path[2] != '\0')
333 {
334 char *p = strchr(path + 2, '/');
335 if(p == NULL || p[1] == '\0')
336 return 1;
337 }
338 #endif
339 return (path[0] == '/' && path[1] == '\0');
340 }
341
342 int
is_unc_root(const char * path)343 is_unc_root(const char *path)
344 {
345 #ifdef _WIN32
346 if(is_unc_path(path) && path[2] != '\0')
347 {
348 char *p = strchr(path + 2, '/');
349 if(p == NULL || p[1] == '\0')
350 return 1;
351 }
352 return 0;
353 #else
354 return 0;
355 #endif
356 }
357
358 char *
shell_like_escape(const char string[],int type)359 shell_like_escape(const char string[], int type)
360 {
361 size_t len;
362 size_t i;
363 char *ret, *dup;
364
365 len = strlen(string);
366
367 dup = ret = malloc(len*3 + 2 + 1);
368
369 if(*string == '-')
370 {
371 *dup++ = '.';
372 *dup++ = '/';
373 }
374
375 for(i = 0; i < len; i++, string++, dup++)
376 {
377 switch(*string)
378 {
379 case '%':
380 if(type == 1)
381 {
382 *dup++ = '%';
383 }
384 break;
385
386 /* Escape the following characters anywhere in the line. */
387 case '\'':
388 case '\\':
389 case '\r':
390 case '\t':
391 case '"':
392 case ';':
393 case ' ':
394 case '?':
395 case '|':
396 case '[':
397 case ']':
398 case '{':
399 case '}':
400 case '<':
401 case '>':
402 case '`':
403 case '!':
404 case '$':
405 case '&':
406 case '*':
407 case '(':
408 case ')':
409 case '#':
410 *dup++ = '\\';
411 break;
412
413 case '\n':
414 if(type != 0)
415 {
416 break;
417 }
418
419 *dup++ = '"';
420 *dup++ = '\n';
421 *dup = '"';
422 continue;
423
424 /* Escape the following characters only at the beginning of the line. */
425 case '~':
426 case '=': /* Command-path expansion in zsh. */
427 if(dup == ret)
428 *dup++ = '\\';
429 break;
430 }
431 *dup = *string;
432 }
433 *dup = '\0';
434 return ret;
435 }
436
437 char *
replace_home_part(const char path[])438 replace_home_part(const char path[])
439 {
440 char *result = replace_home_part_strict(path);
441 if(!is_root_dir(result))
442 {
443 chosp(result);
444 }
445 return result;
446 }
447
448 char *
replace_home_part_strict(const char path[])449 replace_home_part_strict(const char path[])
450 {
451 static char buf[PATH_MAX + 1];
452 size_t len;
453
454 len = strlen(cfg.home_dir) - 1;
455 if(strnoscmp(path, cfg.home_dir, len) == 0 &&
456 (path[len] == '\0' || path[len] == '/'))
457 {
458 strncat(strcpy(buf, "~"), path + len, sizeof(buf) - strlen(buf) - 1);
459 }
460 else
461 {
462 copy_str(buf, sizeof(buf), path);
463 }
464
465 return buf;
466 }
467
468 char *
expand_tilde(const char path[])469 expand_tilde(const char path[])
470 {
471 char *const expanded_path = try_replace_tilde(path);
472 if(expanded_path == path)
473 {
474 return strdup(path);
475 }
476 return expanded_path;
477 }
478
479 char *
replace_tilde(char path[])480 replace_tilde(char path[])
481 {
482 char *const expanded_path = try_replace_tilde(path);
483 if(expanded_path != path)
484 {
485 free(path);
486 }
487 return expanded_path;
488 }
489
490 /* Tries to expands tilde in the front of the path. Returns the path or newly
491 * allocated string without tilde. */
492 static char *
try_replace_tilde(const char path[])493 try_replace_tilde(const char path[])
494 {
495 #ifndef _WIN32
496 char name[NAME_MAX + 1];
497 const char *p;
498 char *result;
499 struct passwd *pw;
500 #endif
501
502 if(path[0] != '~')
503 {
504 return (char *)path;
505 }
506
507 if(path[1] == '\0' || path[1] == '/')
508 {
509 char *const result = format_str("%s%s", cfg.home_dir,
510 (path[1] == '/') ? (path + 2) : "");
511 return result;
512 }
513
514 #ifndef _WIN32
515 if((p = strchr(path, '/')) == NULL)
516 {
517 p = path + strlen(path);
518 copy_str(name, sizeof(name), path + 1);
519 }
520 else
521 {
522 copy_str(name, p - (path + 1) + 1, path + 1);
523 p++;
524 }
525
526 if((pw = getpwnam(name)) == NULL)
527 {
528 return (char *)path;
529 }
530
531 chosp(pw->pw_dir);
532 result = join_paths(pw->pw_dir, p);
533
534 return result;
535 #else
536 return (char *)path;
537 #endif
538 }
539
540 char *
get_last_path_component(const char path[])541 get_last_path_component(const char path[])
542 {
543 char *slash = strrchr(path, '/');
544 if(slash == NULL)
545 {
546 slash = (char *)path;
547 }
548 else if(slash[1] == '\0')
549 {
550 /* Skip slashes. */
551 while(slash > path && slash[0] == '/')
552 {
553 --slash;
554 }
555 /* Skip until slash. */
556 while(slash > path && slash[-1] != '/')
557 {
558 --slash;
559 }
560 }
561 else
562 {
563 ++slash;
564 }
565 return slash;
566 }
567
568 void
remove_last_path_component(char path[])569 remove_last_path_component(char path[])
570 {
571 char *slash;
572
573 /* Get rid of trailing slashes, if any (in rather inefficient way). */
574 while(ends_with_slash(path))
575 {
576 chosp(path);
577 }
578
579 slash = strrchr(path, '/');
580 if(slash == NULL)
581 {
582 /* At most one item of path is left. */
583 path[0] = '\0';
584 return;
585 }
586
587 /* Take care to do not turn path in root into no path (should become just
588 * root). */
589 slash[1] = '\0';
590 if(!is_root_dir(path))
591 {
592 slash[0] = '\0';
593 }
594 }
595
596 int
is_path_well_formed(const char * path)597 is_path_well_formed(const char *path)
598 {
599 #ifndef _WIN32
600 return strchr(path, '/') != NULL;
601 #else
602 return is_unc_path(path) || (strlen(path) >= 2 && path[1] == ':' &&
603 drive_exists(path[0]));
604 #endif
605 }
606
607 void
ensure_path_well_formed(char * path)608 ensure_path_well_formed(char *path)
609 {
610 if(is_path_well_formed(path))
611 {
612 return;
613 }
614
615 #ifndef _WIN32
616 strcpy(path, "/");
617 #else
618 strcpy(path, env_get("SYSTEMDRIVE"));
619 strcat(path, "/");
620 #endif
621 }
622
623 void
to_canonic_path(const char path[],const char base[],char buf[],size_t buf_len)624 to_canonic_path(const char path[], const char base[], char buf[],
625 size_t buf_len)
626 {
627 if(!is_path_absolute(path))
628 {
629 char full_path[PATH_MAX + 1];
630 /* Assert is not level above to keep "." in curr_dir in tests, but this
631 * should be possible to change. */
632 assert(is_path_absolute(base) && "Base path has to be absolute.");
633 snprintf(full_path, sizeof(full_path), "%s/%s", base, path);
634 canonicalize_path(full_path, buf, buf_len);
635 }
636 else
637 {
638 canonicalize_path(path, buf, buf_len);
639 }
640
641 if(!is_root_dir(buf))
642 {
643 chosp(buf);
644 }
645 }
646
647 int
contains_slash(const char * path)648 contains_slash(const char *path)
649 {
650 char *slash_pos = strchr(path, '/');
651 #ifdef _WIN32
652 if(slash_pos == NULL)
653 slash_pos = strchr(path, '\\');
654 #endif
655 return slash_pos != NULL;
656 }
657
658 char *
find_slashr(const char * path)659 find_slashr(const char *path)
660 {
661 char *result = strrchr(path, '/');
662 #ifdef _WIN32
663 if(result == NULL)
664 result = strrchr(path, '\\');
665 #endif
666 return result;
667 }
668
669 char *
cut_extension(char path[])670 cut_extension(char path[])
671 {
672 char *e;
673 char *ext;
674
675 if((ext = strrchr(path, '.')) == NULL)
676 return path + strlen(path);
677
678 *ext = '\0';
679 if((e = strrchr(path, '.')) != NULL && stroscmp(e + 1, "tar") == 0)
680 {
681 *ext = '.';
682 ext = e;
683 }
684 *ext = '\0';
685
686 return ext + 1;
687 }
688
689 void
split_ext(char path[],int * root_len,const char ** ext_pos)690 split_ext(char path[], int *root_len, const char **ext_pos)
691 {
692 char *const dot = find_ext_dot(path);
693
694 if(dot == NULL)
695 {
696 const size_t len = strlen(path);
697
698 *ext_pos = path + len;
699 *root_len = len;
700
701 return;
702 }
703
704 *dot = '\0';
705 *ext_pos = dot + 1;
706 *root_len = dot - path;
707 }
708
709 char *
get_ext(const char path[])710 get_ext(const char path[])
711 {
712 char *const dot = find_ext_dot(path);
713
714 if(dot == NULL)
715 {
716 return (char *)path + strlen(path);
717 }
718
719 return dot + 1;
720 }
721
722 /* Gets extension for file path. Returns pointer to the dot or NULL if file
723 * name has no extension. */
724 static char *
find_ext_dot(const char path[])725 find_ext_dot(const char path[])
726 {
727 const char *const slash = strrchr(path, '/');
728 char *const dot = strrchr(path, '.');
729
730 const int no_ext = dot == NULL
731 || (slash != NULL && dot < slash)
732 || dot == path
733 || dot == slash + 1;
734
735 return no_ext ? NULL : dot;
736 }
737
738 void
exclude_file_name(char path[])739 exclude_file_name(char path[])
740 {
741 if(path_exists(path, DEREF) && !is_valid_dir(path))
742 {
743 remove_last_path_component(path);
744 }
745 }
746
747 int
is_parent_dir(const char path[])748 is_parent_dir(const char path[])
749 {
750 return path[0] == '.' && path[1] == '.' &&
751 ((path[2] == '/' && path[3] == '\0') || path[2] == '\0');
752 }
753
754 int
is_builtin_dir(const char name[])755 is_builtin_dir(const char name[])
756 {
757 return name[0] == '.'
758 && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
759 }
760
761 int
find_cmd_in_path(const char cmd[],size_t path_len,char path[])762 find_cmd_in_path(const char cmd[], size_t path_len, char path[])
763 {
764 size_t i;
765 size_t paths_count;
766 char **paths;
767
768 paths = get_paths(&paths_count);
769 for(i = 0; i < paths_count; i++)
770 {
771 char tmp_path[PATH_MAX + 1];
772 snprintf(tmp_path, sizeof(tmp_path), "%s/%s", paths[i], cmd);
773
774 /* Need to check for executable, not just a file, as this additionally
775 * checks for path with different executable extensions on Windows. */
776 if(executable_exists(tmp_path))
777 {
778 if(path != NULL)
779 {
780 copy_str(path, path_len, tmp_path);
781 }
782 return 0;
783 }
784 }
785 return 1;
786 }
787
788 void
generate_tmp_file_name(const char prefix[],char buf[],size_t buf_len)789 generate_tmp_file_name(const char prefix[], char buf[], size_t buf_len)
790 {
791 snprintf(buf, buf_len, "%s/%s", get_tmpdir(), prefix);
792 system_to_internal_slashes(buf);
793 copy_str(buf, buf_len, make_name_unique(buf));
794 }
795
796 const char *
get_tmpdir(void)797 get_tmpdir(void)
798 {
799 return env_get_one_of_def("/tmp/", "TMPDIR", "TEMP", "TEMPDIR", "TMP",
800 (const char *)NULL);
801 }
802
803 void
build_path(char buf[],size_t buf_len,const char p1[],const char p2[])804 build_path(char buf[], size_t buf_len, const char p1[], const char p2[])
805 {
806 snprintf(buf, buf_len, "%s%s%s", p1, ends_with_slash(p1) ? "" : "/", p2);
807 }
808
809 char *
join_paths(const char p1[],const char p2[])810 join_paths(const char p1[], const char p2[])
811 {
812 const char *slash = (ends_with_slash(p1) ? "" : "/");
813 p2 = skip_char(p2, '/');
814 return format_str("%s%s%s", p1, slash, p2);
815 }
816
817 #ifdef _WIN32
818
819 int
is_unc_path(const char path[])820 is_unc_path(const char path[])
821 {
822 return (path[0] == '/' && path[1] == '/' && path[2] != '/');
823 }
824
825 void
system_to_internal_slashes(char path[])826 system_to_internal_slashes(char path[])
827 {
828 replace_char(path, '\\', '/');
829 }
830
831 void
internal_to_system_slashes(char path[])832 internal_to_system_slashes(char path[])
833 {
834 replace_char(path, '/', '\\');
835 }
836
837 #endif
838
839 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
840 /* vim: set cinoptions+=t0 filetype=c : */
841