Initial code release

Signed-off-by: Claudio Luck <claudio.luck@pep.foundation>
master
Claudio Luck 5 years ago
parent 56f13a7744
commit c117f3ed1a

@ -0,0 +1,14 @@
language: python
env:
global:
- PYTHONUNBUFFERED=1
stages:
- name: build_ext
- name: test
python:
- '2.7'
- '3.6'

@ -0,0 +1,39 @@
MACOSPY = $(shell find "$(HOME)/Library/Python/" -name bin -type d -maxdepth 2 -exec echo -n "{}:" \; 2>/dev/null)
WINPY = $(shell find "/$(SYSTEMDRIVE)/"Python* -name bin -type d -maxdepth 2 -exec echo -n "{}:" \; 2>/dev/null)
PATHPREP = $(PATH):$(HOME)/.local/bin:$(MACOSPY):$(WINPY)
PYTHON := $(shell PATH="$(PATHPREP)" which python3 python2 python | head -n1)
PATH := $(PATHPREP)
ifeq ($(OS),Windows_NT)
BINEXT = .exe
else
BINEXT =
endif
apportable_demo: apportable.c apportable_demo.c
$(CC) -liconv -DAPPORTABLE -o apportable_demo apportable.c apportable_demo.c
apportable_demo.exe: apportable.c apportable_demo.c
$(CC) -DAPPORTABLE -o apportable_demo.exe apportable.c apportable_demo.c
demo: apportable_demo$(BINEXT)
all: demo
build/lib/.build_stamp: setup.py apportable.c apportable_pyext.c
mkdir -p build
$(PYTHON) setup.py build_ext -b build/lib
touch build/lib/.build_stamp
build_ext: build/lib/.build_stamp
test: build_ext
PYTHONPATH=build/lib $(PYTHON) test_apportable.py
clean:
rm -f apportable build/lib/* build/lib/.build_stamp apportable_demo apportable_demo.exe
.PHONY: all test build_ext

@ -1,2 +1,102 @@
# apportable
C library to help making applications portable
**This project is not finished yet; use with care.**
C library to help making applications portable.
## Motivation
There is a distinction between portable applications and portable source-code;
the latter is source code with an inherent flexibility which lets us compile
the program on different platforms The former instead allows to copy
applications from one similar system to another, without the need to place
dependencies in system-default, and thus particularly guarded locations -
i.e. without installation. This library is about offering the functionality
to make binaries portable, providing itself portable source-code, so that it
is particularly interesting for applications with already have portable source
code.
Installed applications can easily locate their files on absolute paths,
once they know the distributions conventions. Configuration files for example are
usually considered to be under **$(sysconfdir)**, which the maintainer can define
at compile time to be - usually - **/etc/**. Portable applications instead can not
be accommodated with a fixed absolute path.
Different strategies exist, but all depend on a carefully prepared environment,
which is normally not realistic. Neither environment variables are reliably set,
nor can a regular user set the working directory for a binary.
Portable applications need a way to compute the paths to their resources
relative to the binary file. This is surprisingly a hard problem, especially
cross-platform. The code differs not only by platform, but also for the
compiler and the specific C library implementation.
## Architecture
This library is designed such that existing build systems can be leveraged, without
too much code change. It is also designed to be turned off by default, so that adding
apportable to the build system in not enough to introduce additional security problems,
except if requested explicitely by the maintainers.
To use apportable add `apportable.c` to the linked objects, and somehow add `apportable.h` to
the necessary source files. It should not hurt to add `apportable.h` globally, as
all variables are prefixed with `apportable_`. Finally you need to enable the code
explicitely by defining `APPORTABLE` on the compiler.
Once you have integrated apportable in the build and enabled it with APPORTABLE,
the basic idea is that whenever the build system expects you to define a static path
```Makefile
CONFIGFILE = "/etc/apportable.conf"
```
it can be replaced by a function call:
```Makefile
CONFIGFILE = apportable_template("$ORIGIN/../etc/apportable.conf")
```
This is possible as static strings are basically pointers to an array of characters (`char *`),
thus replacing the code with a function which is guaranteed to return a valid pointer
is normally fine.
**Note**: this works, as long as the string is not concatenated to other strings:
```c
FILE * cf = open(PREFIX CONFIGFILE, "r");
```
In such a case, the source code needs to be modified directly:
```c
FILE * cf = open(apportable_template("$ORIGIN/../etc/apportable.conf"), "r");
```
## Source code compatibility
This should be written in POSIX 2008 compatible C99, to make it interesting
and usable by most existing software.
It was tested to compile on:
* Apple LLVM version 9.0.0
* gcc (Debian 6.3.0-18) 6.3.0
* Visual Studio 2008
For testig (and a programming exercise in handling Unicode and Python), a
Python extension has been added, which is tested with the same compilers and
Python 2.7 and Python 3.2+.
## Encoding (UTF-8, UTF-16, UTF-32)
Functions for both ASCII compatible UTF-8 (`char *`) and native *wide
character* (`wchar_t`, UTF-16, UTF-32) arguments are provided, the latter are
prefixed with a `w` (like in `wutf8`). All the necessary conversions are done
with platform standard means, that is for now either libiconv or the Windows API.
The testing infrastructure cross-checks the internal conversion against
Python's conversion between the formats, in the hope that this further
validates the function's design.

@ -0,0 +1,756 @@
/* apportable.c - Self-awareness, cross-platform
*
* Copyright (C) 2018 Claudio Luck
*
* This file is part of apportable.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#if defined APPORTABLE
#if defined __APPLE__
# define APPORTABLE_SUPPORTED
# include <mach-o/dyld.h>
# include <mach-o/nlist.h>
# include <sys/syslimits.h>
# include <iconv.h>
# ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct nlist_64 nlist_t;
# else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct nlist nlist_t;
# endif
#elif defined _WIN32
# define APPORTABLE_SUPPORTED
# include <Windows.h>
#elif defined __BIONIC__ // Android
// # define PORTABLE_H_ENABLED
# include <linker/linker.h>
#elif defined __GNUC__
# define _GNU_SOURCE
# ifdef __linux__
# define APPORTABLE_SUPPORTED
# include <linux/limits.h>
# include <link.h>
# include <dlfcn.h>
# include <iconv.h>
# endif
#elif defined __UCLIBC__
// # define PORTABLE_H_ENABLED
# include <ldso.h>
#endif /* platforms */
#if defined _WIN32
#define DIRSEP_S "\\"
#define DIRSEP_C '\\'
#define PATHSEP_S ";"
#define PATHSEP_C ';'
# if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
# endif
#else
#define DIRSEP_S "/"
#define DIRSEP_C '/'
#define PATHSEP_S ":"
#define PATHSEP_C ':'
#endif
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <wchar.h>
#ifdef _WIN32
# include <io.h>
#else
#include <unistd.h>
#endif
#include <sys/stat.h>
// Debugging
#include <stdio.h>
#include "apportable.h"
#define mem_calloc(a, t, c) (a)->_calloc((c), sizeof(t))
#define mem_free(a, v) (a)->_free((v))
#define APPORTABLE_STATE(a) apportable_getstate((a))
static apportable_t apportable_global_state = {0, 0};
void apportable_init (
apportable a,
int enabled
)
{
/* figure out endianness */
int le;
#if !defined _WIN32
volatile uint32_t le_i;
le_i = 0x01234567;
le = ((*((uint8_t*)(&le_i))) == 0x67);
#else /*_WIN32*/
le = 1;
#endif
if (sizeof(wchar_t) == 4)
a->_iconv_wchar_t = le ? "UTF-32LE" : "UTF-32BE";
else if (sizeof(wchar_t) == 2)
a->_iconv_wchar_t = le ? "UTF-16LE" : "UTF-32BE";
a->_calloc = calloc;
a->_free = free;
a->_strndup = &apportable_strndup;
a->_wcsndup = &apportable_wcsndup;
a->wutf8 = &apportable_wutf8;
a->wutf8_free = &apportable_wutf8_free;
a->uwchar_t = &apportable_uwchar_t;
a->uwchar_t_free = &apportable_uwchar_t_free;
a->ugetenv = &apportable_ugetenv;
a->wugetenv = &apportable_wugetenv;
a->progfile = &apportable_progfile;
a->pathexp = &apportable_pathexp;
a->whereis = &apportable_whereis;
a->enabled = enabled;
a->initialized = 1;
return;
}
apportable apportable_getstate (apportable a)
{
apportable self;
int default_enabled = 0;
if (a != NULL)
self = a;
else
{
self = &apportable_global_state;
default_enabled = 1;
}
if (self->initialized)
return self;
apportable_init(self, default_enabled);
return self;
}
/* UTF-8 -> wchar_t -> UTF-8 */
char * apportable_strndup(apportable a, const char * str, size_t syms)
{
// WORKS
apportable self;
wchar_t * wstr, * buffer;
char * ret;
size_t buflen;
self = APPORTABLE_STATE(a);
if (str == NULL)
return NULL;
wstr = self->uwchar_t(self, str);
buflen = wcslen(wstr);
buffer = self->_wcsndup(self, wstr, syms);
ret = self->wutf8(self, buffer);
// // return ret;
// char * x = malloc(1000);
// sprintf(x, "%zu %zu L<%ls> <%s>", syms, buflen, buffer, ret);
// return x;
free(buffer);
return ret;
}
wchar_t * apportable_wcsndup(apportable a, const wchar_t * str, size_t syms)
{
apportable self;
wchar_t * buffer;
size_t buflen;
size_t copysyms;
self = APPORTABLE_STATE(a);
if (str == NULL)
return NULL;
buflen = wcslen(str);
if (syms == 0)
syms = buflen;
if (!(copysyms = syms))
copysyms = (syms > buflen ? buflen : syms);
if ((buffer = self->_calloc(sizeof(wchar_t), buflen + 1)))
wcsncpy(buffer, str, copysyms);
// sprintf(buffer, L"%d %d", strlen(buffer), strlen(str));
return buffer;
}
#ifdef _WIN32
char * apportable_wutf8 (apportable a, const wchar_t * s)
{
apportable self;
char * b;
size_t s_l, b_l;
self = APPORTABLE_STATE(a);
if (s == NULL)
return NULL;
s_l = wcslen(s);
b_l = WideCharToMultiByte(CP_UTF8, 0, s, s_l, NULL, 0, NULL, NULL);
b = self->_calloc(sizeof(wchar_t), b_l + 1);
WideCharToMultiByte(CP_UTF8, 0, s, s_l, b, b_l, NULL, NULL);
return b;
}
char * apportable_wutf8_free(apportable a, wchar_t * s)
{
apportable self;
char * ret;
self = APPORTABLE_STATE(a);
ret = self->wutf8(self, s);
self->_free(s);
return ret;
}
wchar_t * apportable_uwchar_t (apportable a, const char * s)
{
apportable self;
char * b;
size_t s_l, b_l;
self = APPORTABLE_STATE(a);
if (s == NULL)
return NULL;
s_l = strlen(s) + 1;
b_l = MultiByteToWideChar(CP_UTF8, 0, s, s_l, NULL, 0);
b = self->_calloc(sizeof(wchar_t), b_l + 0);
MultiByteToWideChar(CP_UTF8, 0, s, s_l, b, b_l);
return b;
}
wchar_t * apportable_uwchar_t_free (apportable a, const char * s)
{
apportable self;
char * ret;
self = APPORTABLE_STATE(a);
ret = self->uwchar_t(self, s);
self->_free(s);
return ret;
}
char * apportable_ugetenv (apportable a, const char * var)
{
apportable self;
wchar_t * v;
size_t var_l, v_l;
self = APPORTABLE_STATE(a);
var_l = strlen(var) + 1;
v_l = MultiByteToWideChar(CP_UTF8, 0, var, var_l, NULL, 0);
v = self->_calloc(sizeof(wchar_t), v_l + 0);
MultiByteToWideChar(CP_UTF8, 0, var, var_l, v, v_l);
return self->wutf8(self, _wgetenv(v));
}
char * apportable_wugetenv (apportable a, const wchar_t * var)
{
apportable self;
wchar_t * v;
size_t var_l, v_l;
self = APPORTABLE_STATE(a);
var_l = wcslen(var) + 1;
v_l = MultiByteToWideChar(CP_UTF8, 0, var, var_l, NULL, 0);
v = self->_calloc(sizeof(wchar_t), v_l + 0);
MultiByteToWideChar(CP_UTF8, 0, var, var_l, v, v_l);
return self->wutf8(self, _wgetenv(v));
}
#else /*!_WIN32*/
char * apportable_wutf8 (apportable a, const wchar_t * s)
{
apportable self;
char * ret;
size_t s_l, ret_l;
/* iconv expects in-out params, so allocate copies */
wchar_t * iconv_s;
char * iconv_ret;
size_t iconv_s_l, iconv_ret_l;
iconv_t iconv_obj;
self = APPORTABLE_STATE(a);
s_l = sizeof(wchar_t) * ( wcslen(s) + 1 );
iconv_obj = iconv_open("UTF-8", self->_iconv_wchar_t);
if (iconv_obj == (iconv_t) -1) {
return NULL;
}
ret_l = sizeof(char) * ( s_l + 1 );
ret = self->_calloc(1, ret_l);
iconv_s = (wchar_t *) s, iconv_ret = ret;
iconv_s_l = s_l, iconv_ret_l = ret_l;
iconv(iconv_obj, (char **)&iconv_s, &iconv_s_l, &iconv_ret, &iconv_ret_l);
iconv_close(iconv_obj);
// char * b = malloc(1000);
// sprintf(b, "%zu '%ls' '%s'", ret_l, s, ret);
// printf("%s\n", b);
// return b;
return ret;
}
char * apportable_wutf8_free (apportable a, wchar_t * s)
{
apportable self;
char * ret;
self = APPORTABLE_STATE(a);
ret = self->wutf8(self, s);
self->_free(s);
return ret;
}
wchar_t * apportable_uwchar_t (apportable a, const char * s)
{
apportable self;
wchar_t * ret;
wchar_t * buffer;
size_t s_l, buffer_l;
/* iconv expects in-out params, so allocate copies */
char * iconv_s;
wchar_t * iconv_buffer;
size_t iconv_s_l, iconv_buffer_l;
iconv_t iconv_obj;
self = APPORTABLE_STATE(a);
s_l = sizeof(char) * ( strlen(s) + 1 );
iconv_obj = iconv_open(self->_iconv_wchar_t, "UTF-8");
if (iconv_obj == (iconv_t) -1) {
return NULL;
}
buffer_l = sizeof(wchar_t) * ( s_l + 1 );
buffer = self->_calloc(1, buffer_l);
iconv_s = (char *) s, iconv_buffer = buffer;
iconv_s_l = s_l, iconv_buffer_l = buffer_l;
iconv(iconv_obj, (char **)&iconv_s, &iconv_s_l, (char **) &iconv_buffer, &iconv_buffer_l);
// char * b2 = malloc(1000);
// sprintf(b2, "%d->%d %d->%d <%ls> <%s>", s_l, iconv_s_l, buffer_l, iconv_buffer_l, s, buffer);
// return b2;
iconv_close(iconv_obj);
ret = self->_wcsndup(self, buffer, 0);
self->_free(buffer);
return ret;
}
wchar_t * apportable_uwchar_t_free (apportable a, char * s)
{
apportable self;
wchar_t * ret;
self = APPORTABLE_STATE(a);
ret = self->uwchar_t(self, s);
self->_free(s);
return ret;
}
char * apportable_ugetenv (apportable a, char * var)
{
apportable self;
char * ret;
self = APPORTABLE_STATE(a);
if (!(ret = self->_strndup(self, getenv(var), 0)))
ret = self->_strndup(self, "", 0);
return ret;
}
char * apportable_wugetenv (apportable a, wchar_t * wvar)
{
apportable self;
char * var, * ret;
self = APPORTABLE_STATE(a);
var = self->wutf8(self, wvar);
if (!(ret = self->ugetenv(self, var)))
ret = self->_strndup(self, "", 0);
self->_free(var);
return ret;
}
#endif
#ifdef _WIN32
/* for UTF-16 "wide" character */
wchar_t * apportable_wprogfile (apportable a, const wchar_t * library_name) {
apportable self;
wchar_t * ret;
HMODULE handle;
wchar_t * wfile;
wchar_t * file;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return self->_wcsndup(self, library_name, 0);
ret = NULL;
handle = NULL;
if (!GetModuleHandleExW(0, library_name, &handle)) {
if (handle)
FreeLibrary(handle);
return NULL;
}
wfile = self->_calloc(sizeof(WCHAR), MAX_PATH);
file = self->_calloc(sizeof(char), MAX_PATH);
if (GetModuleFileNameW(handle, wfile, MAX_PATH))
ret = file;
self->_free(wfile);
if (!ret)
self->_free(file);
FreeLibrary(handle);
return ret;
}
/* for utf-8, will call into native UTF-16 */
char * apportable_progfile (apportable a, const char * library_name) {
apportable self;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return self->_strndup(a, (const char *)library_name, 0);
char * ret = NULL;
PWSTR wlibnam;
size_t ln_l, wlibnam_l;
if (library_name == NULL)
return apportable_wprogfile (self, NULL);
ln_l = strlen(library_name) + 1;
wlibnam_l = MultiByteToWideChar (CP_UTF8, 0, library_name, ln_l, NULL, 0);
wlibnam = self->_calloc (sizeof(char), wlibnam_l + 0);
if (!MultiByteToWideChar (CP_UTF8, 0, library_name, ln_l, wlibnam, wlibnam_l))
{
self->_free(wlibnam);
return NULL;
}
ret = apportable_wprogfile (a, wlibnam);
self->_free(wlibnam);
return ret;
}
#elif defined __APPLE__
char * apportable_progfile (apportable a, const char * library_name) {
apportable self;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return self->_strndup(self, library_name, 0);
const char* library_base_name = library_name ? strrchr(library_name, DIRSEP_C) : NULL;
if (!library_base_name)
library_base_name = library_name;
else
library_base_name += 1; // skip found '/'
char* library_file = NULL;
const char * image_name = NULL;
const char * library_name_sep = NULL;
unsigned long image_count = _dyld_image_count();
errno = 0;
for (unsigned long i = 0; i < image_count; i++) {
image_name = (const char *)_dyld_get_image_name(i); // dyld owns string
if (!image_name || !image_name[0])
continue;
library_name_sep = strrchr(image_name, '/');
if (!library_name_sep)
library_name_sep = image_name;
else
library_name_sep += 1; // skip found '/'
if (!library_name || !strcmp(library_name_sep, library_base_name)) {
library_file = self->_strndup(a, image_name, PATH_MAX);
break;
}
}
return library_file;
}
#elif defined __GNUC__
extern char *program_invocation_name;
wchar_t * apportable_progfile (apportable a, const wchar_t * library_name) {
apportable self;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return self->_strndup(a, library_name, 0);
const char* library_base_name = library_name ? strrchr(library_name, DIRSEP_C) : NULL;
if (!library_base_name)
library_base_name = library_name;
else
library_base_name += 1; // skip found '/'
char * library_file = NULL;
char * image_name;
char * image_name_real;
char * library_name_sep;
FILE * cmdline;
void *handle = dlopen(NULL, RTLD_LAZY);
if (!handle)
return NULL;
const struct link_map * link_map = 0;
int ret = dlinfo(handle, RTLD_DI_LINKMAP, &link_map);
if (ret || !link_map) {
dlclose(handle);
return NULL;
}
image_name_real = realpath(program_invocation_name, NULL);
/* sometimes program_invocation_name gets wiped for reasons of beauty... */
if (!image_name_real || !image_name_real[0]) {
if ((cmdline = fopen("/proc/self/cmdline", "rb"))) {
char *arg = 0;
size_t size = 0;
while (getdelim(&arg, &size, 0, cmdline) != -1) {
image_name_real = arg;
break;
}
fclose(cmdline);
}
}
while (link_map->l_prev)
link_map = link_map->l_prev;
for(int i = 0; link_map->l_next != NULL; link_map = link_map->l_next, i++) {
if (link_map->l_name && link_map->l_name[0])
image_name = link_map->l_name; /* Absolute pathname according to dlinfo(3) */
else {
if (i == 0 && image_name_real != NULL)
image_name = image_name_real;
}
if (!image_name || !image_name[0])
continue;
library_name_sep = strrchr(image_name, DIRSEP_C);
if (!library_name_sep)
library_name_sep = image_name;
else
library_name_sep += 1; // skip found '/'
if (!library_name || !strcmp(library_name_sep, library_base_name)) {
library_file = self->_strndup(a, image_name, PATH_MAX);
break;
}
}
if (image_name_real)
free(image_name_real);
return library_file;
}
// #else
// char * apportable_progfile (apportable a, const wchar_t * library_name) {
// return NULL;
// }
// char * apportable_pathexp(apportable a, const wchar_t * template, const wchar_t * library_path) {
// return self->_strndup(a, template, strlen(template));
// }
#endif /*_WIN32, __APPLE__, ...*/
char * apportable_whereis(apportable a, const char * searchpath, const char * bin, int execonly)
{
apportable self;
char * sep;
char * pathbuf;
char * pathlim;
char * path;
size_t bin_l;
char * cand;
size_t cand_l;
wchar_t * wcand;
#if defined _WIN32
size_t wcand_l;
#endif
int filetest;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return NULL;
pathbuf = (self->_strndup(self, searchpath, 0));
pathlim = pathbuf + strlen(pathbuf);
path = pathbuf;
bin_l = strlen(bin);
while (path < pathlim) {
sep = strchr(path, PATHSEP_C);
if (!sep)
sep = pathlim;
*sep = 0;
/* construct candidate binary path */
cand_l = strlen(path) + strlen(DIRSEP_S) + bin_l + 1;
if (!(cand = self->_calloc(1, cand_l)))
{
self->_free(pathbuf);
return NULL;
}
strcpy(cand, path);
strcat(cand, DIRSEP_S);
strcat(cand, bin);
cand[cand_l - 1] = 0;
#if !defined _WIN32
wcand = NULL;
filetest = execonly ? X_OK : F_OK;
if (access(cand, filetest) == -1)
#else
wcand_l = MultiByteToWideChar(CP_UTF8, 0, cand, cand_l, NULL, 0);
wcand = self->_calloc(sizeof(wchar_t), wcand_l + 0);
MultiByteToWideChar(CP_UTF8, 0, cand, cand_l, wcand, wcand_l);
if (_waccess_s(wcand, 04) != 0)
#endif
{
if (wcand)
self->_free(wcand);
path = sep < pathlim ? sep + 1 : pathlim;
self->_free(cand);
continue;
}
/* found a candidate */
self->_free(pathbuf);
return cand;
}
free(pathbuf);
return self->_strndup(self, bin, 0);
}
char * apportable_pathexp(apportable a, const char * template, const char * library_path)
{
apportable self;
char * exec_path_sym;
unsigned long exec_path_symlen;
char * result;
const char * library_name;
char * executable_path_p;
char * executable_path;
unsigned long exec_path_len;
unsigned long template_len;
const char * sub_template;
unsigned long sub_template_len;
unsigned long result_len;
self = APPORTABLE_STATE(a);
if (!self->enabled)
return NULL;
if (!library_path || !template)
return NULL;
exec_path_sym = "$ORIGIN";
exec_path_symlen = strlen(exec_path_sym);
result = NULL;
/* if not starting with $ORIGIN, return straight away a copy of the template, unaltered */
if (strncmp(template, exec_path_sym, exec_path_symlen) != 0) {
result = self->_strndup(a, template, 0);
return result;
}
library_name = strrchr(library_path, DIRSEP_C);
library_name = library_name ? library_name + 1 : library_path;
if (library_name - library_path != 0) {
executable_path_p = self->_strndup(a, library_path, library_name - library_path);
executable_path = executable_path_p;
} else {
executable_path_p = NULL;
executable_path = "." DIRSEP_S;
}
exec_path_len = strlen(executable_path);
template_len = strlen(template);
sub_template = &template[exec_path_symlen];
while (sub_template[0] == DIRSEP_C && executable_path[0] == DIRSEP_C)
sub_template += 1;
sub_template_len = template_len - exec_path_symlen;
result_len = template_len + exec_path_len - exec_path_symlen;
// concatenate
result = calloc(1, result_len + 1);
memcpy(result, executable_path, exec_path_len); // "@executable_path"
memcpy(&result[exec_path_len], sub_template, sub_template_len); // "/../share/"
result[result_len] = 0; // (again)
if (executable_path_p)
free(executable_path_p);
return result;
}
#endif /*APPORTABLE*/

