summaryrefslogtreecommitdiffstats
path: root/support/junction/path.c
blob: 13a143860b0ed829b7c952f0775942f696c32056 (plain)
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/**
 * @file support/junction/path.c
 * @brief Encode and decode FedFS pathnames
 */

/*
 * Copyright 2010, 2011, 2018 Oracle.  All rights reserved.
 *
 * This file is part of nfs-utils.
 *
 * nfs-utils is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2.0 as
 * published by the Free Software Foundation.
 *
 * nfs-utils is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License version 2.0 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2.0 along with nfs-utils.  If not, see:
 *
 *	http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>

#include <netinet/in.h>

#include "junction.h"
#include "xlog.h"

#define STRLEN_SLASH	((size_t)1)	/* strlen("/") */

#define XDR_UINT_BYTES	(sizeof(uint32_t))

/**
 * Compute count of XDR 4-octet units from byte count
 *
 * @param bytes number of bytes to convert
 * @return equivalent number of XDR 4-octet units
 */
static inline size_t
nsdb_quadlen(size_t bytes)
{
	return (bytes + 3) >> 2;
}

/**
 * Free array of NUL-terminated C strings
 *
 * @param strings array of char * to be released
 */
void
nsdb_free_string_array(char **strings)
{
	int i;

	if (strings == NULL)
		return;
	for (i = 0; strings[i] != NULL; i++)
		free(strings[i]);
	free(strings);
}

static FedFsStatus
nsdb_alloc_zero_component_pathname(char ***path_array)
{
	char **result;

	xlog(D_GENERAL, "%s: Zero-component pathname", __func__);

	result = (char **)calloc(1, sizeof(char *));
	if (result == NULL)
		return FEDFS_ERR_SVRFAULT;
	result[0] = NULL;
	*path_array = result;
	return FEDFS_OK;
}

/**
 * Sanitize an incoming POSIX path
 *
 * @param pathname NUL-terminated C string containing a POSIX pathname
 * @return NUL-terminated C string containing sanitized path
 *
 * Caller must free the returned pathname with free(3).
 *
 * Remove multiple sequential slashes and any trailing slashes,
 * but leave "/" by itself alone.
 */
static __attribute_malloc__ char *
nsdb_normalize_path(const char *pathname)
{
	size_t i, j, len;
	char *result;

	len = strlen(pathname);
	if (len == 0) {
		xlog(D_CALL, "%s: NULL pathname", __func__);
		return NULL;
	}

	result = malloc(len + 1);
	if (result == NULL)
		return NULL;

	for (i = 0, j = 0; i < len; i++) {
		if (pathname[i] == '/' && pathname[i + 1] == '/')
			continue;
		result[j++] = pathname[i];
	}
	result[j] = '\0';

	if (j > 1 && result[j - 1] == '/')
		result[j - 1] = '\0';

	xlog(D_CALL, "%s: result = '%s'", __func__, result);
	return result;
}

/**
 * Count the number of components in a POSIX pathname
 *
 * @param pathname NUL-terminated C string containing a POSIX pathname
 * @param len OUT: number of bytes the encoded XDR stream will consume
 * @param cnt OUT: component count
 * @return true when successful
 */
static _Bool
nsdb_count_components(const char *pathname, size_t *len,
		unsigned int *cnt)
{
	char *start, *component;
	unsigned int count;
	size_t length;

	/* strtok(3) will tromp on the string */
	start = strdup(pathname);
	if (start == NULL)
		return false;

	length = XDR_UINT_BYTES;
	count = 0;
	component = start;
	for ( ;; ) {
		char *next;
		size_t tmp;

		if (*component == '/')
			component++;
		if (*component == '\0')
			break;
		next = strchrnul(component, '/');
		tmp = (size_t)(next - component);
		if (tmp > 255) {
			free(start);
			return false;
		}
		length += XDR_UINT_BYTES + (nsdb_quadlen(tmp) << 2);
		count++;

		if (*next == '\0')
			break;
		component = next;
	}

	free(start);

	xlog(D_CALL, "%s: length = %zu, count = %u, path = '%s'",
		__func__, length, count, pathname);
	*len = length;
	*cnt = count;
	return true;
}

/**
 * Predicate: is input character set for a POSIX pathname valid UTF-8?
 *
 * @param pathname NUL-terminated C string containing a POSIX path
 * @return true if the string is valid UTF-8
 *
 * XXX: implement this
 */
static _Bool
nsdb_pathname_is_utf8(__attribute__((unused)) const char *pathname)
{
	return true;
}

