/*************************************************************************/
/* Copyright (c) 2004 */
/* Daniel Sleator, David Temperley, and John Lafferty */
/* Copyright 2008, 2009, 2013 Linas Vepstas */
/* All rights reserved */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/*************************************************************************/
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h> // fstat
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <windows.h>
#include <Shlwapi.h> // PathRemoveFileSpecA
#include <direct.h> // getcwd
#endif /* _MSC_VER */
#include <stdlib.h>
#include <string.h>
#include "file-utils.h"
#include "error.h" // verbosity_level
#include "link-includes.h"
#include "utilities.h" // lg_strerror
#ifdef _WIN32
#define DIR_SEPARATOR "\\"
#else
#define DIR_SEPARATOR "/"
#endif /*_WIN32 */
#ifndef DICTIONARY_DIR
#define DICTIONARY_DIR NULL
#endif
/* =========================================================== */
/* File path and dictionary open routines below */
#define MAX_PATH_NAME 200 /* file names (including paths)
should not be longer than this */
/**
* Find the location of the last directory separator (Windows/POSIX).
* Return NULL if none found.
* It doesn't modify its argument, but const is not used because
* it is to be called also with non-const argument.
*/
char *find_last_dir_separator(char *path)
{
char *dirsep = NULL;
size_t pathlen = strlen(path);
for (size_t p = pathlen; p > 0; p--)
if (('/' == path[p]) || ('\\' == path[p])) return &path[p];
return dirsep;
}
char * join_path(const char * prefix, const char * suffix)
{
char * path;
size_t path_len, prel;
path_len = strlen(prefix) + 1 /* len(DIR_SEPARATOR) */ + strlen(suffix);
path = (char *) malloc(path_len + 1);
strcpy(path, prefix);
/* Windows is allergic to multiple path separators, so append one
* only if the prefix isn't already terminated by a path sep.
*/
prel = strlen(path);
if (0 < prel && (path[prel-1] != '/') && (path[prel-1] != '\\'))
{
path[prel] = '/';
path[prel+1] = '\0';
}
strcat(path, suffix);
return path;
}
/* global - but that's OK, since this is set only during initialization,
* and is is thenceforth a read-only item. So it doesn't need to be
* locked. XXX Assuming the user actually does set it only once,
* and doesn't hammer on this inside of a thread loop, in which case
* it will race and abort with a double-free error.
*/
static char * custom_data_dir = NULL;
static void free_custom_data_dir(void) {
free(custom_data_dir);
}
void dictionary_set_data_dir(const char * path)
{
if (custom_data_dir)
free(custom_data_dir);
else
atexit(free_custom_data_dir);
custom_data_dir = safe_strdup(path);
}
char * dictionary_get_data_dir(void)
{
char * data_dir = NULL;
if (custom_data_dir != NULL) {
data_dir = safe_strdup(custom_data_dir);
return data_dir;
}
return NULL;
}
#ifdef _MSC_VER
static const char *get_dictionary_dir(bool);
static void free_dictionary_dir(void)
{
get_dictionary_dir(false);
}
#endif // _MSC_VER
/**
* Return the path of the system dictionary directory.
* If DICTIONARY_DIR is an absolute path (must be so on systems other than
* Windows) then it is directly used.
* On Windows (currently implemented on MSVC only), a relative
* DICTIONARY_DIR is combined with the location of this library DLL, and
* if it includes a drive letter or is a UNC path then it must be
* absolute. It shouldn't end with a directory separator.
* @param find If true, find the system dictionary directory; else free it
* if needed.
* @return The actual dictionary directory.
*/
static const char *get_dictionary_dir(bool find)
{
#ifndef _MSC_VER
return DICTIONARY_DIR;
#else
/* We are on Windows. DICTIONARY_DIR, if defined, can be relative or
* absolute. An absolute path must start with a drive letter or be a
* UNC path, and a relative path should not start with a drive letter
* or be a UNC path.
* In case it is not so, the result is undefined. If it is a relative
* path, it is concatenated to the program location. */
static const char *dictionary_dir;
if (!find) {
free((void *)dictionary_dir);
return NULL;
}
/* If we already found it, just return it. */
if (NULL != dictionary_dir) return dictionary_dir;
dictionary_dir = DICTIONARY_DIR;
/* If it is NULL or "", assume it is relative to the current directory. */
if ((NULL == dictionary_dir) || ('\0' == dictionary_dir[1]))
dictionary_dir = ".";
/* DICTIONARY_DIR is already an absolute path - return it.
* Note: UNC paths are supposed to be absolute. */
if (0 == strncmp(dictionary_dir, "\\\\", 2)) return DICTIONARY_DIR;
if (0 == strncmp(dictionary_dir, "//", 2)) return DICTIONARY_DIR;
/* It includes a drive letter - suppose it's absolute so just return it. */
if ((strlen(dictionary_dir) > 2) && (':' == dictionary_dir[1]))
return DICTIONARY_DIR;
/* Here DICTIONARY_DIR is a relative path (to be concatenated to the
* location of this library DLL), or an absolute path w/o a drive
* letter (to be prepended with the drive letter of our program).
* Non-ASCII characters in the DLL location are not supported (files
* will not be found).
* Note: In case of an error, we return dictionary_dir.
* Is there anything better to do? */
char dll_path[MAX_PATH_NAME] = "";
HMODULE dll_hm = NULL;
/* First find the module handle of this library DLL. */
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR) &get_dictionary_dir, &dll_hm))
{
prt_error("Warning: GetModuleHandleEx error %d\n", (int)GetLastError());
return dictionary_dir; /* Is there anything better to do? */
}
/* Then use it to find the DLL path. */
if (!GetModuleFileNameA(dll_hm, dll_path, sizeof(dll_path)))
{
prt_error("Warning: GetModuleFileNameA error %d\n", (int)GetLastError());
return dictionary_dir;
}
if ('\0' == dll_path[0])
{
/* Can it happen? */
prt_error("Warning: GetModuleFileNameA didn't return a path!\n");
return dictionary_dir;
}
if (!PathRemoveFileSpecA(dll_path))
{
prt_error("Warning: Cannot get directory from LG DLL path '%s'!\n",
dll_path);
return dictionary_dir;
}
/* Unconvertible characters are marked as '?' */
if (NULL != strchr(dll_path, '?'))
{
prt_error("Warning: Directory of LG DLL (%s) "
"contains unsupported characters\n", dll_path);
return dictionary_dir;
}
/* Just a sanity check of the directory length. */
if (strlen(dll_path) < 3)
{
prt_error("Warning: DLL directory name '%s' too short!\n", dll_path);
return dictionary_dir;
}
lgdebug(D_USER_FILES, "Debug: Directory of LG DLL: %s\n", dll_path);
char *combined_dictionary_dir;
if (('\\' == dictionary_dir[0]) || ('/' == dictionary_dir[0]))
{
/* DICTIONARY_DIR is an absolute path without a drive
* (UNC paths already returned unmodified, above).
* Prepend the drive or the host of our program location. */
size_t prefix_len = 0;
if (dll_path[1] == ':')
{
/* "X:path" */
prefix_len = 2;
}
else
{
/* \\host\path */
const char *hostend = strchr(dll_path+3, '\\');
if (NULL == hostend)
hostend = strchr(dll_path+3, '/');
if (NULL != hostend)
prefix_len = (size_t)(hostend - dll_path);
}
size_t len = prefix_len + strlen(dictionary_dir) + 1;
combined_dictionary_dir = malloc(len);
strncpy(combined_dictionary_dir, dll_path, prefix_len);
strcpy(combined_dictionary_dir + prefix_len, dictionary_dir);
}
else
{
size_t len = strlen(dll_path)+1+strlen(dictionary_dir)+1;
combined_dictionary_dir = malloc(len);
strcpy(combined_dictionary_dir, dll_path);
strcat(combined_dictionary_dir, "\\"); /* Mixing / and \ is allowed. */
strcat(combined_dictionary_dir, dictionary_dir);
}
dictionary_dir = combined_dictionary_dir;
atexit(free_dictionary_dir);
lgdebug(D_USER_FILES, "Debug: Using dictionary directory '%s'\n",
dictionary_dir);
return dictionary_dir;
#endif /* _MSC_VER */
}
static void *dict_file_open(const char *fullname, const void *how)
{
return fopen(fullname, how);
}
/**
* Locate a data file and open it.
*
* This function is used to open a dictionary file or a word file,
* or any associated data file (like a post process knowledge file).
*
* It works as follows. If the file name begins with a "/", then
* it's assumed to be an absolute file name and it tries to open
* that exact file.
*
* Otherwise, it looks for the file in a sequence of directories, as
* specified in the dictpath array, until it finds it.
*
* If it is still not found, it may be that the user specified a relative
* path, so it tries to open the exact file.
*
* Associated data files are looked in the *same* directory in which the
* first one was found (typically "en/4.0.dict"). The private static
* "path_found" serves as a directory path cache which records where the
* first file was found. The goal here is to avoid insanity due to
* user's fractured installs.
* If the filename argument is NULL, the function just invalidates this
* directory path cache.
*/
#define NOTFOUND(fp) ((NULL == (fp)) ? " (Not found)" : "")
void * object_open(const char *filename,
void * (*opencb)(const char *, const void *),
const void * user_data)
{
/* Dictionary data directory path cache -- per-thread storage. */
static TLS char *path_found;
char *completename = NULL;
void *fp = NULL;
char *data_dir = NULL;
const char *dictionary_dir = NULL;
const char **path = NULL;
if (NULL == filename)
{
/* Invalidate the dictionary data directory path cache. */
char *pf = path_found;
path_found = NULL;
free(pf);
return NULL;
}
if (NULL == path_found)
{
dictionary_dir = get_dictionary_dir(true);
data_dir = dictionary_get_data_dir();
if (verbosity_level(D_USER_FILES))
{
char cwd[MAX_PATH_NAME];
char *cwdp = getcwd(cwd, sizeof(cwd));
prt_error("Debug: Current directory: %s\n", NULL == cwdp ? "NULL": cwdp);
prt_error("Debug: Data directory: %s\n",
data_dir ? data_dir : "NULL");
prt_error("Debug: System data directory: %s\n",
dictionary_dir ? dictionary_dir : "NULL");
}
}
/* Look for absolute filename.
* Unix: starts with leading slash.
* Windows: starts with C:\ except that the drive letter may differ. */
if ((filename[0] == '/')
#ifdef _WIN32
|| ((filename[1] == ':')
&& ((filename[2] == '\\') || (filename[2] == '/')))
|| (filename[0] == '\\') /* UNC path */
#endif /* _WIN32 */
)
{
/* opencb() returns NULL if the file does not exist. */
fp = opencb(filename, user_data);
lgdebug(D_USER_FILES, "Debug: Opening file %s%s\n", filename, NOTFOUND(fp));
}
else
{
/* A path list in which to search for dictionaries.
* path_found, data_dir or DEFAULTPATH may be NULL. */
const char *dictpath[] =
{
path_found,
".",
"./data",
"..",
"../data",
data_dir,
dictionary_dir,
};
size_t i = sizeof(dictpath)/sizeof(dictpath[0]);
for (path = dictpath; i-- > 0; path++)
{
if (NULL == *path) continue;
free(completename);
completename = join_path(*path, filename);
fp = opencb(completename, user_data);
lgdebug(D_USER_FILES, "Debug: Opening file %s%s\n", completename, NOTFOUND(fp));
if ((NULL != fp) || (NULL != path_found)) break;
}
}
if (NULL == fp)
{
fp = opencb(filename, user_data);
lgdebug(D_USER_FILES, "Debug: Opening file %s%s\n", filename, NOTFOUND(fp));
}
else if (NULL == path_found)
{
char *pfnd = strdup((NULL != completename) ? completename : filename);
if ((0 < verbosity) && (dict_file_open == opencb))
prt_error("Info: Dictionary found at %s\n", pfnd);
for (size_t i = 0; i < 2; i++)
{
char *root = find_last_dir_separator(pfnd);
if (NULL != root) *root = '\0';
}
path_found = pfnd;
lgdebug(D_USER_FILES, "Debug: Using dictionary path \"%s\"\n", path_found);
}
free(data_dir);
free(completename);
return fp;
}
#undef NOTFOUND
FILE *dictopen(const char *filename, const char *how)
{
return object_open(filename, dict_file_open, how);
}
/*
* XXX - dict_file_open() cannot be used due to the Info printout
* of opening a dictionary.
*/
static void *data_file_open(const char *fullname, const void *how)
{
return fopen(fullname, how);
}
/**
* Open a file in the dictionary search path.
* Experimental API (may be unstable).
* @param filename Filename to be opened.
* @return FILE pointer, or NULL if the file was no found.
*/
FILE *linkgrammar_open_data_file(const char *filename)
{
object_open(NULL, NULL, NULL); /* Invalidate the directory path cache */
return object_open(filename, data_file_open, "r");
}
/* ======================================================== */
bool check_db(const char *lang)
{
char *dbname = join_path (lang, "dict.db");
bool retval = file_exists(dbname);
#if !HAVE_SQLITE3
if (retval)
prt_error("Error: Could not open dictionary \"%s\" "
"(not configured with SQLite support)\n", dbname);
#endif /* !HAVE_SQLITE3 */
free(dbname);
return retval;
}
#define ATOMESE_DICT "storage.dict" /* See also same define elsewhere */
bool check_atomspace(const char *lang)
{
char *cfgfile = join_path (lang, ATOMESE_DICT);
bool retval = file_exists(cfgfile);
#if !HAVE_ATOMESE
if (retval)
prt_error("Error: Could not open dictionary \"%s\" "
"(not configured with AtomSpace support)\n", cfgfile);
#endif /* !HAVE_ATOMESE */
free(cfgfile);
return retval;
}
/**
* Check to see if a file exists.
*/
bool file_exists(const char * dict_name)
{
bool retval = false;
int fd;
struct stat buf;
/* On Windows, 'b' (binary mode) is mandatory, otherwise fstat file length
* is confused by crlf counted as one byte. POSIX systems just ignore it. */
FILE *fp = dictopen(dict_name, "rb");
if (fp == NULL)
return false;
/* Get the file size, in bytes. */
fd = fileno(fp);
fstat(fd, &buf);
if (0 < buf.st_size) retval = true;
fclose(fp);
return retval;
}
/**
* Read in the whole stinkin file. This routine returns
* malloced memory, which should be freed as soon as possible.
*/
char *get_file_contents(const char * dict_name)
{
int fd;
size_t tot_size;
size_t tot_read = 0;
struct stat buf;
char * contents;
/* On Windows, 'b' (binary mode) is mandatory, otherwise fstat file length
* is confused by crlf counted as one byte. POSIX systems just ignore it. */
FILE *fp = dictopen(dict_name, "rb");
if (fp == NULL)
return NULL;
/* Get the file size, in bytes. */
fd = fileno(fp);
fstat(fd, &buf);
tot_size = buf.st_size;
contents = (char *) malloc(sizeof(char) * (tot_size+7));
/* Now, read the whole file.
* Normally, a single fread() call below reads the whole file. */
while (1)
{
size_t read_size = fread(contents, 1, tot_size+7, fp);
if (0 == read_size)
{
bool err = (0 != ferror(fp));
if (err)
{
prt_error("Error: %s: Read error (%s)\n", dict_name,
syserror_msg(errno));
fclose(fp);
free(contents);
return NULL;
}
fclose(fp);
break;
}
tot_read += read_size;
}
if (tot_read > tot_size+6)
{
prt_error("Error: %s: File size is insane (%zu)!\n", dict_name, tot_size);
free(contents);
return NULL;
}
contents[tot_read] = '\0';
return contents;
}
void free_file_contents(char * contents)
{
free(contents);
}
/* ============================================================= */