/*
* Copyright (c) 2011 Collabora Ltd.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * The names of contributors to this software may not be
* used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* Author: Stef Walter <stefw@collabora.co.uk>
*/
#include "config.h"
/*
* This is needed to expose pthread_mutexattr_settype and PTHREAD_MUTEX_DEFAULT
* on older pthreads implementations
*/
#define _XOPEN_SOURCE 700
#if defined(HAVE_ISSETUGID) && defined(__FreeBSD__)
#define __BSD_VISIBLE 1
#endif
#include "compat.h"
#include "debug.h"
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/*-
* Portions of this file are covered by the following copyright:
*
* Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org>
* Copyright (c) 1990, 1993
* Copyright (c) 1987, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef HAVE_GETPROGNAME
#ifdef OS_UNIX
#include <unistd.h>
#if defined (HAVE_PROGRAM_INVOCATION_SHORT_NAME) && !HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME
extern char *program_invocation_short_name;
#endif
#if defined (HAVE___PROGNAME) && !HAVE_DECL___PROGNAME
extern char *__progname;
#endif
const char *
getprogname (void)
{
const char *name;
#if defined (HAVE_GETEXECNAME)
const char *p;
name = getexecname();
p = strrchr (name ? name : "", '/');
if (p != NULL)
name = p + 1;
#elif defined (HAVE_PROGRAM_INVOCATION_SHORT_NAME)
name = program_invocation_short_name;
#elif defined (HAVE___PROGNAME)
name = __progname;
#else
#error No way to retrieve short program name
#endif
return name;
}
#else /* OS_WIN32 */
extern char **__argv;
static char prognamebuf[256];
const char *
getprogname (void)
{
const char *name;
const char *p, *p2;
size_t length;
name = __argv[0];
if (name == NULL)
return NULL;
p = strrchr (name, '\\');
p2 = strrchr (name, '/');
if (p2 > p)
p = p2;
if (p != NULL)
name = p + 1;
length = sizeof (prognamebuf) - 1;
strncpy (prognamebuf, name, length);
prognamebuf[length] = 0;
length = strlen (prognamebuf);
if (length > 4 && _stricmp (prognamebuf + (length - 4), ".exe") == 0)
prognamebuf[length - 4] = '\0';
return prognamebuf;
}
#endif /* OS_WIN32 */
#endif /* HAVE_GETPROGNAME */
#ifdef OS_UNIX
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
void
p11_recursive_mutex_init (p11_mutex_t *mutex)
{
pthread_mutexattr_t attr;
int ret;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
ret = pthread_mutex_init (mutex, &attr);
assert (ret == 0);
pthread_mutexattr_destroy (&attr);
}
#endif
char *
p11_dl_error (void)
{
const char *msg = dlerror ();
return msg ? strdup (msg) : NULL;
}
struct _p11_mmap {
int fd;
void *data;
size_t size;
};
p11_mmap *
p11_mmap_open (const char *path,
struct stat *sb,
void **data,
size_t *size)
{
struct stat stb;
p11_mmap *map;
map = calloc (1, sizeof (p11_mmap));
if (map == NULL)
return NULL;
map->fd = open (path, O_RDONLY | O_CLOEXEC);
if (map->fd == -1) {
free (map);
return NULL;
}
if (sb == NULL) {
sb = &stb;
if (fstat (map->fd, &stb) < 0) {
close (map->fd);
free (map);
return NULL;
}
}
/* Workaround for broken ZFS on Linux */
if (S_ISDIR (sb->st_mode)) {
errno = EISDIR;
close (map->fd);
free (map);
return NULL;
}
if (sb->st_size == 0) {
*data = "";
*size = 0;
return map;
}
map->size = sb->st_size;
map->data = mmap (NULL, map->size, PROT_READ, MAP_PRIVATE, map->fd, 0);
if (map->data == MAP_FAILED) {
close (map->fd);
free (map);
return NULL;
}
*data = map->data;
*size = map->size;
return map;
}
void
p11_mmap_close (p11_mmap *map)
{
if (map->size)
munmap (map->data, map->size);
close (map->fd);
free (map);
}
#endif /* OS_UNIX */
#ifdef OS_WIN32
char *
p11_dl_error (void)
{
DWORD code = GetLastError();
LPVOID msg_buf;
FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, code,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg_buf, 0, NULL);
return msg_buf;
}
int
p11_thread_create (p11_thread_t *thread,
p11_thread_routine routine,
void *arg)
{
assert (thread);
*thread = CreateThread (NULL, 0,
(LPTHREAD_START_ROUTINE)routine,
arg, 0, NULL);
if (*thread == NULL)
return GetLastError ();
return 0;
}
int
p11_thread_join (p11_thread_t thread)
{
DWORD res;
res = WaitForSingleObject (thread, INFINITE);
if (res == WAIT_FAILED)
return GetLastError ();
CloseHandle (thread);
return 0;
}
struct _p11_mmap {
HANDLE file;
HANDLE mapping;
void *data;
};
p11_mmap *
p11_mmap_open (const char *path,
struct stat *sb,
void **data,
size_t *size)
{
HANDLE mapping;
LARGE_INTEGER large;
DWORD errn;
p11_mmap *map;
map = calloc (1, sizeof (p11_mmap));
if (map == NULL) {
errno = ENOMEM;
return NULL;
}
map->file = CreateFile (path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
if (map->file == INVALID_HANDLE_VALUE) {
errn = GetLastError ();
free (map);
SetLastError (errn);
if (errn == ERROR_PATH_NOT_FOUND || errn == ERROR_FILE_NOT_FOUND)
errno = ENOENT;
else if (errn == ERROR_ACCESS_DENIED)
errno = EPERM;
return NULL;
}
if (sb == NULL) {
if (!GetFileSizeEx (map->file, &large)) {
errn = GetLastError ();
CloseHandle (map->file);
free (map);
SetLastError (errn);
if (errn == ERROR_ACCESS_DENIED)
errno = EPERM;
return NULL;
}
} else {
large.QuadPart = sb->st_size;
}
mapping = CreateFileMapping (map->file, NULL, PAGE_READONLY, 0, 0, NULL);
if (!mapping) {
errn = GetLastError ();
CloseHandle (map->file);
free (map);
SetLastError (errn);
if (errn == ERROR_ACCESS_DENIED)
errno = EPERM;
return NULL;
}
map->data = MapViewOfFile (mapping, FILE_MAP_READ, 0, 0, large.QuadPart);
CloseHandle (mapping);
if (map->data == NULL) {
errn = GetLastError ();
CloseHandle (map->file);
free (map);
SetLastError (errn);
if (errn == ERROR_ACCESS_DENIED)
errno = EPERM;
return NULL;
}
*data = map->data;
*size = large.QuadPart;
return map;
}
void
p11_mmap_close (p11_mmap *map)
{
UnmapViewOfFile (map->data);
CloseHandle (map->file);
free (map);
}
#endif /* OS_WIN32 */
#ifndef HAVE_STRNSTR
#include <string.h>
/*
* Find the first occurrence of find in s, where the search is limited to the
* first slen characters of s.
*/
char *
strnstr (const char *s,
const char *find,
size_t slen)
{
char c, sc;
size_t len;
if ((c = *find++) != '\0') {
len = strlen (find);
do {
do {
if (slen-- < 1 || (sc = *s++) == '\0')
return (NULL);
} while (sc != c);
if (len > slen)
return (NULL);
} while (strncmp(s, find, len) != 0);
s--;
}
return ((char *)s);
}
#endif /* HAVE_STRNSTR */
#ifndef HAVE_MEMDUP
void *
memdup (const void *data,
size_t length)
{
void *dup;
if (!data)
return NULL;
dup = malloc (length);
if (dup != NULL)
memcpy (dup, data, length);
return dup;
}
#endif /* HAVE_MEMDUP */
/*
* WORKAROUND: So in lots of released builds of firefox a completely broken strndup()
* is present. It does not NULL terminate its string output. It is unconditionally
* defined, and overrides the libc strndup() function on platforms where it
* exists as a function. For this reason we (for now) unconditionally define
* strndup().
*/
#if 1 /* #ifndef HAVE_STRNDUP */
/*
* HAVE_STRNDUP may be undefined if strndup() isn't working. So it may be
* present, and yet strndup may still be a defined header macro.
*/
#ifdef strndup
#undef strndup
#endif
char *
strndup (const char *data,
size_t length);
char *
strndup (const char *data,
size_t length)
{
char *ret;
ret = malloc (length + 1);
if (ret != NULL) {
strncpy (ret, data, length);
ret[length] = 0;
}
return ret;
}
#endif /* HAVE_STRNDUP */
#ifndef HAVE_REALLOCARRAY
void *
reallocarray (void *ptr,
size_t nmemb,
size_t size)
{
assert (nmemb > 0 && size > 0);
if (SIZE_MAX / nmemb < size) {
errno = ENOMEM;
return NULL;
}
return realloc (ptr, nmemb * size);
}
#endif /* HAVE_MEMDUP */
#ifndef HAVE_STRCONCAT
#include <stdarg.h>
char *
strconcat (const char *first,
...)
{
size_t length = 0;
const char *arg;
char *result, *at;
va_list va;
va_start (va, first);
for (arg = first; arg; arg = va_arg (va, const char*)) {
size_t old_length = length;
length += strlen (arg);
if (length < old_length) {
va_end (va);
return_val_if_reached (NULL);
}
}
va_end (va);
at = result = malloc (length + 1);
if (result == NULL)
return NULL;
va_start (va, first);
for (arg = first; arg; arg = va_arg (va, const char*)) {
length = strlen (arg);
memcpy (at, arg, length);
at += length;
}
va_end (va);
*at = 0;
return result;
}
#endif /* HAVE_STRCONCAT */
#ifndef HAVE_VASPRINTF
#include <stdio.h>
int vasprintf(char **strp, const char *fmt, va_list ap);
int
vasprintf (char **strp,
const char *fmt,
va_list ap)
{
char *buf = NULL;
char *nbuf;
int guess = 128;
int length = 0;
int ret;
if (fmt == NULL) {
errno = EINVAL;
return -1;
}
for (;;) {
nbuf = realloc (buf, guess);
if (!nbuf) {
free (buf);
return -1;
}
buf = nbuf;
length = guess;
ret = vsnprintf (buf, length, fmt, ap);
if (ret < 0)
guess *= 2;
else if (ret >= length)
guess = ret + 1;
else
break;
}
*strp = buf;
return ret;
}
#endif /* HAVE_VASPRINTF */
#ifndef HAVE_ASPRINTF
int asprintf(char **strp, const char *fmt, ...);
int
asprintf (char **strp,
const char *fmt,
...)
{
va_list va;
int ret;
va_start (va, fmt);
ret = vasprintf (strp, fmt, va);
va_end (va);
return ret;
}
#endif /* HAVE_ASPRINTF */
#ifndef HAVE_GMTIME_R
struct tm *
gmtime_r (const time_t *timep,
struct tm *result)
{
#ifdef OS_WIN32
/*
* On win32 gmtime() returns thread local storage, so we can
* just copy it out into the buffer without worrying about races.
*/
struct tm *tg;
tg = gmtime (timep);
if (!tg)
return NULL;
memcpy (result, tg, sizeof (struct tm));
return result;
#else
#error Need either gmtime_r() function on Unix
#endif
}
#endif /* HAVE_GMTIME_R */
#if !defined(HAVE_MKDTEMP) || !defined(HAVE_MKSTEMP)
#include <sys/stat.h>
#include <fcntl.h>
static int
_gettemp (char *path,
int *doopen,
int domkdir,
int slen)
{
static const char padchar[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static const int maxpathlen = 1024;
char *start, *trv, *suffp, *carryp;
char *pad;
struct stat sbuf;
int rval;
int rnd;
char carrybuf[maxpathlen];
if ((doopen != NULL && domkdir) || slen < 0) {
errno = EINVAL;
return (0);
}
for (trv = path; *trv != '\0'; ++trv)
;
if (trv - path >= maxpathlen) {
errno = ENAMETOOLONG;
return (0);
}
trv -= slen;
suffp = trv;
--trv;
if (trv < path || NULL != strchr (suffp, '/')) {
errno = EINVAL;
return (0);
}
/* Fill space with random characters */
while (trv >= path && *trv == 'X') {
rnd = rand () % (sizeof (padchar) - 1);
*trv-- = padchar[rnd];
}
start = trv + 1;
/* save first combination of random characters */
memcpy (carrybuf, start, suffp - start);
/*
* check the target directory.
*/
if (doopen != NULL || domkdir) {
for (; trv > path; --trv) {
if (*trv == '/') {
*trv = '\0';
rval = stat(path, &sbuf);
*trv = '/';
if (rval != 0)
return (0);
if (!S_ISDIR(sbuf.st_mode)) {
errno = ENOTDIR;
return (0);
}
break;
}
}
}
for (;;) {
if (doopen) {
if ((*doopen = open (path, O_BINARY | O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600)) >= 0)
return (1);
if (errno != EEXIST)
return (0);
} else if (domkdir) {
#ifdef OS_UNIX
if (mkdir (path, 0700) == 0)
#else
if (mkdir (path) == 0)
#endif
return (1);
if (errno != EEXIST)
return (0);
#ifdef OS_UNIX
} else if (lstat (path, &sbuf))
#else
} else if (stat (path, &sbuf))
#endif
return (errno == ENOENT);
/* If we have a collision, cycle through the space of filenames */
for (trv = start, carryp = carrybuf;;) {
/* have we tried all possible permutations? */
if (trv == suffp)
return (0); /* yes - exit with EEXIST */
pad = strchr(padchar, *trv);
if (pad == NULL) {
/* this should never happen */
errno = EIO;
return (0);
}
/* increment character */
*trv = (*++pad == '\0') ? padchar[0] : *pad;
/* carry to next position? */
if (*trv == *carryp) {
/* increment position and loop */
++trv;
++carryp;
} else {
/* try with new name */
break;
}
}
}
/*NOTREACHED*/
}
#endif /* !HAVE_MKDTEMP || !HAVE_MKSTEMP */
#ifndef HAVE_MKSTEMP
int
mkstemp (char *template)
{
int fd;
return (_gettemp (template, &fd, 0, 0) ? fd : -1);
}
#endif /* HAVE_MKSTEMP */
#ifndef HAVE_MKDTEMP
char *
mkdtemp (char *template)
{
return (_gettemp (template, (int *)NULL, 1, 0) ? template : (char *)NULL);
}
#endif /* HAVE_MKDTEMP */
#ifndef HAVE_GETAUXVAL
unsigned long
getauxval (unsigned long type)
{
static unsigned long secure = 0UL;
static bool check_secure_initialized = false;
/*
* This is the only one our stand-in impl supports and is
* also the only type we define in compat.h header
*/
assert (type == AT_SECURE);
if (!check_secure_initialized) {
#if defined(HAVE___LIBC_ENABLE_SECURE)
extern int __libc_enable_secure;
secure = __libc_enable_secure;
#elif defined(HAVE_ISSETUGID) && defined(HAVE_ISSETUGID_OPENBSD)
secure = issetugid ();
#elif defined(OS_UNIX)
uid_t ruid, euid, suid; /* Real, effective and saved user ID's */
gid_t rgid, egid, sgid; /* Real, effective and saved group ID's */
#ifdef HAVE_GETRESUID
if (getresuid (&ruid, &euid, &suid) != 0 ||
getresgid (&rgid, &egid, &sgid) != 0)
#endif /* HAVE_GETRESUID */
{
suid = ruid = getuid ();
sgid = rgid = getgid ();
euid = geteuid ();
egid = getegid ();
}
secure = (ruid != euid || ruid != suid ||
rgid != egid || rgid != sgid);
#endif /* OS_UNIX */
check_secure_initialized = true;
}
return secure;
}
#endif /* HAVE_GETAUXVAL */
char *
secure_getenv (const char *name)
{
if (getauxval (AT_SECURE))
return NULL;
return getenv (name);
}
#ifndef HAVE_STRERROR_R
int
strerror_r (int errnum,
char *buf,
size_t buflen)
{
#ifdef OS_WIN32
#if _WIN32_WINNT < 0x502 /* WinXP or older */
int n = sys_nerr;
const char *p;
if (errnum < 0 || errnum >= n)
p = sys_errlist[n];
else
p = sys_errlist[errnum];
if (buf == NULL || buflen == 0)
return EINVAL;
strncpy(buf, p, buflen);
buf[buflen-1] = 0;
return 0;
#else /* Server 2003 or newer */
return strerror_s (buf, buflen, errnum);
#endif /*_WIN32_WINNT*/
#else
#error no strerror_r implementation
#endif
}
#endif /* HAVE_STRERROR_R */
void
p11_dl_close (void *dl)
{
#ifdef OS_WIN32
FreeLibrary (dl);
#else
(void) dlclose (dl);
#endif
}
#ifdef OS_UNIX
#include <unistd.h>
#ifndef HAVE_FDWALK
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
int
fdwalk (int (* cb) (void *data, int fd),
void *data)
{
struct dirent *de;
char *end;
DIR *dir;
int open_max;
long num;
int res = 0;
int fd;
#ifdef HAVE_SYS_RESOURCE_H
struct rlimit rl;
#endif
dir = opendir ("/proc/self/fd");
if (dir != NULL) {
while ((de = readdir (dir)) != NULL) {
end = NULL;
num = (int) strtol (de->d_name, &end, 10);
/* didn't parse or is the opendir() fd */
if (!end || *end != '\0' ||
(int)num == dirfd (dir))
continue;
fd = num;
/* call the callback */
res = cb (data, fd);
if (res != 0)
break;
}
closedir (dir);
return res;
}
/* No /proc, brute force */
#ifdef HAVE_SYS_RESOURCE_H
if (getrlimit (RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
open_max = rl.rlim_max;
else
#endif
open_max = sysconf (_SC_OPEN_MAX);
for (fd = 0; fd < open_max; fd++) {
res = cb (data, fd);
if (res != 0)
break;
}
return res;
}
#endif /* HAVE_FDWALK */
#endif /* OS_UNIX */
int
p11_ascii_tolower (int c)
{
if (c >= 'A' && c <= 'Z')
return 'a' + (c - 'A');
return c;
}
int
p11_ascii_toupper (int c)
{
if (c >= 'a' && c <= 'z')
return 'A' + (c - 'a');
return c;
}