/**
 * Construct a local POSIX-style pathname from an array of component strings
 *
 * @param path_array array of pointers to NUL-terminated C strings
 * @param pathname OUT: pointer to NUL-terminated UTF-8 C string containing a POSIX-style path
 * @return a FedFsStatus code
 *
 * Caller must free the returned pathname with free(3).
 */
FedFsStatus
nsdb_path_array_to_posix(char * const *path_array, char **pathname)
{
	char *component, *result;
	unsigned int i, count;
	size_t length, len;

	if (path_array == NULL || pathname == NULL)
		return FEDFS_ERR_INVAL;

	if (path_array[0] == NULL) {
		xlog(D_GENERAL, "%s: Zero-component pathname", __func__);
		result = strdup("/");
		if (result == NULL)
			return FEDFS_ERR_SVRFAULT;
		*pathname = result;
		return FEDFS_OK;
	}

	for (length = 0, count = 0;
	     path_array[count] != NULL;
	     count++) {
		component = path_array[count];
		len = strlen(component);

		if (len == 0) {
			xlog(D_GENERAL, "%s: Zero-length component", __func__);
			return FEDFS_ERR_BADNAME;
		}
		if (len > NAME_MAX) {
			xlog(D_GENERAL, "%s: Component length too long", __func__);
			return FEDFS_ERR_NAMETOOLONG;
		}
		if (strchr(component, '/') != NULL) {
			xlog(D_GENERAL, "%s: Local separator character "
					"found in component", __func__);
			return FEDFS_ERR_BADNAME;
		}
		if (!nsdb_pathname_is_utf8(component)) {
			xlog(D_GENERAL, "%s: Bad character in component",
				__func__);
			return FEDFS_ERR_BADCHAR;
		}

		length += STRLEN_SLASH + len;

		if (length > PATH_MAX) {
			xlog(D_GENERAL, "%s: Pathname too long", __func__);
			return FEDFS_ERR_NAMETOOLONG;
		}
	}

	result = calloc(1, length + 1);
	if (result == NULL)
		return FEDFS_ERR_SVRFAULT;

	for (i = 0; i < count; i++) {
		strcat(result, "/");
		strcat(result, path_array[i]);
	}
	*pathname = nsdb_normalize_path(result);
	free(result);
	if (*pathname == NULL)
		return FEDFS_ERR_SVRFAULT;
	return FEDFS_OK;
}

/**
 * Construct an array of component strings from a local POSIX-style pathname
 *
 * @param pathname NUL-terminated C string containing a POSIX-style pathname
 * @param path_array OUT: pointer to array of pointers to NUL-terminated C strings
 * @return a FedFsStatus code
 *
 * Caller must free "path_array" with nsdb_free_string_array().
 */
FedFsStatus
nsdb_posix_to_path_array(const char *pathname, char ***path_array)
{
	char *normalized, *component, **result;
	unsigned int i, count;
	size_t length;

	if (pathname == NULL || path_array == NULL)
		return FEDFS_ERR_INVAL;

	if (!nsdb_pathname_is_utf8(pathname)) {
		xlog(D_GENERAL, "%s: Bad character in pathname", __func__);
		return FEDFS_ERR_BADCHAR;
	}

	normalized = nsdb_normalize_path(pathname);
	if (normalized == NULL)
		return FEDFS_ERR_SVRFAULT;

	if (!nsdb_count_components(normalized, &length, &count)) {
		free(normalized);
		return FEDFS_ERR_BADNAME;
	}

	if (count == 0) {
		free(normalized);
		return nsdb_alloc_zero_component_pathname(path_array);
	}

	result = (char **)calloc(count + 1, sizeof(char *));
	if (result == NULL) {
		free(normalized);
		return FEDFS_ERR_SVRFAULT;
	}

	component = normalized;
	for (i = 0; ; i++) {
		char *next;

		if (*component == '/')
			component++;
		if (*component == '\0')
			break;
		next = strchrnul(component, '/');
		length = (size_t)(next - component);
		if (length > 255) {
			nsdb_free_string_array(result);
			free(normalized);
			return FEDFS_ERR_SVRFAULT;
		}

		result[i] = strndup(component, length);
		if (result[i] == NULL) {
			free(normalized);
			nsdb_free_string_array(result);
			return FEDFS_ERR_SVRFAULT;
		}

		if (*next == '\0')
			break;
		component = next;
	}

	*path_array = result;
	free(normalized);
	return FEDFS_OK;
}