/* * This file is part of the bladeRF project: * http://www.github.com/nuand/bladeRF * * Copyright (C) 2013-2014 Nuand LLC * Copyright (C) 2013 Daniel Gröber * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "host_config.h" #include "minmax.h" #include "log.h" #include "rel_assert.h" #include "helpers/file.h" /* Paths to search for bladeRF files */ struct search_path_entries { bool prepend_home; const char *path; }; int file_write(FILE *f, uint8_t *buf, size_t len) { size_t rv; rv = fwrite(buf, 1, len, f); if(rv < len) { log_debug("File write failed: %s\n", strerror(errno)); return BLADERF_ERR_IO; } return 0; } int file_read(FILE *f, char *buf, size_t len) { size_t rv; rv = fread(buf, 1, len, f); if(rv < len) { if(feof(f)) log_debug("Unexpected end of file: %s\n", strerror(errno)); else log_debug("Error reading file: %s\n", strerror(errno)); return BLADERF_ERR_IO; } return 0; } ssize_t file_size(FILE *f) { ssize_t rv = BLADERF_ERR_IO; long int fpos = ftell(f); long len; if(fpos < 0) { log_verbose("ftell failed: %s\n", strerror(errno)); goto out; } if(fseek(f, 0, SEEK_END)) { log_verbose("fseek failed: %s\n", strerror(errno)); goto out; } len = ftell(f); if(len < 0) { log_verbose("ftell failed: %s\n", strerror(errno)); goto out; } else if (len == LONG_MAX) { log_debug("ftell called with a directory?\n"); goto out; } if(fseek(f, fpos, SEEK_SET)) { log_debug("fseek failed: %s\n", strerror(errno)); goto out; } rv = (ssize_t) len; assert(rv == len); out: return rv; } int file_read_buffer(const char *filename, uint8_t **buf_ret, size_t *size_ret) { int status = BLADERF_ERR_UNEXPECTED; FILE *f; uint8_t *buf = NULL; ssize_t len; f = fopen(filename, "rb"); if (!f) { log_error("%s: could not open %s: %s\n", __FUNCTION__, filename, strerror(errno)); switch (errno) { case ENOENT: return BLADERF_ERR_NO_FILE; case EACCES: return BLADERF_ERR_PERMISSION; default: return BLADERF_ERR_IO; } } len = file_size(f); if (len < 0) { status = BLADERF_ERR_IO; goto out; } buf = (uint8_t *)malloc(len); if (!buf) { status = BLADERF_ERR_MEM; goto out; } status = file_read(f, (char *)buf, len); if (status < 0) { goto out; } *buf_ret = buf; *size_ret = len; fclose(f); return 0; out: free(buf); if (f) { fclose(f); } return status; } /* Remove the last entry in a path. This is used to strip the executable name * from a path to get the directory that the executable resides in. */ static size_t strip_last_path_entry(char *buf, char dir_delim) { size_t len = strlen(buf); while (len > 0 && buf[len - 1] != dir_delim) { buf[len - 1] = '\0'; len--; } return len; } #if BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_FREEBSD #define ACCESS_FILE_EXISTS F_OK #define DIR_DELIMETER '/' static const struct search_path_entries search_paths[] = { { false, "" }, { true, "/.config/Nuand/bladeRF/" }, { true, "/.Nuand/bladeRF/" }, /* LIBBLADERF_SEARCH_PATH_PREFIX is defined in the libbladeRF * CMakeLists.txt file. It defaults to ${CMAKE_INSTALL_PREFIX}, but * can be overridden via -DLIBBLADERF_SEARCH_PATH_OVERRIDE */ //{ false, LIBBLADERF_SEARCH_PREFIX "/etc/Nuand/bladeRF/" }, //{ false, LIBBLADERF_SEARCH_PREFIX "/share/Nuand/bladeRF/" }, /* These two entries are here for reverse compatibility. * * We failed to prefix ${CMAKE_INSTALL_PREFIX} on these from the beginning, * forcing package maintainers to hard-code one of these locations, * despite having a different ${CMAKE_INSTALL_PREFIX}. * * We'll keep these around for some time as fall-backs, as not to break * existing packaging scripts. */ { false, "/etc/Nuand/bladeRF/" }, { false, "/usr/share/Nuand/bladeRF/" }, }; static inline size_t get_home_dir(char *buf, size_t max_len) { const char *home; home = getenv("HOME"); if (home != NULL && strlen(home) > 0 && strlen(home) < max_len) { strncat(buf, home, max_len); } else { const struct passwd *passwd; const uid_t uid = getuid(); passwd = getpwuid(uid); strncat(buf, passwd->pw_dir, max_len); } return strlen(buf); } static inline size_t get_install_dir(char *buf, size_t max_len) { return 0; } #if BLADERF_OS_LINUX static inline size_t get_binary_dir(char *buf, size_t max_len) { ssize_t result = readlink("/proc/self/exe", buf, max_len); if (result > 0) { return strip_last_path_entry(buf, DIR_DELIMETER); } else { return 0; } } #elif BLADERF_OS_FREEBSD static inline size_t get_binary_dir(char *buf, size_t max_len) { int mib[4]; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; ssize_t result = sysctl(mib, 4, buf, &max_len, NULL, 0); if (result > 0) { return strip_last_path_entry(buf, DIR_DELIMETER); } else { return 0; } } #elif BLADERF_OS_OSX #include static inline size_t get_binary_dir(char *buf, size_t max_len) { uint32_t buf_size = max_len; int status = _NSGetExecutablePath(buf, &buf_size); if (status == 0) { return strip_last_path_entry(buf, DIR_DELIMETER); } else { return 0; } } #endif #elif BLADERF_OS_WINDOWS #define ACCESS_FILE_EXISTS 0 #define DIR_DELIMETER '\\' #include static const struct search_path_entries search_paths[] = { { false, "" }, { true, "/Nuand/bladeRF/" }, }; static inline size_t get_home_dir(char *buf, size_t max_len) { /* Explicitly link to a runtime DLL to get SHGetKnownFolderPath. * This deals with the case where we might not be able to staticly * link it at build time, e.g. mingw32. * * http://msdn.microsoft.com/en-us/library/784bt7z7.aspx */ typedef HRESULT (CALLBACK* LPFNSHGKFP_T)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*); HINSTANCE hDLL; // Handle to DLL LPFNSHGKFP_T lpfnSHGetKnownFolderPath; // Function pointer const KNOWNFOLDERID folder_id = FOLDERID_RoamingAppData; PWSTR path; HRESULT status; assert(max_len < INT_MAX); hDLL = LoadLibrary("Shell32"); if (hDLL == NULL) { // DLL couldn't be loaded, bail out. return 0; } lpfnSHGetKnownFolderPath = (LPFNSHGKFP_T)GetProcAddress(hDLL, "SHGetKnownFolderPath"); if (!lpfnSHGetKnownFolderPath) { // Can't find the procedure we want. Free and bail. FreeLibrary(hDLL); return 0; } status = lpfnSHGetKnownFolderPath(&folder_id, 0, NULL, &path); if (status == S_OK) { WideCharToMultiByte(CP_ACP, 0, path, -1, buf, (int)max_len, NULL, NULL); CoTaskMemFree(path); } FreeLibrary(hDLL); return strlen(buf); } static inline size_t get_binary_dir(char *buf, size_t max_len) { DWORD status; assert(max_len <= MAXDWORD); status = GetModuleFileName(NULL, buf, (DWORD) max_len); if (status > 0) { return strip_last_path_entry(buf, DIR_DELIMETER); } else { return 0; } } static inline size_t get_install_dir(char *buf, size_t max_len) { typedef LONG (CALLBACK* LPFNREGOPEN_T)(HKEY, LPCTSTR, DWORD, REGSAM, PHKEY); typedef LONG (CALLBACK* LPFNREGQUERY_T)(HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD); typedef LONG (CALLBACK* LPFNREGCLOSE_T)(HKEY); HINSTANCE hDLL; // Handle to DLL LPFNREGOPEN_T lpfnRegOpenKeyEx; // Function pointer LPFNREGQUERY_T lpfnRegQueryValueEx; // Function pointer LPFNREGCLOSE_T lpfnRegCloseKey; // Function pointer HKEY hk; DWORD len; assert(max_len < INT_MAX); memset(buf, 0, max_len); hDLL = LoadLibrary("Advapi32"); if (hDLL == NULL) { // DLL couldn't be loaded, bail out. return 0; } lpfnRegOpenKeyEx = (LPFNREGOPEN_T)GetProcAddress(hDLL, "RegOpenKeyExA"); if (!lpfnRegOpenKeyEx) { // Can't find the procedure we want. Free and bail. FreeLibrary(hDLL); return 0; } lpfnRegQueryValueEx = (LPFNREGQUERY_T)GetProcAddress(hDLL, "RegQueryValueExA"); if (!lpfnRegQueryValueEx) { // Can't find the procedure we want. Free and bail. FreeLibrary(hDLL); return 0; } lpfnRegCloseKey = (LPFNREGCLOSE_T)GetProcAddress(hDLL, "RegCloseKey"); if (!lpfnRegCloseKey) { // Can't find the procedure we want. Free and bail. FreeLibrary(hDLL); return 0; } if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Nuand LLC", 0, KEY_READ, &hk)) { FreeLibrary(hDLL); return 0; } len = (DWORD)max_len; if (RegQueryValueEx(hk, "Path", 0, NULL, (LPBYTE) buf, &len) == ERROR_SUCCESS) { if (len > 0 && len < max_len && buf[len - 1] != '\\') strcat(buf, "\\"); } else len = 0; if (len) { lpfnRegCloseKey(hk); } FreeLibrary(hDLL); return len; } #else #error "Unknown OS or missing BLADERF_OS_* definition" #endif /* We're not using functions that use the *nix PATH_MAX (which is known to be * problematic), or the WIN32 MAX_PATH. Therefore, we'll just use this * arbitrary, but "sufficiently" large max buffer size for paths */ #define PATH_MAX_LEN 4096 char *file_find(const char *filename) { size_t i, max_len; char *full_path; const char *env_var; full_path = calloc(PATH_MAX_LEN+1, 1); if (full_path == NULL) { return NULL; } /* Check directory specified by environment variable */ env_var = getenv("BLADERF_SEARCH_DIR"); if (env_var != NULL) { strncat(full_path, env_var, PATH_MAX_LEN - 1); full_path[strlen(full_path)] = DIR_DELIMETER; max_len = PATH_MAX_LEN - strlen(full_path); if (max_len >= strlen(filename)) { strncat(full_path, filename, max_len); if (access(full_path, ACCESS_FILE_EXISTS) != -1) { return full_path; } } } /* Check the directory containing the currently running binary */ memset(full_path, 0, PATH_MAX_LEN); max_len = PATH_MAX_LEN - 1; if (get_binary_dir(full_path, max_len) != 0) { max_len -= strlen(full_path); if (max_len >= strlen(filename)) { strncat(full_path, filename, max_len); if (access(full_path, ACCESS_FILE_EXISTS) != -1) { return full_path; } } else { log_debug("Skipping search for %s in %s. " "Path would be truncated.\n", filename, full_path); } } /* Search our list of pre-determined paths */ for (i = 0; i < ARRAY_SIZE(search_paths); i++) { memset(full_path, 0, PATH_MAX_LEN); max_len = PATH_MAX_LEN; if (search_paths[i].prepend_home) { const size_t len = get_home_dir(full_path, max_len); if (len != 0) { max_len -= len; } else { continue; } } strncat(full_path, search_paths[i].path, max_len); max_len = PATH_MAX_LEN - strlen(full_path); if (max_len >= strlen(filename)) { strncat(full_path, filename, max_len); if (access(full_path, ACCESS_FILE_EXISTS) != -1) { return full_path; } } else { log_debug("Skipping search for %s in %s. " "Path would be truncated.\n", filename, full_path); } } /* Search the installation directory, if applicable */ if (get_install_dir(full_path, PATH_MAX_LEN)) { max_len = PATH_MAX_LEN - strlen(full_path); if (max_len >= strlen(filename)) { strncat(full_path, filename, max_len); if (access(full_path, ACCESS_FILE_EXISTS) != -1) { return full_path; } } else { log_debug("Skipping search for %s in %s. " "Path would be truncated.\n", filename, full_path); } } free(full_path); return NULL; }