File: paths.c

package info (click to toggle)
libjodycode 3.1-3~bpo12%2B1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-backports
  • size: 356 kB
  • sloc: ansic: 1,536; makefile: 170; sh: 155; xml: 9
file content (138 lines) | stat: -rw-r--r-- 4,014 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* Jody Bruchon's path manipulation code library
 *
 * Copyright (C) 2014-2023 by Jody Bruchon <jody@jodybruchon.com>
 * Released under The MIT License
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "likely_unlikely.h"
#include "libjodycode.h"

/* Collapse dot-dot and single dot path components
 * This code MUST be passed a full file pathname (starting with '/') */
extern int jc_collapse_dotdot(char * const path)
{
  char *p;   /* string copy input */
  char *out; /* string copy output */
  unsigned int i = 0;

  /* Fail if not passed an absolute path */
  if (unlikely(*path != '/')) return -1;

  p = path; out = path;

  while (*p != '\0') {
    /* Abort if we're too close to the end of the buffer */
    if (unlikely(i >= (PATHBUF_SIZE - 3))) return -2;

    /* Skip repeated slashes */
    while (*p == '/' && *(p + 1) == '/') {
      p++; i++;
    }

    /* Scan for '/./', '/..', '/.\0' combinations */
    if (*p == '/' && *(p + 1) == '.'
        && (*(p + 2) == '.' || *(p + 2) == '/' || *(p + 2) == '\0')) {
      /* Check for '../' or terminal '..' */
      if (*(p + 2) == '.' && (*(p + 3) == '/' || *(p + 3) == '\0')) {
        /* Found a dot-dot; pull everything back to the previous directory */
        p += 3; i += 3;
        /* If already at root, skip over the dot-dot */
        if (i == 0) continue;
        /* Don't seek back past the first character */
	if ((uintptr_t)out == (uintptr_t)path) continue;
	out--;
        while (*out != '/') out--;
	if (*p == '\0') break;
        continue;
      } else if (*(p + 2) == '/' || *(p + 2) == '\0') {
        /* Found a single dot; seek input ptr past it */
        p += 2; i += 2;
	if (*p == '\0') break;
        continue;
      }
      /* Fall through: not a dot or dot-dot, just a slash */
    }

    /* Copy all remaining text */
    *out = *p;
    p++; out++; i++;
  }

  /* If only a root slash remains, be sure to keep it */
  if ((uintptr_t)out == (uintptr_t)path) {
    *out = '/';
    out++;
  }

  /* Output must always be terminated properly */
  *out = '\0';

  return 0;
}


/* Create a relative symbolic link path for a destination file */
extern int jc_make_relative_link_name(const char * const src,
                const char * const dest, char * rel_path)
{
  static char p1[PATHBUF_SIZE * 2], p2[PATHBUF_SIZE * 2];
  static char *sp, *dp, *ss;

  if (unlikely(!src || !dest)) return -1;

  /* Get working directory path and prefix to pathnames if needed */
  if (*src != '/' || *dest != '/') {
    if (!getcwd(p1, PATHBUF_SIZE * 2)) return -2;
    *(p1 + (PATHBUF_SIZE * 2) - 1) = '\0';
    strncat(p1, "/", PATHBUF_SIZE * 2 - 1);
    strncpy(p2, p1, PATHBUF_SIZE * 2);
  }

  /* If an absolute path is provided, use it as-is */
  if (*src == '/') *p1 = '\0';
  if (*dest == '/') *p2 = '\0';

  /* Concatenate working directory to relative paths */
  strncat(p1, src, PATHBUF_SIZE);
  strncat(p2, dest, PATHBUF_SIZE);

  /* Collapse . and .. path components */
  if (unlikely(jc_collapse_dotdot(p1) != 0)) return -3;
  if (unlikely(jc_collapse_dotdot(p2) != 0)) return -3;

  /* Find where paths differ, remembering each slash along the way */
  sp = p1; dp = p2; ss = p1;
  while (*sp == *dp && *sp != '\0' && *dp != '\0') {
    if (*sp == '/') ss = sp;
    sp++; dp++;
  }
  /* If paths are 100% identical then the files are the same file */
  if (*sp == '\0' && *dp == '\0') return 1;

  /* Replace dirs in destination path with dot-dot */
  while (*dp != '\0') {
    if (*dp == '/') {
      *rel_path++ = '.'; *rel_path++ = '.'; *rel_path++ = '/';
    }
    dp++;
  }

  /* Copy the file name into rel_path and return */
  ss++;
  while (unlikely(*ss != '\0')) *rel_path++ = *ss++;

  /* . and .. dirs at end are invalid */
  if (*(rel_path - 1) == '.')
    if (*(rel_path - 2) == '/' ||
        (*(rel_path - 2) == '.' && *(rel_path - 3) == '/'))
      return -4;
  if (unlikely(*(rel_path - 1) == '/')) return -4;

  *rel_path = '\0';
  return 0;
}