File: paths.c

package info (click to toggle)
libjodycode 4.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,676 kB
  • sloc: ansic: 2,820; makefile: 372; sh: 160; xml: 37
file content (161 lines) | stat: -rw-r--r-- 4,306 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* libjodycode: path manipulation
 *
 * Copyright (C) 2014-2026 by Jody Bruchon <jody@jodybruchon.com>
 * Released under The MIT License
 */

#include <stdio.h>
#include <stdlib.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 '/') */
int jc_collapse_dotdot(char * const path)
{
	char *p; /* string copy input */
	char *out; /* string copy output */
	unsigned int i = 0;

	if (unlikely(*path != '/')) return JC_ENULL;

	p = path; out = path;

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

		/* 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 */
int jc_make_relative_link_name(const char * const src, const char * const dest, char * rel_path)
{
	char *p1, *p2;
	char *sp, *dp, *ss;
	int retval;

	if (unlikely(!src || !dest)) return JC_ENULL;

	p1 = (char *)malloc(JC_PATHBUF_SIZE + 8);
	if (unlikely(p1 == NULL)) return JC_ENOMEM;
	p2 = (char *)malloc(JC_PATHBUF_SIZE + 8);
	if (unlikely(p2 == NULL)) { free(p1); return JC_ENOMEM; }

	/* Get working directory path and prefix to pathnames if needed */
	if (*src != '/' || *dest != '/') {
		jc_errno = 0;
		if (!jc_getcwd(p1, JC_PATHBUF_SIZE)) {
			retval = jc_errno;
			goto finish;
		}
		*(p1 + JC_PATHBUF_SIZE - 1) = '\0';
#ifdef ON_WINDOWS
		strncat_s(p1, JC_PATHBUF_SIZE, "/", JC_PATHBUF_SIZE - 1);
		strncpy_s(p2, JC_PATHBUF_SIZE, p1, JC_PATHBUF_SIZE);
#else
		strncat(p1, "/", JC_PATHBUF_SIZE - 1);
		strncpy(p2, p1, JC_PATHBUF_SIZE);
#endif
	}

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

	/* Concatenate working directory to relative paths */
#ifdef ON_WINDOWS
	strncat_s(p1, JC_PATHBUF_SIZE, src, JC_PATHBUF_SIZE);
	strncat_s(p2, JC_PATHBUF_SIZE, dest, JC_PATHBUF_SIZE);
#else
	strncat(p1, src, JC_PATHBUF_SIZE);
	strncat(p2, dest, JC_PATHBUF_SIZE);
#endif

	/* Collapse . and .. path components */
	if (unlikely(jc_collapse_dotdot(p1) != 0 || jc_collapse_dotdot(p2) != 0)) {
		retval = JC_ECDOTDOT;
		goto finish;
	}

	/* 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') {
		retval = 1;
		goto finish;
	}

	/* 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) == '/') || unlikely(*(rel_path - 1) == '/')) {
			retval = JC_EGRNEND;
		}

	*rel_path = '\0';

finish:
	free(p1); free(p2);
	return retval;
}