@ -0,0 +1,80 @@
/* apportable.c - Self-awareness, cross-platform
*
* Copyright (C) 2018 Claudio Luck
*
* This file is part of apportable.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef APPORTABLE_H
#define APPORTABLE_H
typedef struct apportable_t
{
int initialized; /* keep this first, for initialization = {0} */
int enabled;
char * _iconv_wchar_t;
void * (*_calloc) (size_t, size_t);
void (*_free) (void *);
char * (*_strndup) (struct apportable_t *, const char *, size_t);
wchar_t * (*_wcsndup) (struct apportable_t *, const wchar_t *, size_t);
char * (*wutf8) (struct apportable_t *, const wchar_t *);
char * (*wutf8_free) (struct apportable_t *, wchar_t *);
wchar_t * (*uwchar_t) (struct apportable_t *, const char *);
wchar_t * (*uwchar_t_free) (struct apportable_t *, char *);
char * (*ugetenv) (struct apportable_t *, char *);
char * (*wugetenv) (struct apportable_t *, wchar_t *);
char * (*whereis) (struct apportable_t *, const char *, const char *, int);
char * (*progfile) (struct apportable_t *, const char *);
char * (*pathexp) (struct apportable_t *, const char *, const char *);
}
apportable_t, * apportable;
void apportable_new (apportable a);
void apportable_init (apportable a, int enabled);
apportable apportable_getstate (apportable a);
char * apportable_strndup (apportable a, const char * str, size_t size);
wchar_t * apportable_wcsndup (apportable a, const wchar_t * str, size_t syms);
char * apportable_wutf8 (apportable a, const wchar_t * s);
char * apportable_wutf8_free (apportable a, wchar_t * s);
wchar_t * apportable_uwchar_t (apportable a, const char * s);
wchar_t * apportable_uwchar_t_free (apportable a, char * s);
char * apportable_ugetenv (apportable a, char * var);
char * apportable_wugetenv (apportable a, wchar_t * wvar);
char * apportable_whereis (apportable a, const char * searchpath, const char * bin, int execonly);
char * apportable_progfile (apportable a, const char * library_name);
char * apportable_pathexp (apportable a, const char * template, const char * library_path);
#endif /*APPORTABLE_H*/

