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