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 "synonyms.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 <md5.h> 45 #include "pos4obj.h" 46 47 #define HASHSTRLEN 32 48 49 static char *__pos4obj_name(const char *, const char *); 50 static void __pos4obj_md5toa(unsigned char *, unsigned char *); 51 static void __pos4obj_clean(char *); 52 53 static char objroot[] = "/tmp/"; 54 static long int name_max = 0; 55 56 int 57 __open_nc(const char *path, int oflag, mode_t mode) 58 { 59 int cancel_state; 60 int val; 61 struct stat64 statbuf; 62 63 /* 64 * Ensure path is not a symlink to somewhere else. This provides 65 * a modest amount of protection against easy security attacks. 66 */ 67 if (lstat64(path, &statbuf) == 0) { 68 if (S_ISLNK(statbuf.st_mode)) { 69 errno = EINVAL; 70 return (-1); 71 } 72 } 73 74 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); 75 val = open64(path, oflag, mode); 76 (void) pthread_setcancelstate(cancel_state, NULL); 77 78 return (val); 79 } 80 81 int 82 __close_nc(int fildes) 83 { 84 int cancel_state; 85 int val; 86 87 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); 88 val = close(fildes); 89 (void) pthread_setcancelstate(cancel_state, NULL); 90 91 return (val); 92 } 93 94 /* 95 * This is to avoid loading libmd.so.1 unless we absolutely have to. 96 */ 97 typedef void (*md5_calc_t)(unsigned char *, unsigned char *, unsigned int); 98 static void *md5_handle = NULL; 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 lmutex_lock(&md5_lock); 106 if (real_md5_calc == NULL) { 107 md5_handle = dlopen("libmd.so.1", RTLD_LAZY); 108 if (md5_handle == NULL) 109 real_md5_calc = (md5_calc_t)(-1); 110 else { 111 real_md5_calc = 112 (md5_calc_t)dlsym(md5_handle, "md5_calc"); 113 if (real_md5_calc == NULL) { 114 (void) dlclose(md5_handle); 115 md5_handle = NULL; 116 real_md5_calc = (md5_calc_t)(-1); 117 } 118 } 119 } 120 lmutex_unlock(&md5_lock); 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