@ -0,0 +1,439 @@
#define PY_SSIZE_T_CLEAN
#include "stdlib.h"
#include "Python.h"
#include "bytesobject.h"
#include "apportable.h"
#include <stdio.h>
// #include <iconv.h>
struct module_state {
PyObject *error;
apportable_t apportable;
};
#if PY_VERSION_HEX >= 0x03000000
# define GETSTATE(self) ((struct module_state*)PyModule_GetState((self)))
#else
# define GETSTATE(self) (&_state)
static struct module_state _state;
#endif
char * hexdump(void * p, size_t count)
{
unsigned char * m = (unsigned char *) p;
char * s = calloc(3, count);
for (unsigned int i = 0; i < count; i++) {
sprintf(s + 3*i, "%02x", ((m + i))[0]);
s[3*i + 2] = ' ';
}
s[3*count - 1] = 0;
return s;
}
char * hexstr(char * p)
{
size_t l;
l = strlen(p);
return hexdump(p, l + 1);
}
char * whexstr(wchar_t * p)
{
size_t l;
l = wcslen(p);
return hexdump(p, sizeof(wchar_t) * (l + 1));
}
/* Python Unicode string object -> wchar_t * */
wchar_t * _appoext_pyobywstr (PyObject * self, PyObject * str)
{
wchar_t * wstr;
Py_ssize_t str_l;
#if PY_VERSION_HEX >= 0x03020000
str_l = PyUnicode_GetLength(str);
if (!(wstr = PyUnicode_AsWideCharString(str, NULL)))
return NULL;
#else
Py_ssize_t wstr_l;
str_l = PyUnicode_GetSize(str); /* DEPRECATED in Python 3.2+ */
wstr_l = sizeof(wchar_t) * (str_l + 1);
if (!(wstr = PyMem_Malloc(wstr_l)))
return NULL;
memset(wstr, 0, wstr_l);
if (-1 == (wstr_l = PyUnicode_AsWideChar((PyUnicodeObject *) str, wstr, wstr_l))) {
PyMem_Free(wstr);
return NULL;
}
wstr[wstr_l] = 0;
#endif
return wstr; /* to PyMem_Free() */
}
/* take a Python Unicode string object, and get a UTF-8 copy */
char * _appoext_pyobyutf8 (PyObject * self, PyObject * str)
{
PyObject * bytes;
char * s, * ret;
size_t s_l;
if (!(bytes = PyUnicode_AsUTF8String(str)))
return NULL;
if (!(s = PyBytes_AsString(bytes))) {
return NULL;
}
s_l = strlen(s);
if (!(ret = PyMem_Malloc(s_l + 1)))
return NULL;
memcpy(ret, s, s_l);
ret[s_l] = 0;
return ret;
}
int _selftest (PyObject * self)
{
struct module_state *st;
st = GETSTATE(self);
apportable a = &(st->apportable);
// wchar_t * t = L"äβ©☃☂";
// wchar_t * x = "❤"
// "\xc3\xa4\xce\xb2\xc2\xa9\xe2\x98\x83\xe2\x98\x82"; /* UTF-16 */
char * tt = "\xc3\xa4\xce\xb2\xe2\x9d\xa4\xc2\xa9\xe2\x98\x83\xe2\x98\x82";
char * r;
wchar_t * wr;
char * r2;
PyObject * o = PyUnicode_FromString(tt);
r = _appoext_pyobyutf8(self, o);
printf("utf-8 : %zu '%s'\n", strlen(r), r);
for (int i = 0; i < 5; i++) {
wr = _appoext_pyobywstr(self, o);
printf("wchar_t: %zu %s\n", wcslen(wr), whexstr(wr));
r2 = a->wutf8(a, wr);
printf("wutf8(): %zu %s\n", strlen(r2), hexstr(r2));
o = PyUnicode_FromString(r2);
}
return 0;
}
static PyObject *
appoext_selftest (PyObject * self, PyObject * args)
{
_selftest(self);
Py_RETURN_NONE;
}
static PyObject *
appoext_strndup (PyObject * self, PyObject * args)
{
PyObject * str;
Py_ssize_t size;
struct module_state *st;
char * s, * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "Un", &str, &size)) {
return NULL;
}
st = GETSTATE(self);
s = _appoext_pyobyutf8(self, str);
ret = st->apportable._strndup(&(st->apportable), s, size);
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_wcsndup (PyObject * self, PyObject * args)
{
PyObject * str;
Py_ssize_t size;
wchar_t * wstr;
struct module_state *st;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "Un", &str, &size)) {
return NULL;
}
st = GETSTATE(self);
wstr = _appoext_pyobywstr(self, str);
ret = (char *) st->apportable._wcsndup(&(st->apportable), wstr, size);
PyMem_Free(wstr);
pyres = PyUnicode_FromWideChar((wchar_t *) ret, wcslen((wchar_t *) ret));
free(ret);
return pyres;
}
static PyObject *
appoext_wutf8 (PyObject * self, PyObject * args)
{
PyObject * str;
wchar_t * wstr;
struct module_state *st;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "U", &str)) {
return NULL;
}
st = GETSTATE(self);
wstr = _appoext_pyobywstr(self, str);
ret = st->apportable.wutf8(&(st->apportable), wstr);
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_uwchar_t (PyObject * self, PyObject * args)
{
PyObject * str;
char * cstr;
struct module_state *st;
wchar_t * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "U", &str)) {
return NULL;
}
st = GETSTATE(self);
cstr = _appoext_pyobyutf8(self, str);
ret = st->apportable.uwchar_t(&(st->apportable), cstr);
pyres = PyUnicode_FromWideChar((wchar_t *) ret, wcslen((wchar_t *) ret));
free(ret);
return pyres;
}
static PyObject *
appoext_whereis (PyObject * self, PyObject * args)
{
struct module_state *st;
PyObject * osp, * obin;
char * searchpath, * bin;
int execonly;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "UUb", &osp, &obin, &execonly)) {
return NULL;
}
st = GETSTATE(self);
searchpath = _appoext_pyobyutf8(self, osp);
bin = _appoext_pyobyutf8(self, obin);
ret = st->apportable.whereis(&(st->apportable), searchpath, bin, execonly);
if (!ret) {
Py_RETURN_NONE;
}
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_progfile (PyObject * self, PyObject * args)
{
const char * library_name;
struct module_state *st;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "z", &library_name)) {
return NULL;
}
st = GETSTATE(self);
ret = st->apportable.progfile(&(st->apportable), library_name);
// pyres = Py_BuildValue("s", ret);
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_pathexp (PyObject * self, PyObject * args)
{
PyObject * ot, * olb;
const char * template, * library_path;
struct module_state *st;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "UU", &ot, &olb)) {
return NULL;
}
st = GETSTATE(self);
template = _appoext_pyobyutf8(self, ot);
library_path = _appoext_pyobyutf8(self, olb);
ret = st->apportable.pathexp(&(st->apportable), template, library_path);
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_ugetenv (PyObject * self, PyObject * args)
{
struct module_state *st;
PyObject * oenv;
char * env;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "U", &oenv)) {
return NULL;
}
st = GETSTATE(self);
env = _appoext_pyobyutf8(self, oenv);
ret = st->apportable.ugetenv(&(st->apportable), env);
if (!ret) {
Py_RETURN_NONE;
}
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyObject *
appoext_wugetenv (PyObject * self, PyObject * args)
{
struct module_state *st;
PyObject * oenv;
wchar_t * env;
char * ret;
PyObject * pyres;
if (!PyArg_ParseTuple(args, "U", &oenv)) {
return NULL;
}
st = GETSTATE(self);
env = _appoext_pyobywstr(self, oenv);
ret = st->apportable.wugetenv(&(st->apportable), env);
if (!ret) {
Py_RETURN_NONE;
}
pyres = PyUnicode_FromString(ret);
free(ret);
return pyres;
}
static PyMethodDef apportable_methods[] = {
{"selftest", appoext_selftest, METH_VARARGS, NULL},
{"strndup", appoext_strndup, METH_VARARGS, NULL},
{"wcsndup", appoext_wcsndup, METH_VARARGS, NULL},
{"wutf8", appoext_wutf8, METH_VARARGS, NULL},
{"uwchar_t", appoext_uwchar_t, METH_VARARGS, NULL},
{"progfile", appoext_progfile, METH_VARARGS, NULL},
{"pathexp", appoext_pathexp, METH_VARARGS, NULL},
{"whereis", appoext_whereis, METH_VARARGS, NULL},
{"ugetenv", appoext_ugetenv, METH_VARARGS, NULL},
{"wugetenv", appoext_wugetenv, METH_VARARGS, NULL},
{ NULL, NULL, 0, NULL }
};
#if PY_MAJOR_VERSION >= 3
static int apportable_traverse(PyObject *self, visitproc visit, void *arg) {
Py_VISIT(GETSTATE(self)->error);
return 0;
}
static int apportable_clear(PyObject *self) {
Py_CLEAR(GETSTATE(self)->error);
return 0;
}
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, "apportable", NULL, sizeof(struct module_state),
apportable_methods, NULL, apportable_traverse, apportable_clear, NULL
};
#define INITERROR return NULL
PyMODINIT_FUNC
PyInit_apportable(void)
#else /* PY_MAJOR_VERSION >= 3 */
#define INITERROR return
void
initapportable(void)
#endif
{
struct module_state *st;
#if PY_MAJOR_VERSION >= 3
PyObject *module = PyModule_Create(&moduledef);
#else
PyObject *module = Py_InitModule("apportable", apportable_methods);
#endif
if (module == NULL)
INITERROR;
st = GETSTATE(module);
apportable_init(&(st->apportable), 1);
st->error = PyErr_NewException("apportable.Error", NULL, NULL);
if (st->error == NULL) {
Py_DECREF(module);
INITERROR;
}
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
/* Not for release */
// PyErr_Format(PyExc_ValueError, "X %zd", str_l);
// return NULL;

5
configure vendored

@ -0,0 +1,5 @@
#!/bin/sh
echo configure: success
exit 0

@ -0,0 +1,25 @@
import sys
from distutils.core import setup, Extension
if sys.platform != 'win32':
ext_libs = ['iconv']
else:
ext_libs = []
apportable = Extension(
'apportable',
define_macros = [('APPORTABLE', '1')],
libraries = [] + ext_libs,
sources = ['apportable.c', 'apportable_pyext.c']
)
setup(
name = 'libapportable-test',
version = '1.0',
description = 'Apportable tests',
ext_modules = [apportable],
)

@ -0,0 +1,133 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
try:
unicode
except:
unicode = str
import sys
import os
import subprocess
import unittest
try:
import apportable
except ImportError:
sys.path.append("build/lib")
import apportable
class TestClass(unittest.TestCase):
t1 = u"abcde"
t2 = u"äβ©☃☂"
def test_strndup(self):
a = apportable
for t in (self.t1, self.t2):
self.assertEqual( a.strndup(t, 0), t )
for x in range(1, len(t)+1):
# self.assertEqual( repr(a.strndup(t, x)), repr(t[0:x]) )
self.assertEqual( a.strndup(t, x), t[0:x] )
def test_wcsndup(self):
a = apportable
for t in (self.t1, self.t2):
self.assertEqual( a.wcsndup(t, 0), t )
for x in range(1, len(t)+1):
self.assertEqual( a.wcsndup(t, x), t[0:x] )
def test_wutf8(self):
a = apportable
for t in (self.t1, self.t2):
self.assertEqual( a.wutf8(t), t)
def test_uwchar_t(self):
a = apportable
for t in (self.t1, self.t2):
self.assertEqual( a.uwchar_t(t), t)
def test_whereis(self):
a = apportable
pth = unicode(os.environ.get("PATH", "/opt:/bin"))
wherepy = subprocess.check_output("whereis python", shell=True)
wherepy = wherepy.decode('utf-8').split('\n')[0]
self.assertEqual(a.whereis(pth, u"python", 1), wherepy)
self.assertEqual(a.whereis(u"/opt:/etc", u"hosts", 1), "hosts")
self.assertEqual(a.whereis(u"/opt:/etc", u"hosts", 0), "/etc/hosts")
self.assertEqual(a.whereis(u"::::", u"hosts", 0), "hosts")
self.assertEqual(a.whereis(u""