/*
* 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 (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#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;
/*
* Ensure path is not a symlink to somewhere else. This provides
* a modest amount of protection against easy security attacks.
* Also, set the close-on-exec flag atomically on open().
*/
(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
val = open64(path, oflag | O_NOFOLLOW | O_CLOEXEC, 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);
}