blob: 3d48691a120cc99c914d4ef13ce9aa9c27cc09e9 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2013-2022 Google, Inc. All rights reserved.
* **********************************************************/
/*
* 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.
*
* * Neither the name of Google, Inc. 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 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 GOOGLE, INC. 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.
*/
#include "share.h"
#ifndef UNIX
# error UNIX-only
#endif
#ifdef LINUX
# include <elf.h>
#elif defined(MACOS)
# include <mach-o/loader.h> /* mach_header */
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <fcntl.h>
#include "dr_frontend.h"
#include "drlibc.h"
extern bool
module_get_platform(file_t f, dr_platform_t *platform, dr_platform_t *alt_platform);
#define drfront_die() exit(1)
/* up to caller to call die() if necessary */
#define drfront_error(msg, ...) \
do { \
fprintf(stderr, "ERROR: " msg "\n", ##__VA_ARGS__); \
fflush(stderr); \
} while (0)
/* Semi-compatibility with the Windows CRT _access function.
*/
drfront_status_t
drfront_access(const char *fname, drfront_access_mode_t mode, DR_PARAM_OUT bool *ret)
{
int r;
struct stat64 st;
uid_t euid;
if (ret == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
/* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */
r = dr_stat_syscall(fname, &st);
if (r < 0) {
*ret = false;
if (r == -EACCES || r == -ENOENT || r == -ENOTDIR)
return DRFRONT_SUCCESS;
return DRFRONT_ERROR;
} else if (mode == DRFRONT_EXIST) {
/* Just checking for existence */
*ret = true;
return DRFRONT_SUCCESS;
}
*ret = false;
euid = geteuid();
/* It is assumed that (S_IRWXU >> 6) == DRFRONT_READ | DRFRONT_WRITE | DRFRONT_EXEC */
if (euid == 0) {
/* XXX DrMi#1857: we assume that euid == 0 means +rw access to any file,
* and +x access to any file with at least one +x bit set. This is
* usually true but not always.
*/
if (TEST(DRFRONT_EXEC, mode)) {
*ret = TESTANY((DRFRONT_EXEC << 6) | (DRFRONT_EXEC << 3) | DRFRONT_EXEC,
st.st_mode);
} else
*ret = true;
} else if (euid == st.st_uid) {
/* Check owner permissions */
*ret = TESTALL(mode << 6, st.st_mode);
} else {
/* Check other permissions */
*ret = TESTALL(mode, st.st_mode);
}
if (*ret && S_ISDIR(st.st_mode) && TEST(DRFRONT_WRITE, mode)) {
/* We use an actual write try, to avoid failing on a read-only filesystem
* (DrMi#1857).
*/
return drfront_dir_try_writable(fname, ret);
}
return DRFRONT_SUCCESS;
}
/* Implements a normal path search for fname on the paths in env_var.
* Resolves symlinks, which is needed to get the right config filename (i#1062).
*/
drfront_status_t
drfront_searchenv(const char *fname, const char *env_var, DR_PARAM_OUT char *full_path,
const size_t full_path_size, DR_PARAM_OUT bool *ret)
{
const char *paths = getenv(env_var);
const char *cur;
const char *next;
const char *end;
char tmp[PATH_MAX];
char realpath_buf[PATH_MAX]; /* realpath hardcodes its buffer length */
drfront_status_t status_check = DRFRONT_ERROR;
bool access_ret = false;
int r;
struct stat64 st;
if (ret == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
/* Windows searches the current directory first. */
/* XXX: realpath resolves symlinks, which we may not want. */
if (realpath(fname, realpath_buf) != NULL) {
status_check = drfront_access(realpath_buf, DRFRONT_EXIST, &access_ret);
if (status_check != DRFRONT_SUCCESS) {
*ret = false;
return status_check;
} else if (access_ret == true) {
*ret = true;
snprintf(full_path, full_path_size, "%s", realpath_buf);
full_path[full_path_size - 1] = '\0';
return DRFRONT_SUCCESS;
}
}
cur = paths;
end = strchr(paths, '\0');
while (cur < end) {
next = strchr(cur, ':');
next = (next == NULL ? end : next);
snprintf(tmp, BUFFER_SIZE_ELEMENTS(tmp), "%.*s/%s", (int)(next - cur), cur,
fname);
NULL_TERMINATE_BUFFER(tmp);
/* realpath checks for existence too. */
if (realpath(tmp, realpath_buf) != NULL) {
status_check = drfront_access(realpath_buf, DRFRONT_EXEC, &access_ret);
if (status_check != DRFRONT_SUCCESS) {
*ret = false;
return status_check;
} else if (access_ret == true) {
// XXX: An other option to prevent calling stat() twice
// could be a new variant DRFRONT_NOTDIR for drfront_access_mode_t
// that drfront_access() then takes into account.
/* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */
r = dr_stat_syscall(realpath_buf, &st);
if (r == 0 && !S_ISDIR(st.st_mode)) {
*ret = true;
snprintf(full_path, full_path_size, "%s", realpath_buf);
full_path[full_path_size - 1] = '\0';
return DRFRONT_SUCCESS;
}
}
}
cur = next + 1;
}
full_path[0] = '\0';
*ret = false;
return DRFRONT_ERROR;
}
/* Always null-terminates.
* No conversion on UNIX, just copy the data.
*/
drfront_status_t
drfront_tchar_to_char(const char *wstr, DR_PARAM_OUT char *buf,
size_t buflen /*# elements*/)
{
strncpy(buf, wstr, buflen);
buf[buflen - 1] = '\0';
return DRFRONT_SUCCESS;
}
/* includes the terminating null */
drfront_status_t
drfront_tchar_to_char_size_needed(const char *wstr, DR_PARAM_OUT size_t *needed)
{
if (needed == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
*needed = strlen(wstr) + 1;
return DRFRONT_SUCCESS;
}
/* Always null-terminates.
* No conversion on UNIX, just copy the data.
*/
drfront_status_t
drfront_char_to_tchar(const char *str, DR_PARAM_OUT char *wbuf,
size_t wbuflen /*# elements*/)
{
strncpy(wbuf, str, wbuflen);
wbuf[wbuflen - 1] = '\0';
return DRFRONT_SUCCESS;
}
drfront_status_t
drfront_is_64bit_app(const char *exe, DR_PARAM_OUT bool *is_64,
DR_PARAM_OUT bool *also_32)
{
FILE *target_file;
drfront_status_t res = DRFRONT_ERROR;
if (is_64 == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
target_file = fopen(exe, "rb");
if (target_file != NULL) {
dr_platform_t platform, alt_platform;
if (module_get_platform(fileno(target_file), &platform, &alt_platform)) {
res = DRFRONT_SUCCESS;
/* XXX: on a 32-bit kernel we'll claim a 64+32 binary is *not* 64-bit:
* is that ok?
*/
*is_64 = (platform == DR_PLATFORM_64BIT);
if (also_32 != NULL)
*also_32 = (alt_platform == DR_PLATFORM_32BIT);
}
fclose(target_file);
}
return res;
}
/* This function is only relevant on Windows, so we return false. */
drfront_status_t
drfront_is_graphical_app(const char *exe, DR_PARAM_OUT bool *is_graphical)
{
if (is_graphical == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
*is_graphical = false;
return DRFRONT_SUCCESS;
}
drfront_status_t
drfront_get_env_var(const char *name, DR_PARAM_OUT char *buf,
size_t buflen /*# elements*/)
{
const char *tmp_buf = getenv(name);
size_t len_var = 0;
if (tmp_buf == NULL) {
return DRFRONT_ERROR_INVALID_PARAMETER;
}
len_var = strlen(tmp_buf);
/* strlen doesn't include \0 in length */
if (len_var + 1 > buflen) {
return DRFRONT_ERROR_INVALID_SIZE;
}
strncpy(buf, tmp_buf, len_var + 1);
buf[buflen - 1] = '\0';
return DRFRONT_SUCCESS;
}
/* Simply concatenates the cwd with the given relative path. Previously we
* called realpath, but this requires the path to exist and expands symlinks,
* which is inconsistent with Windows GetFullPathName().
*/
drfront_status_t
drfront_get_absolute_path(const char *rel, DR_PARAM_OUT char *buf,
size_t buflen /*# elements*/)
{
size_t len = 0;
char *err = NULL;
if (rel[0] != '/') {
err = getcwd(buf, buflen);
if (err != NULL) {
len = strlen(buf);
/* Append a slash if it doesn't have a trailing slash. */
if (buf[len - 1] != '/' && len < buflen) {
buf[len++] = '/';
buf[len] = '\0';
}
/* Omit any leading "./". */
if (rel[0] == '.' && rel[1] == '/') {
rel += 2;
}
}
}
strncpy(buf + len, rel, buflen - len);
buf[buflen - 1] = '\0';
return DRFRONT_SUCCESS;
}
drfront_status_t
drfront_get_app_full_path(const char *app, DR_PARAM_OUT char *buf,
size_t buflen /*# elements*/)
{
bool res = false;
drfront_status_t status_check = DRFRONT_ERROR;
status_check = drfront_searchenv(app, "PATH", buf, buflen, &res);
if (status_check != DRFRONT_SUCCESS)
return status_check;
buf[buflen - 1] = '\0';
if (buf[0] == '\0') {
/* last try: expand w/ cur dir */
status_check = drfront_get_absolute_path(app, buf, buflen);
if (status_check != DRFRONT_SUCCESS)
return status_check;
buf[buflen - 1] = '\0';
}
return DRFRONT_SUCCESS;
}
drfront_status_t
drfront_dir_exists(const char *path, DR_PARAM_OUT bool *is_dir)
{
struct stat64 st_buf;
if (is_dir == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
/* check if path is a file or directory */
/* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */
if (dr_stat_syscall(path, &st_buf) != 0) {
*is_dir = false;
return DRFRONT_ERROR_INVALID_PATH;
} else {
if (S_ISDIR(st_buf.st_mode))
*is_dir = true;
else
*is_dir = false;
}
return DRFRONT_SUCCESS;
}
drfront_status_t
drfront_dir_try_writable(const char *path, DR_PARAM_OUT bool *is_writable)
{
/* It would be convenient to use O_TMPFILE but not all filesystems support it */
int fd;
char tmpname[PATH_MAX];
/* We actually don't care about races w/ other threads or processes running
* this same code: each syscall should succeed and truncate whatever is there.
*/
#define TMP_FILE_NAME ".__drfrontendlib_tmp"
if (is_writable == NULL)
return DRFRONT_ERROR_INVALID_PARAMETER;
snprintf(tmpname, BUFFER_SIZE_ELEMENTS(tmpname), "%s/%s", path, TMP_FILE_NAME);
NULL_TERMINATE_BUFFER(tmpname);
fd = creat(tmpname, S_IRUSR | S_IWUSR);
if (fd == -1) {
drfront_status_t res;
bool is_dir;
*is_writable = false;
res = drfront_dir_exists(path, &is_dir);
if (res != DRFRONT_SUCCESS)
return res;
if (!is_dir)
return DRFRONT_ERROR_INVALID_PATH;
} else {
*is_writable = true;
close(fd);
unlink(tmpname);
}
return DRFRONT_SUCCESS;
}