/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include "lint.h" #include "mtlib.h" #include <sys/types.h> #include <errno.h> #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <limits.h> #include <pthread.h> #include <thread.h> #include <string.h> #include <dirent.h> #include <stdio.h> #include <dlfcn.h> #include <atomic.h> #include <md5.h> #include "pos4obj.h" #define HASHSTRLEN 32 static char *__pos4obj_name(const char *, const char *); static void __pos4obj_md5toa(unsigned char *, unsigned char *); static void __pos4obj_clean(char *); static char objroot[] = "/tmp/"; static long int name_max = 0; int __open_nc(const char *path, int oflag, mode_t mode) { int cancel_state; int val; struct stat64 statbuf; /* * Ensure path is not a symlink to somewhere else. This provides * a modest amount of protection against easy security attacks. */ if (lstat64(path, &statbuf) == 0) { if (S_ISLNK(statbuf.st_mode)) { errno = EINVAL; return (-1); } } (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); val = open64(path, oflag, mode); (void) pthread_setcancelstate(cancel_state, NULL); return (val); } int __close_nc(int fildes) { int cancel_state; int val; (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); val = close(fildes); (void) pthread_setcancelstate(cancel_state, NULL); return (val); } /* * This is to avoid loading libmd.so.1 unless we absolutely have to. */ typedef void (*md5_calc_t)(unsigned char *, unsigned char *, unsigned int); static md5_calc_t real_md5_calc = NULL; static mutex_t md5_lock = DEFAULTMUTEX; static void load_md5_calc(void) { void *md5_handle = dlopen("libmd.so.1", RTLD_LAZY); md5_calc_t md5_calc = (md5_handle == NULL)? NULL : (md5_calc_t)dlsym(md5_handle, "md5_calc"); lmutex_lock(&md5_lock); if (real_md5_calc == NULL) { if (md5_calc == NULL) real_md5_calc = (md5_calc_t)(-1); else { real_md5_calc = md5_calc; md5_handle = NULL; /* don't dlclose it */ } membar_producer(); } lmutex_unlock(&md5_lock); if (md5_handle) (void) dlclose(md5_handle); } static char * __pos4obj_name(const char *path, const char *type) { int shortpath = 1; int olderrno; size_t len; char *dfile; unsigned char hashbuf[HASHSTRLEN + 1]; unsigned char md5_digest[MD5_DIGEST_LENGTH]; /* * If the path is path_max - strlen(type) characters or less, * the name of the file to use will be the path prefixed by * the type. * * In the special case where the path is longer than * path_max - strlen(type) characters, we create a string based on the * MD5 hash of the path. We prefix that string with a '.' to * make it obscure, and create a directory in objroot with * that name. In that directory, we create a directory named * after the type of object requested. Inside the type * directory, the filename will be the path of the object. This * prevents collisions in all namespaces. * * Example: * Let objroot = "/tmp/", path = "/<longpath>", and type = ".MQD" * Let the MD5 hash of "<longpath>" = "<hash>" * * The desired file is /tmp/.<hash>/.MQD/<longpath> */ /* * Do not include the leading '/' in the path length. * Assumes __pos4obj_check(path) has already been called. */ if ((strlen(path) - 1) > (name_max - strlen(type))) shortpath = 0; if (shortpath) { /* * strlen(path) includes leading slash as space for NUL. */ len = strlen(objroot) + strlen(type) + strlen(path); } else { /* * Long path name. Add 3 for extra '/', '.' and '\0' */ len = strlen(objroot) + HASHSTRLEN + strlen(type) + strlen(path) + 3; } if ((dfile = malloc(len)) == NULL) return (NULL); (void) memset(dfile, 0, len); (void) strcpy(dfile, objroot); if (shortpath) { (void) strcat(dfile, type); (void) strcat(dfile, path + 1); return (dfile); } /* * If we can successfully load it, call md5_calc(). * Otherwise, (this "can't happen") return NULL. */ if (real_md5_calc == NULL) load_md5_calc(); if (real_md5_calc == (md5_calc_t)(-1)) { free(dfile); return (NULL); } real_md5_calc(md5_digest, (unsigned char *)path + 1, strlen(path + 1)); __pos4obj_md5toa(hashbuf, md5_digest); (void) strcat(dfile, "."); (void) strcat(dfile, (const char *)hashbuf); /* * Errno must be preserved across the following calls to * mkdir. This needs to be done to prevent incorrect error * reporting in certain cases. When we attempt to open a * non-existent object without the O_CREAT flag, it will * always create a lock file first. The lock file is created * and then the open is attempted, but fails with ENOENT. The * lock file is then destroyed. In the following code path, we * are finding the absolute path to the lock file after * already having attempted the open (which set errno to * ENOENT). The following calls to mkdir will return -1 and * set errno to EEXIST, since the hash and type directories * were created when the lock file was created. The correct * errno is the ENOENT from the attempted open of the desired * object. */ olderrno = errno; /* * Create hash directory. Use 777 permissions so everyone can use it. */ if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) { if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) { free(dfile); return (NULL); } } else { if (errno != EEXIST) { free(dfile); return (NULL); } } (void) strcat(dfile, "/"); (void) strcat(dfile, type); /* * Create directory for requested type. Use 777 perms so everyone * can use it. */ if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) { if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) { free(dfile); return (NULL); } } else { if (errno != EEXIST) { free(dfile); return (NULL); } } errno = olderrno; (void) strcat(dfile, path); return (dfile); } /* * Takes a 128-bit MD5 digest and transforms to a sequence of 32 ASCII * characters. Output is the hexadecimal representation of the digest. * * The output buffer must be at least HASHSTRLEN + 1 characters * long. HASHSTRLEN is the size of the MD5 digest (128 bits) * divided by the number of bits used per char of output (4). The * extra character at the end is for the NUL terminating character. */ static void __pos4obj_md5toa(unsigned char *dest, unsigned char *src) { int i; uint32_t *p; /* LINTED pointer cast may result in improper alignment */ p = (uint32_t *)src; for (i = 0; i < (MD5_DIGEST_LENGTH / 4); i++) (void) snprintf((char *)dest + (i * 8), 9, "%.8x", *p++); dest[HASHSTRLEN] = '\0'; } /* * This open function assume that there is no simultaneous * open/unlink operation is going on. The caller is supposed * to ensure that both open in O_CREAT mode happen atomically. * It returns the crflag as 1 if file is created else 0. */ int __pos4obj_open(const char *name, char *type, int oflag, mode_t mode, int *crflag) { int fd; char *dfile; errno = 0; *crflag = 0; if ((dfile = __pos4obj_name(name, type)) == NULL) { return (-1); } if (!(oflag & O_CREAT)) { if ((fd = __open_nc(dfile, oflag, mode)) == -1) __pos4obj_clean(dfile); free(dfile); return (fd); } /* * We need to make sure that crflag is set iff we actually create * the file. We do this by or'ing in O_EXCL, and attempting an * open. If that fails with an EEXIST, and O_EXCL wasn't specified * by the caller, then the file seems to exist; we'll try an * open with O_CREAT cleared. If that succeeds, then the file * did indeed exist. If that fails with an ENOENT, however, the * file was removed between the opens; we need to take another * lap. */ for (;;) { if ((fd = __open_nc(dfile, (oflag | O_EXCL), mode)) == -1) { if (errno == EEXIST && !(oflag & O_EXCL)) { fd = __open_nc(dfile, oflag & ~O_CREAT, mode); if (fd == -1 && errno == ENOENT) continue; break; } } else { *crflag = 1; } break; } free(dfile); return (fd); } int __pos4obj_unlink(const char *name, const char *type) { int err; char *dfile; if ((dfile = __pos4obj_name(name, type)) == NULL) { return (-1); } err = unlink(dfile); __pos4obj_clean(dfile); free(dfile); return (err); } /* * This function opens the lock file for each named object * the presence of this file in the file system is the lock */ int __pos4obj_lock(const char *name, const char *ltype) { char *dfile; int fd; int limit = 64; if ((dfile = __pos4obj_name(name, ltype)) == NULL) { return (-1); } while (limit-- > 0) { if ((fd = __open_nc(dfile, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { if (errno != EEXIST) break; (void) sleep(1); continue; } (void) __close_nc(fd); free(dfile); return (1); } free(dfile); return (-1); } /* * Unlocks the file by unlinking it from the filesystem */ int __pos4obj_unlock(const char *path, const char *type) { return (__pos4obj_unlink(path, type)); } /* * Removes unused hash and type directories that may exist in specified path. */ static void __pos4obj_clean(char *path) { char *p; int olderrno; /* * path is either * 1) /<objroot>/<type><path> or * 2) /<objroot>/.<hash>/<type>/<path> * * In case 1, there is nothing to clean. * * Detect case 2 by looking for a '/' after /objroot/ and * remove the two trailing directories, if empty. */ if (strchr(path + strlen(objroot), '/') == NULL) return; /* * Preserve errno across calls to rmdir. See block comment in * __pos4obj_name() for explanation. */ olderrno = errno; if ((p = strrchr(path, '/')) == NULL) return; *p = '\0'; (void) rmdir(path); if ((p = strrchr(path, '/')) == NULL) return; *p = '\0'; (void) rmdir(path); errno = olderrno; } /* * Check that path starts with a /, does not contain a / within it * and is not longer than PATH_MAX or NAME_MAX */ int __pos4obj_check(const char *path) { long int i; /* * This assumes that __pos4obj_check() is called before * any of the other functions in this file */ if (name_max == 0 || name_max == -1) { name_max = pathconf(objroot, _PC_NAME_MAX); if (name_max == -1) return (-1); } if (*path++ != '/') { errno = EINVAL; return (-1); } for (i = 0; *path != '\0'; i++) { if (*path++ == '/') { errno = EINVAL; return (-1); } } if (i > PATH_MAX || i > name_max) { errno = ENAMETOOLONG; return (-1); } return (0); }