| /* ********************************************************** |
| * Copyright (c) 2013-2025 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" |
| |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| #include "dr_frontend_private.h" /* for debuglevel/abortlevel */ |
| #include "dr_frontend.h" |
| |
| #ifdef WINDOWS |
| # include <direct.h> |
| /* It looks better to consistently use the same separator */ |
| # define DIRSEP '\\' |
| # define snprintf _snprintf |
| #else |
| # include <unistd.h> |
| # define DIRSEP '/' |
| #endif |
| #include <sys/types.h> |
| #include <sys/stat.h> /* for _stat */ |
| |
| int drfrontend_verbosity = 0; |
| |
| drfront_status_t |
| drfront_bufprint(char *buf, size_t bufsz, DR_PARAM_INOUT size_t *sofar, |
| DR_PARAM_OUT ssize_t *len, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| /* XXX i#1397: We would like to use d_r_vsnprintf() instead of depending |
| * on libc/winapi. |
| */ |
| *len = vsnprintf(buf + *sofar, bufsz - *sofar, fmt, ap); |
| *sofar += (*len < 0 ? 0 : *len); |
| if (bufsz <= *sofar) { |
| va_end(ap); |
| return DRFRONT_ERROR_INVALID_SIZE; |
| } |
| /* be paranoid: though usually many calls in a row and could delay until end */ |
| buf[bufsz - 1] = '\0'; |
| va_end(ap); |
| return DRFRONT_SUCCESS; |
| } |
| |
| /* Always null-terminates. |
| * No conversion on UNIX, just copy the data. |
| */ |
| drfront_status_t |
| drfront_convert_args(const TCHAR **targv, DR_PARAM_OUT char ***argv, int argc) |
| { |
| int i = 0; |
| drfront_status_t ret = DRFRONT_ERROR; |
| *argv = (char **)calloc(argc + 1 /*null*/, sizeof(**argv)); |
| for (i = 0; i < argc; i++) { |
| size_t len = 0; |
| ret = drfront_tchar_to_char_size_needed(targv[i], &len); |
| if (ret != DRFRONT_SUCCESS) |
| return ret; |
| (*argv)[i] = (char *)malloc(len); /* len includes terminating null */ |
| ret = drfront_tchar_to_char(targv[i], (*argv)[i], len); |
| if (ret != DRFRONT_SUCCESS) |
| return ret; |
| } |
| (*argv)[i] = NULL; |
| return DRFRONT_SUCCESS; |
| } |
| |
| drfront_status_t |
| drfront_cleanup_args(char **argv, int argc) |
| { |
| int i = 0; |
| for (i = 0; i < argc; i++) |
| free(argv[i]); |
| free(argv); |
| return DRFRONT_SUCCESS; |
| } |
| |
| #ifdef WINDOWS |
| static bool |
| drfront_is_system_install_dir(const char *dir) |
| { |
| /* XXX DrM-i#1730: there could be 8.3 name path or junction or other way |
| * to get to the system install dir, so we cannot just rely on path name to |
| * check if dir is the system install dir. |
| */ |
| return (_strnicmp(dir + strlen("c:\\"), "progra~", strlen("progra~")) == 0 || |
| _strnicmp(dir + strlen("c:\\"), "Program Files", strlen("Program Files")) == |
| 0); |
| } |
| #endif |
| |
| drfront_status_t |
| drfront_appdata_logdir(const char *root, const char *subdir, DR_PARAM_OUT bool *use_root, |
| DR_PARAM_OUT char *buf, size_t buflen /*# elements*/) |
| { |
| drfront_status_t res = DRFRONT_ERROR; |
| bool writable = false; |
| char env[MAXIMUM_PATH]; |
| if (use_root == NULL || buf == NULL) |
| return DRFRONT_ERROR_INVALID_PARAMETER; |
| /* On Vista+ we can't write to Program Files; plus better to not store |
| * logs there on 2K or XP either. |
| * DrM-i#1730: trying to write to Program Files has complexities: |
| * modifications to files "pretend" to succeed and are redirected to |
| * VirtualStore. |
| */ |
| if (IF_WINDOWS(drfront_is_system_install_dir(root) ||) |
| drfront_access(root, DRFRONT_WRITE, &writable) != DRFRONT_SUCCESS || |
| !writable) { |
| bool have_env = false; |
| drfront_status_t sc; |
| *use_root = false; |
| #ifdef WINDOWS |
| sc = drfront_get_env_var("APPDATA", env, BUFFER_SIZE_ELEMENTS(env)); |
| if (sc == DRFRONT_SUCCESS) |
| have_env = true; |
| if (have_env) { |
| snprintf(buf, buflen, "%s%c%s", env, DIRSEP, subdir); |
| buf[buflen - 1] = '\0'; |
| } else { |
| sc = drfront_get_env_var("USERPROFILE", env, BUFFER_SIZE_ELEMENTS(env)); |
| if (sc == DRFRONT_SUCCESS) |
| have_env = true; |
| if (have_env) { |
| snprintf(buf, buflen, "%s%cApplication Data%c%s", env, DIRSEP, DIRSEP, |
| subdir); |
| buf[buflen - 1] = '\0'; |
| } |
| } |
| #endif |
| if (!have_env) { |
| sc = drfront_get_env_var("TMPDIR", env, BUFFER_SIZE_ELEMENTS(env)); |
| if (sc != DRFRONT_SUCCESS) |
| sc = drfront_get_env_var("TEMP", env, BUFFER_SIZE_ELEMENTS(env)); |
| if (sc != DRFRONT_SUCCESS) |
| sc = drfront_get_env_var("TMP", env, BUFFER_SIZE_ELEMENTS(env)); |
| if (sc != DRFRONT_SUCCESS) { |
| #ifdef WINDOWS |
| /* bail */ |
| #else |
| have_env = true; |
| # ifdef ANDROID |
| /* It's impractical to query Java for the "cache dir" so we use |
| * this commonly present dir. |
| */ |
| # define TMP_DIR "/data/local/tmp" |
| # else |
| # define TMP_DIR "/tmp" |
| # endif |
| snprintf(env, BUFFER_SIZE_ELEMENTS(env), "%s", TMP_DIR); |
| NULL_TERMINATE_BUFFER(env); |
| #endif |
| } else |
| have_env = true; |
| if (have_env) { |
| snprintf(buf, buflen, "%s%c%s", env, DIRSEP, subdir); |
| buf[buflen - 1] = '\0'; |
| } |
| } |
| if (have_env) { |
| /* XXX: I would create the dir, or check for existence, here -- |
| * but currently this lib has no reliance on DR so I can't |
| * easily use dr_create_dir() or dr_directory_exists(). |
| */ |
| res = DRFRONT_SUCCESS; |
| } |
| } else { |
| *use_root = true; |
| res = DRFRONT_SUCCESS; |
| } |
| return res; |
| } |
| |
| void |
| drfront_string_replace_character(DR_PARAM_OUT char *str, char old_char, char new_char) |
| { |
| while (*str != '\0') { |
| if (*str == old_char) { |
| *str = new_char; |
| } |
| str++; |
| } |
| } |
| |
| void |
| drfront_string_replace_character_wide(DR_PARAM_OUT TCHAR *str, TCHAR old_char, |
| TCHAR new_char) |
| { |
| while (*str != _T('\0')) { |
| if (*str == old_char) { |
| *str = new_char; |
| } |
| str++; |
| } |
| } |
| |
| /* XXX: Since we can't simply use dr_* routines, we implement create/remove/exists |
| * directory here as a short-term solution (xref i#1409). |
| */ |
| drfront_status_t |
| drfront_create_dir(const char *dir) |
| { |
| drfront_status_t status = DRFRONT_ERROR; |
| TCHAR wdir[MAXIMUM_PATH]; |
| if (dir == NULL) |
| return DRFRONT_ERROR_INVALID_PARAMETER; |
| /* i#1530: use WinAPI W versions directly to avoid using libc on Windows */ |
| status = drfront_char_to_tchar(dir, wdir, BUFFER_SIZE_ELEMENTS(wdir)); |
| if (status != DRFRONT_SUCCESS) |
| return status; |
| #ifdef WINDOWS |
| if (CreateDirectoryW(wdir, NULL) == 0) { |
| DWORD errcode = GetLastError(); |
| if (errcode == ERROR_ALREADY_EXISTS) { |
| return DRFRONT_ERROR_FILE_EXISTS; |
| } else if (errcode == ERROR_PATH_NOT_FOUND) { |
| return DRFRONT_ERROR_INVALID_PATH; |
| } else if (errcode == ERROR_ACCESS_DENIED) { |
| return DRFRONT_ERROR_ACCESS_DENIED; |
| } else { |
| NOTIFY(1, "CreateDirectoryW failed %d\n", errcode); |
| return DRFRONT_ERROR; |
| } |
| } |
| #else |
| if (mkdir(wdir, 0777) != 0) { |
| if (errno == EEXIST) { |
| return DRFRONT_ERROR_FILE_EXISTS; |
| } else if (errno == EACCES) { |
| return DRFRONT_ERROR_ACCESS_DENIED; |
| } else { |
| NOTIFY(1, "mkdir failed %d\n", errno); |
| return DRFRONT_ERROR; |
| } |
| } |
| #endif |
| return DRFRONT_SUCCESS; |
| } |
| |
| drfront_status_t |
| drfront_remove_dir(const char *dir) |
| { |
| drfront_status_t status = DRFRONT_ERROR; |
| TCHAR wdir[MAXIMUM_PATH]; |
| if (dir == NULL) |
| return DRFRONT_ERROR_INVALID_PARAMETER; |
| /* i#1530: use WinAPI W versions directly to avoid using libc on Windows */ |
| status = drfront_char_to_tchar(dir, wdir, BUFFER_SIZE_ELEMENTS(wdir)); |
| if (status != DRFRONT_SUCCESS) |
| return status; |
| #ifdef WINDOWS |
| if (RemoveDirectoryW(wdir) == 0) { |
| DWORD errcode = GetLastError(); |
| if (errcode == ERROR_PATH_NOT_FOUND) { |
| return DRFRONT_ERROR_INVALID_PATH; |
| } else if (errcode == ERROR_ACCESS_DENIED) { |
| return DRFRONT_ERROR_ACCESS_DENIED; |
| } else { |
| NOTIFY(1, "RemoveDirectoryW failed %d\n", errcode); |
| return DRFRONT_ERROR; |
| } |
| } |
| #else |
| if (rmdir(dir) != 0) { |
| if (errno == ENOENT) { |
| return DRFRONT_ERROR_INVALID_PATH; |
| } else if (errno == EACCES) { |
| return DRFRONT_ERROR_ACCESS_DENIED; |
| } else { |
| NOTIFY(1, "rmdir failed %d\n", errno); |
| return DRFRONT_ERROR; |
| } |
| } |
| #endif |
| return DRFRONT_SUCCESS; |
| } |
| |
| drfront_status_t |
| drfront_set_verbose(int verbosity) |
| { |
| if (verbosity < 0) |
| return DRFRONT_ERROR_INVALID_PARAMETER; |
| drfrontend_verbosity = verbosity; |
| return DRFRONT_SUCCESS; |
| } |