xref: /illumos-gate/usr/src/lib/libc/port/rt/pos4obj.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include "lint.h"
28 #include "mtlib.h"
29 #include <sys/types.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <limits.h>
36 #include <pthread.h>
37 #include <thread.h>
38 #include <string.h>
39 #include <dirent.h>
40 #include <stdio.h>
41 #include <dlfcn.h>
42 #include <atomic.h>
43 #include <md5.h>
44 #include "pos4obj.h"
45 
46 #define	HASHSTRLEN	32
47 
48 static	char	*__pos4obj_name(const char *, const char *);
49 static	void	__pos4obj_md5toa(unsigned char *, unsigned char *);
50 static	void	__pos4obj_clean(char *);
51 
52 static	char	objroot[] = "/tmp/";
53 static	long int	name_max = 0;
54 
55 int
56 __open_nc(const char *path, int oflag, mode_t mode)
57 {
58 	int		cancel_state;
59 	int		val;
60 	struct stat64	statbuf;
61 
62 	/*
63 	 * Ensure path is not a symlink to somewhere else. This provides
64 	 * a modest amount of protection against easy security attacks.
65 	 */
66 	if (lstat64(path, &statbuf) == 0) {
67 		if (S_ISLNK(statbuf.st_mode)) {
68 			errno = EINVAL;
69 			return (-1);
70 		}
71 	}
72 
73 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
74 	val = open64(path, oflag, mode);
75 	(void) pthread_setcancelstate(cancel_state, NULL);
76 
77 	return (val);
78 }
79 
80 int
81 __close_nc(int fildes)
82 {
83 	int	cancel_state;
84 	int	val;
85 
86 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
87 	val = close(fildes);
88 	(void) pthread_setcancelstate(cancel_state, NULL);
89 
90 	return (val);
91 }
92 
93 /*
94  * This is to avoid loading libmd.so.1 unless we absolutely have to.
95  */
96 typedef void (*md5_calc_t)(unsigned char *, unsigned char *, unsigned int);
97 static md5_calc_t real_md5_calc = NULL;
98 static mutex_t md5_lock = DEFAULTMUTEX;
99 
100 static void
101 load_md5_calc(void)
102 {
103 	void *md5_handle = dlopen("libmd.so.1", RTLD_LAZY);
104 	md5_calc_t md5_calc = (md5_handle == NULL)? NULL :
105 	    (md5_calc_t)dlsym(md5_handle, "md5_calc");
106 
107 	lmutex_lock(&md5_lock);
108 	if (real_md5_calc == NULL) {
109 		if (md5_calc == NULL)
110 			real_md5_calc = (md5_calc_t)(-1);
111 		else {
112 			real_md5_calc = md5_calc;
113 			md5_handle = NULL;	/* don't dlclose it */
114 		}
115 		membar_producer();
116 	}
117 	lmutex_unlock(&md5_lock);
118 
119 	if (md5_handle)
120 		(void) dlclose(md5_handle);
121 }
122 
123 static char *
124 __pos4obj_name(const char *path, const char *type)
125 {
126 	int	shortpath = 1;
127 	int	olderrno;
128 	size_t	len;
129 	char	*dfile;
130 	unsigned char	hashbuf[HASHSTRLEN + 1];
131 	unsigned char	md5_digest[MD5_DIGEST_LENGTH];
132 
133 	/*
134 	 * If the path is path_max - strlen(type) characters or less,
135 	 * the name of the file to use will be the path prefixed by
136 	 * the type.
137 	 *
138 	 * In the special case where the path is longer than
139 	 * path_max - strlen(type) characters, we create a string based on the
140 	 * MD5 hash of the path. We prefix that string with a '.' to
141 	 * make it obscure, and create a directory in objroot with
142 	 * that name. In that directory, we create a directory named
143 	 * after the type of object requested.  Inside the type
144 	 * directory, the filename will be the path of the object. This
145 	 * prevents collisions in all namespaces.
146 	 *
147 	 * Example:
148 	 * Let objroot = "/tmp/", path = "/<longpath>", and type = ".MQD"
149 	 * Let the MD5 hash of "<longpath>" = "<hash>"
150 	 *
151 	 * The desired file is /tmp/.<hash>/.MQD/<longpath>
152 	 */
153 
154 	/*
155 	 * Do not include the leading '/' in the path length.
156 	 * Assumes __pos4obj_check(path) has already been called.
157 	 */
158 	if ((strlen(path) - 1) > (name_max - strlen(type)))
159 		shortpath = 0;
160 
161 	if (shortpath) {
162 		/*
163 		 * strlen(path) includes leading slash as space for NUL.
164 		 */
165 		len = strlen(objroot) + strlen(type) + strlen(path);
166 	} else {
167 		/*
168 		 * Long path name. Add 3 for extra '/', '.' and '\0'
169 		 */
170 		len = strlen(objroot) + HASHSTRLEN + strlen(type) +
171 		    strlen(path) + 3;
172 	}
173 
174 	if ((dfile = malloc(len)) == NULL)
175 		return (NULL);
176 
177 	(void) memset(dfile, 0, len);
178 	(void) strcpy(dfile, objroot);
179 
180 	if (shortpath) {
181 		(void) strcat(dfile, type);
182 		(void) strcat(dfile, path + 1);
183 		return (dfile);
184 	}
185 
186 	/*
187 	 * If we can successfully load it, call md5_calc().
188 	 * Otherwise, (this "can't happen") return NULL.
189 	 */
190 	if (real_md5_calc == NULL)
191 		load_md5_calc();
192 	if (real_md5_calc == (md5_calc_t)(-1)) {
193 		free(dfile);
194 		return (NULL);
195 	}
196 
197 	real_md5_calc(md5_digest, (unsigned char *)path + 1, strlen(path + 1));
198 	__pos4obj_md5toa(hashbuf, md5_digest);
199 	(void) strcat(dfile, ".");
200 	(void) strcat(dfile, (const char *)hashbuf);
201 
202 	/*
203 	 * Errno must be preserved across the following calls to
204 	 * mkdir.  This needs to be done to prevent incorrect error
205 	 * reporting in certain cases. When we attempt to open a
206 	 * non-existent object without the O_CREAT flag, it will
207 	 * always create a lock file first.  The lock file is created
208 	 * and then the open is attempted, but fails with ENOENT. The
209 	 * lock file is then destroyed. In the following code path, we
210 	 * are finding the absolute path to the lock file after
211 	 * already having attempted the open (which set errno to
212 	 * ENOENT). The following calls to mkdir will return -1 and
213 	 * set errno to EEXIST, since the hash and type directories
214 	 * were created when the lock file was created. The correct
215 	 * errno is the ENOENT from the attempted open of the desired
216 	 * object.
217 	 */
218 	olderrno = errno;
219 
220 	/*
221 	 * Create hash directory. Use 777 permissions so everyone can use it.
222 	 */
223 	if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) {
224 		if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
225 			free(dfile);
226 			return (NULL);
227 		}
228 	} else {
229 		if (errno != EEXIST) {
230 			free(dfile);
231 			return (NULL);
232 		}
233 	}
234 
235 	(void) strcat(dfile, "/");
236 	(void) strcat(dfile, type);
237 
238 	/*
239 	 * Create directory for requested type. Use 777 perms so everyone
240 	 * can use it.
241 	 */
242 	if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) {
243 		if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
244 			free(dfile);
245 			return (NULL);
246 		}
247 	} else {
248 		if (errno != EEXIST) {
249 			free(dfile);
250 			return (NULL);
251 		}
252 	}
253 
254 	errno = olderrno;
255 	(void) strcat(dfile, path);
256 	return (dfile);
257 }
258 
259 /*
260  * Takes a 128-bit MD5 digest and transforms to a sequence of 32 ASCII
261  * characters. Output is the hexadecimal representation of the digest.
262  *
263  * The output buffer must be at least HASHSTRLEN + 1 characters
264  * long.  HASHSTRLEN is the size of the MD5 digest (128 bits)
265  * divided by the number of bits used per char of output (4). The
266  * extra character at the end is for the NUL terminating character.
267  */
268 
269 static void
270 __pos4obj_md5toa(unsigned char *dest, unsigned char *src)
271 {
272 	int i;
273 	uint32_t *p;
274 
275 	/* LINTED pointer cast may result in improper alignment */
276 	p = (uint32_t *)src;
277 
278 	for (i = 0; i < (MD5_DIGEST_LENGTH / 4); i++)
279 		(void) snprintf((char *)dest + (i * 8), 9, "%.8x", *p++);
280 
281 	dest[HASHSTRLEN] = '\0';
282 }
283 
284 /*
285  * This open function assume that there is no simultaneous
286  * open/unlink operation is going on. The caller is supposed
287  * to ensure that both open in O_CREAT mode happen atomically.
288  * It returns the crflag as 1 if file is created else 0.
289  */
290 int
291 __pos4obj_open(const char *name, char *type, int oflag,
292 		mode_t mode, int *crflag)
293 {
294 	int fd;
295 	char *dfile;
296 
297 	errno = 0;
298 	*crflag = 0;
299 
300 	if ((dfile = __pos4obj_name(name, type)) == NULL) {
301 		return (-1);
302 	}
303 
304 	if (!(oflag & O_CREAT)) {
305 		if ((fd = __open_nc(dfile, oflag, mode)) == -1)
306 			__pos4obj_clean(dfile);
307 
308 		free(dfile);
309 		return (fd);
310 	}
311 
312 	/*
313 	 * We need to make sure that crflag is set iff we actually create
314 	 * the file.  We do this by or'ing in O_EXCL, and attempting an
315 	 * open.  If that fails with an EEXIST, and O_EXCL wasn't specified
316 	 * by the caller, then the file seems to exist;  we'll try an
317 	 * open with O_CREAT cleared.  If that succeeds, then the file
318 	 * did indeed exist.  If that fails with an ENOENT, however, the
319 	 * file was removed between the opens;  we need to take another
320 	 * lap.
321 	 */
322 	for (;;) {
323 		if ((fd = __open_nc(dfile, (oflag | O_EXCL), mode)) == -1) {
324 			if (errno == EEXIST && !(oflag & O_EXCL)) {
325 				fd = __open_nc(dfile, oflag & ~O_CREAT, mode);
326 
327 				if (fd == -1 && errno == ENOENT)
328 					continue;
329 				break;
330 			}
331 		} else {
332 			*crflag = 1;
333 		}
334 		break;
335 	}
336 
337 	free(dfile);
338 	return (fd);
339 }
340 
341 
342 int
343 __pos4obj_unlink(const char *name, const char *type)
344 {
345 	int	err;
346 	char	*dfile;
347 
348 	if ((dfile = __pos4obj_name(name, type)) == NULL) {
349 		return (-1);
350 	}
351 
352 	err = unlink(dfile);
353 
354 	__pos4obj_clean(dfile);
355 
356 	free(dfile);
357 
358 	return (err);
359 }
360 
361 /*
362  * This function opens the lock file for each named object
363  * the presence of this file in the file system is the lock
364  */
365 int
366 __pos4obj_lock(const char *name, const char *ltype)
367 {
368 	char	*dfile;
369 	int	fd;
370 	int	limit = 64;
371 
372 	if ((dfile = __pos4obj_name(name, ltype)) == NULL) {
373 		return (-1);
374 	}
375 
376 	while (limit-- > 0) {
377 		if ((fd = __open_nc(dfile, O_RDWR | O_CREAT | O_EXCL, 0666))
378 		    < 0) {
379 			if (errno != EEXIST)
380 				break;
381 			(void) sleep(1);
382 			continue;
383 		}
384 
385 		(void) __close_nc(fd);
386 		free(dfile);
387 		return (1);
388 	}
389 
390 	free(dfile);
391 	return (-1);
392 }
393 
394 /*
395  * Unlocks the file by unlinking it from the filesystem
396  */
397 int
398 __pos4obj_unlock(const char *path, const char *type)
399 {
400 	return (__pos4obj_unlink(path, type));
401 }
402 
403 /*
404  * Removes unused hash and type directories that may exist in specified path.
405  */
406 static void
407 __pos4obj_clean(char *path)
408 {
409 	char	*p;
410 	int	olderrno;
411 
412 	/*
413 	 * path is either
414 	 * 1) /<objroot>/<type><path>  or
415 	 * 2) /<objroot>/.<hash>/<type>/<path>
416 	 *
417 	 * In case 1, there is nothing to clean.
418 	 *
419 	 * Detect case 2 by looking for a '/' after /objroot/ and
420 	 * remove the two trailing directories, if empty.
421 	 */
422 	if (strchr(path + strlen(objroot), '/') == NULL)
423 		return;
424 
425 	/*
426 	 * Preserve errno across calls to rmdir. See block comment in
427 	 * __pos4obj_name() for explanation.
428 	 */
429 	olderrno = errno;
430 
431 	if ((p = strrchr(path, '/')) == NULL)
432 		return;
433 	*p = '\0';
434 
435 	(void) rmdir(path);
436 
437 	if ((p = strrchr(path, '/')) == NULL)
438 		return;
439 	*p = '\0';
440 
441 	(void) rmdir(path);
442 
443 	errno = olderrno;
444 }
445 
446 
447 /*
448  * Check that path starts with a /, does not contain a / within it
449  * and is not longer than PATH_MAX or NAME_MAX
450  */
451 int
452 __pos4obj_check(const char *path)
453 {
454 	long int	i;
455 
456 	/*
457 	 * This assumes that __pos4obj_check() is called before
458 	 * any of the other functions in this file
459 	 */
460 	if (name_max == 0 || name_max == -1) {
461 		name_max = pathconf(objroot, _PC_NAME_MAX);
462 		if (name_max == -1)
463 			return (-1);
464 	}
465 
466 	if (*path++ != '/') {
467 		errno = EINVAL;
468 		return (-1);
469 	}
470 
471 	for (i = 0; *path != '\0'; i++) {
472 		if (*path++ == '/') {
473 			errno = EINVAL;
474 			return (-1);
475 		}
476 	}
477 
478 	if (i > PATH_MAX || i > name_max) {
479 		errno = ENAMETOOLONG;
480 		return (-1);
481 	}
482 
483 	return (0);
484 }
485