/*******************************************************************/
/* slibtool: a skinny libtool implementation, written in C */
/* Copyright (C) 2016--2024 SysDeer Technologies, LLC */
/* Released under the Standard MIT License; see COPYING.SLIBTOOL. */
/*******************************************************************/
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "slibtool_lconf_impl.h"
#include "slibtool_driver_impl.h"
#include "slibtool_errinfo_impl.h"
#include "slibtool_symlink_impl.h"
#include "slibtool_readlink_impl.h"
#include "slibtool_realpath_impl.h"
#include "slibtool_visibility_impl.h"
enum slbt_lconf_opt {
SLBT_LCONF_OPT_UNKNOWN,
SLBT_LCONF_OPT_NO,
SLBT_LCONF_OPT_YES,
};
static const char aclr_reset[] = "\x1b[0m";
static const char aclr_bold[] = "\x1b[1m";
static const char aclr_red[] = "\x1b[31m";
static const char aclr_green[] = "\x1b[32m";
static const char aclr_yellow[] = "\x1b[33m";
static const char aclr_blue[] = "\x1b[34m";
static const char aclr_magenta[] = "\x1b[35m";
static void slbt_lconf_close(int fdcwd, int fdlconfdir)
{
if (fdlconfdir != fdcwd)
close(fdlconfdir);
}
static int slbt_lconf_trace_lconf_plain(
struct slbt_driver_ctx * dctx,
const char * lconf)
{
int fderr = slbt_driver_fderr(dctx);
if (slbt_dprintf(
fderr,
"%s: %s: {.name=%c%s%c}.\n",
dctx->program,
"lconf",
'"',lconf,'"') < 0)
return -1;
return 0;
}
static int slbt_lconf_trace_lconf_annotated(
struct slbt_driver_ctx * dctx,
const char * lconf)
{
int fderr = slbt_driver_fderr(dctx);
if (slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: {.name=%s%s%c%s%c%s}.\n",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
aclr_bold,aclr_green,
'"',lconf,'"',
aclr_reset) < 0)
return -1;
return 0;
}
static int slbt_lconf_trace_openat_silent(
struct slbt_driver_ctx * dctx,
int fdat,
const char * path,
int oflag,
int mode)
{
(void)dctx;
return openat(fdat,path,oflag,mode);
}
static int slbt_lconf_trace_openat_plain(
struct slbt_driver_ctx * dctx,
int fdat,
const char * path,
int oflag,
int mode)
{
char scwd[20];
char serr[512];
int ret = openat(fdat,path,oflag,mode);
int fderr = slbt_driver_fderr(dctx);
if (fdat == AT_FDCWD) {
strcpy(scwd,"AT_FDCWD");
} else {
sprintf(scwd,"%d",fdat);
}
if ((ret < 0) && (errno == ENOENT)) {
strcpy(serr," [ENOENT]");
} else if (ret < 0) {
memset(serr,0,sizeof(serr));
strerror_r(errno,&serr[2],sizeof(serr)-4);
serr[0] = ' ';
serr[1] = '(';
serr[strlen(serr)] = ')';
} else {
serr[0] = 0;
}
slbt_dprintf(
fderr,
"%s: %s: openat(%s,%c%s%c,%s,%d) = %d%s.\n",
dctx->program,
"lconf",
scwd,
'"',path,'"',
(oflag == O_DIRECTORY) ? "O_DIRECTORY" : "O_RDONLY",
mode,ret,serr);
return ret;
}
static int slbt_lconf_trace_openat_annotated(
struct slbt_driver_ctx * dctx,
int fdat,
const char * path,
int oflag,
int mode)
{
char scwd[20];
char serr[512];
int ret = openat(fdat,path,oflag,mode);
int fderr = slbt_driver_fderr(dctx);
if (fdat == AT_FDCWD) {
strcpy(scwd,"AT_FDCWD");
} else {
sprintf(scwd,"%d",fdat);
}
if ((ret < 0) && (errno == ENOENT)) {
strcpy(serr," [ENOENT]");
} else if (ret < 0) {
memset(serr,0,sizeof(serr));
strerror_r(errno,&serr[2],sizeof(serr)-4);
serr[0] = ' ';
serr[1] = '(';
serr[strlen(serr)] = ')';
} else {
serr[0] = 0;
}
slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: openat(%s%s%s%s,%s%s%c%s%c%s,%s%s%s%s,%d) = %s%d%s%s%s%s%s.\n",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
aclr_bold,aclr_blue,
scwd,
aclr_reset,
aclr_bold,aclr_green,
'"',path,'"',
aclr_reset,
aclr_bold,aclr_blue,
(oflag == O_DIRECTORY) ? "O_DIRECTORY" : "O_RDONLY",
aclr_reset,
mode,
aclr_bold,
ret,
aclr_reset,
aclr_bold,aclr_red,
serr,
aclr_reset);
return ret;
}
static int slbt_lconf_trace_fstat_silent(
struct slbt_driver_ctx * dctx,
int fd,
const char * path,
struct stat * st)
{
(void)dctx;
return path ? fstatat(fd,path,st,0) : fstat(fd,st);
}
static int slbt_lconf_trace_fstat_plain(
struct slbt_driver_ctx * dctx,
int fd,
const char * path,
struct stat * st)
{
char scwd[20];
char serr[512];
char quot[2] = {'"',0};
int ret = path ? fstatat(fd,path,st,0) : fstat(fd,st);
int fderr = slbt_driver_fderr(dctx);
if (fd == AT_FDCWD) {
strcpy(scwd,"AT_FDCWD");
} else {
sprintf(scwd,"%d",fd);
}
if ((ret < 0) && (errno == ENOENT)) {
strcpy(serr," [ENOENT]");
} else if (ret < 0) {
memset(serr,0,sizeof(serr));
strerror_r(errno,&serr[2],sizeof(serr)-4);
serr[0] = ' ';
serr[1] = '(';
serr[strlen(serr)] = ')';
} else {
serr[0] = 0;
}
slbt_dprintf(
fderr,
"%s: %s: %s(%s%s%s%s%s,...) = %d%s%s",
dctx->program,
"lconf",
path ? "fstatat" : "fstat",
scwd,
path ? "," : "",
path ? quot : "",
path ? path : "",
path ? quot : "",
ret,
serr,
ret ? ".\n" : "");
if (ret == 0)
slbt_dprintf(
fderr,
" {.st_dev = %ld, .st_ino = %ld}.\n",
st->st_dev,
st->st_ino);
return ret;
}
static int slbt_lconf_trace_fstat_annotated(
struct slbt_driver_ctx * dctx,
int fd,
const char * path,
struct stat * st)
{
char scwd[20];
char serr[512];
char quot[2] = {'"',0};
int ret = path ? fstatat(fd,path,st,0) : fstat(fd,st);
int fderr = slbt_driver_fderr(dctx);
if (fd == AT_FDCWD) {
strcpy(scwd,"AT_FDCWD");
} else {
sprintf(scwd,"%d",fd);
}
if ((ret < 0) && (errno == ENOENT)) {
strcpy(serr," [ENOENT]");
} else if (ret < 0) {
memset(serr,0,sizeof(serr));
strerror_r(errno,&serr[2],sizeof(serr)-4);
serr[0] = ' ';
serr[1] = '(';
serr[strlen(serr)] = ')';
} else {
serr[0] = 0;
}
slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: %s(%s%s%s%s%s%s%s%s%s%s%s,...) = %s%d%s%s%s%s%s%s",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
path ? "fstatat" : "fstat",
aclr_bold,aclr_blue,
scwd,
aclr_reset,
aclr_bold,aclr_green,
path ? "," : "",
path ? quot : "",
path ? path : "",
path ? quot : "",
aclr_reset,
aclr_bold,
ret,
aclr_reset,
aclr_bold,aclr_red,
serr,
aclr_reset,
ret ? ".\n" : "");
if (ret == 0)
slbt_dprintf(
fderr,
" {%s%s.st_dev%s = %s%ld%s, %s%s.st_ino%s = %s%ld%s}.\n",
aclr_bold,aclr_yellow,aclr_reset,
aclr_bold,
st->st_dev,
aclr_reset,
aclr_bold,aclr_yellow,aclr_reset,
aclr_bold,
st->st_ino,
aclr_reset);
return ret;
}
static int slbt_lconf_trace_result_silent(
struct slbt_driver_ctx * dctx,
int fd,
int fdat,
const char * lconf,
int err)
{
(void)dctx;
(void)fd;
(void)fdat;
(void)lconf;
return err ? (-1) : fd;
}
static int slbt_lconf_trace_result_plain(
struct slbt_driver_ctx * dctx,
int fd,
int fdat,
const char * lconf,
int err)
{
int fderr;
const char * cpath;
char path[PATH_MAX];
fderr = slbt_driver_fderr(dctx);
cpath = !(slbt_realpath(fdat,lconf,0,path,sizeof(path)))
? path : lconf;
switch (err) {
case 0:
slbt_dprintf(
fderr,
"%s: %s: found %c%s%c.\n",
dctx->program,
"lconf",
'"',cpath,'"');
return fd;
case EXDEV:
slbt_dprintf(
fderr,
"%s: %s: stopped in %c%s%c "
"(config file not found on current device).\n",
dctx->program,
"lconf",
'"',cpath,'"');
return -1;
default:
slbt_dprintf(
fderr,
"%s: %s: stopped in %c%s%c "
"(top-level directory reached).\n",
dctx->program,
"lconf",
'"',cpath,'"');
return -1;
}
}
static int slbt_lconf_trace_result_annotated(
struct slbt_driver_ctx * dctx,
int fd,
int fdat,
const char * lconf,
int err)
{
int fderr;
const char * cpath;
char path[PATH_MAX];
fderr = slbt_driver_fderr(dctx);
cpath = !(slbt_realpath(fdat,lconf,0,path,sizeof(path)))
? path : lconf;
switch (err) {
case 0:
slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: found %s%s%c%s%c%s.\n",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
aclr_bold,aclr_green,
'"',cpath,'"',
aclr_reset);
return fd;
case EXDEV:
slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: stopped in %s%s%c%s%c%s "
"%s%s(config file not found on current device)%s.\n",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
aclr_bold,aclr_green,
'"',cpath,'"',
aclr_reset,
aclr_bold,aclr_red,
aclr_reset);
return -1;
default:
slbt_dprintf(
fderr,
"%s%s%s%s: %s%s%s: stopped in %s%s%c%s%c%s "
"%s%s(top-level directory reached)%s.\n",
aclr_bold,aclr_magenta,
dctx->program,
aclr_reset,
aclr_bold,
"lconf",
aclr_reset,
aclr_bold,aclr_green,
'"',cpath,'"',
aclr_reset,
aclr_bold,aclr_red,
aclr_reset);
return -1;
}
}
static int slbt_lconf_open(
struct slbt_driver_ctx * dctx,
const char * lconf)
{
int fderr;
int fdcwd;
int fdlconf;
int fdlconfdir;
int fdparent;
struct stat stcwd;
struct stat stparent;
ino_t stinode;
int (*trace_lconf)(struct slbt_driver_ctx *,
const char *);
int (*trace_fstat)(struct slbt_driver_ctx *,
int,const char *, struct stat *);
int (*trace_openat)(struct slbt_driver_ctx *,
int,const char *,int,int);
int (*trace_result)(struct slbt_driver_ctx *,
int,int,const char *,int);
lconf = lconf ? lconf : "libtool";
fderr = slbt_driver_fderr(dctx);
fdcwd = slbt_driver_fdcwd(dctx);
fdlconfdir = fdcwd;
if (dctx->cctx->drvflags & SLBT_DRIVER_SILENT) {
trace_lconf = 0;
trace_fstat = slbt_lconf_trace_fstat_silent;
trace_openat = slbt_lconf_trace_openat_silent;
trace_result = slbt_lconf_trace_result_silent;
} else if (dctx->cctx->drvflags & SLBT_DRIVER_ANNOTATE_NEVER) {
trace_lconf = slbt_lconf_trace_lconf_plain;
trace_fstat = slbt_lconf_trace_fstat_plain;
trace_openat = slbt_lconf_trace_openat_plain;
trace_result = slbt_lconf_trace_result_plain;
} else if (dctx->cctx->drvflags & SLBT_DRIVER_ANNOTATE_ALWAYS) {
trace_lconf = slbt_lconf_trace_lconf_annotated;
trace_fstat = slbt_lconf_trace_fstat_annotated;
trace_openat = slbt_lconf_trace_openat_annotated;
trace_result = slbt_lconf_trace_result_annotated;
} else if (isatty(fderr)) {
trace_lconf = slbt_lconf_trace_lconf_annotated;
trace_fstat = slbt_lconf_trace_fstat_annotated;
trace_openat = slbt_lconf_trace_openat_annotated;
trace_result = slbt_lconf_trace_result_annotated;
} else {
trace_lconf = slbt_lconf_trace_lconf_plain;
trace_fstat = slbt_lconf_trace_fstat_plain;
trace_openat = slbt_lconf_trace_openat_plain;
trace_result = slbt_lconf_trace_result_plain;
}
if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT)) {
trace_lconf(dctx,lconf);
slbt_output_fdcwd(dctx);
}
if (lconf && strchr(lconf,'/'))
return ((fdlconf = trace_openat(dctx,fdcwd,lconf,O_RDONLY,0)) < 0)
? SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_LCONF_OPEN)
: trace_result(dctx,fdlconf,fdcwd,lconf,0);
if (trace_fstat(dctx,fdlconfdir,".",&stcwd) < 0)
return SLBT_SYSTEM_ERROR(dctx,0);
stinode = stcwd.st_ino;
fdlconf = trace_openat(dctx,fdlconfdir,lconf,O_RDONLY,0);
while (fdlconf < 0) {
fdparent = trace_openat(dctx,fdlconfdir,"../",O_DIRECTORY,0);
slbt_lconf_close(fdcwd,fdlconfdir);
if (fdparent < 0)
return SLBT_SYSTEM_ERROR(dctx,0);
if (trace_fstat(dctx,fdparent,0,&stparent) < 0) {
close(fdparent);
return SLBT_SYSTEM_ERROR(dctx,0);
}
if (stparent.st_dev != stcwd.st_dev) {
trace_result(dctx,fdparent,fdparent,".",EXDEV);
close(fdparent);
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_OPEN);
}
if (stparent.st_ino == stinode) {
trace_result(dctx,fdparent,fdparent,".",ELOOP);
close(fdparent);
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_OPEN);
}
fdlconfdir = fdparent;
fdlconf = trace_openat(dctx,fdlconfdir,lconf,O_RDONLY,0);
stinode = stparent.st_ino;
}
trace_result(dctx,fdlconf,fdlconfdir,lconf,0);
slbt_lconf_close(fdcwd,fdlconfdir);
return fdlconf;
}
static int slbt_get_lconf_var(
void * addr,
const char * cap,
const char * var,
const char space,
char (*val)[PATH_MAX])
{
const char * mark;
const char * match;
ssize_t len;
int cint;
/* init */
len = strlen(var);
mark = addr;
match = 0;
memset(*val,0,PATH_MAX);
/* search for ^var= */
for (; (mark < cap) && !match; ) {
if ((cap - mark) <= len)
return 0;
if (!strncmp(mark,var,len)) {
match = mark;
} else {
while ((*mark != '\n') && (mark < cap))
mark++;
while (isspace((cint=*mark)) && (mark < cap))
mark++;
}
}
/* not found? */
if (mark == cap)
return 0;
/* support a single pair of double quotes */
match = &match[len];
mark = match;
if (match[0] == '"') {
match++;
mark++;
for (; (*mark != '"') && (mark < cap); )
mark++;
} else {
for (; !isspace((cint=*mark)) && (mark < cap); )
mark++;
}
/* validate */
if ((len = mark - match) >= PATH_MAX)
return -1;
/* copy and validate */
strncpy(*val,match,len);
for (mark=*val; *mark; mark++) {
if ((*mark >= 'a') && (*mark <= 'z'))
(void)0;
else if ((*mark >= 'A') && (*mark <= 'Z'))
(void)0;
else if ((*mark >= '0') && (*mark <= '9'))
(void)0;
else if ((*mark == '+') || (*mark == '-'))
(void)0;
else if ((*mark == '/') || (*mark == '@'))
(void)0;
else if ((*mark == '.') || (*mark == '_'))
(void)0;
else if ((*mark == ':') || (*mark == space))
(void)0;
else
return -1;
}
return 0;
}
slbt_hidden int slbt_get_lconf_flags(
struct slbt_driver_ctx * dctx,
const char * lconf,
uint64_t * flags)
{
struct slbt_driver_ctx_impl * ctx;
int fdlconf;
struct stat st;
void * addr;
const char * mark;
const char * cap;
uint64_t optshared;
uint64_t optstatic;
char val[PATH_MAX];
/* driver context (ar, ranlib, cc) */
ctx = slbt_get_driver_ictx(dctx);
/* open relative libtool script */
if ((fdlconf = slbt_lconf_open(dctx,lconf)) < 0)
return SLBT_NESTED_ERROR(dctx);
/* map relative libtool script */
if (fstat(fdlconf,&st) < 0)
return SLBT_SYSTEM_ERROR(dctx,0);
addr = mmap(
0,st.st_size,
PROT_READ,MAP_SHARED,
fdlconf,0);
close(fdlconf);
if (addr == MAP_FAILED)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_MAP);
mark = addr;
cap = &mark[st.st_size];
/* scan */
optshared = 0;
optstatic = 0;
/* shared libraries option */
if (slbt_get_lconf_var(addr,cap,"build_libtool_libs=",0,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (!strcmp(val,"yes")) {
optshared = SLBT_DRIVER_SHARED;
} else if (!strcmp(val,"no")) {
optshared = SLBT_DRIVER_DISABLE_SHARED;
}
/* static libraries option */
if (slbt_get_lconf_var(addr,cap,"build_old_libs=",0,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (!strcmp(val,"yes")) {
optstatic = SLBT_DRIVER_STATIC;
} else if (!strcmp(val,"no")) {
optstatic = SLBT_DRIVER_DISABLE_STATIC;
}
if (!optshared || !optstatic)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
*flags = optshared | optstatic;
/* host */
if (!ctx->cctx.host.host) {
if (slbt_get_lconf_var(addr,cap,"host=",0,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (val[0] && !(ctx->host.host = strdup(val)))
return SLBT_SYSTEM_ERROR(dctx,0);
ctx->cctx.host.host = ctx->host.host;
}
/* ar tool */
if (!ctx->cctx.host.ar) {
if (slbt_get_lconf_var(addr,cap,"AR=",0x20,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (val[0] && !(ctx->host.ar = strdup(val)))
return SLBT_SYSTEM_ERROR(dctx,0);
ctx->cctx.host.ar = ctx->host.ar;
}
/* ranlib tool */
if (!ctx->cctx.host.ranlib) {
if (slbt_get_lconf_var(addr,cap,"RANLIB=",0x20,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (val[0] && !(ctx->host.ranlib = strdup(val)))
return SLBT_SYSTEM_ERROR(dctx,0);
ctx->cctx.host.ranlib = ctx->host.ranlib;
}
/* as tool (optional) */
if (!ctx->cctx.host.as) {
if (slbt_get_lconf_var(addr,cap,"AS=",0x20,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (val[0] && !(ctx->host.as = strdup(val)))
return SLBT_SYSTEM_ERROR(dctx,0);
ctx->cctx.host.as = ctx->host.as;
}
/* dlltool tool (optional) */
if (!ctx->cctx.host.dlltool) {
if (slbt_get_lconf_var(addr,cap,"DLLTOOL=",0x20,&val) < 0)
return SLBT_CUSTOM_ERROR(
dctx,SLBT_ERR_LCONF_PARSE);
if (val[0] && !(ctx->host.dlltool = strdup(val)))
return SLBT_SYSTEM_ERROR(dctx,0);
ctx->cctx.host.dlltool = ctx->host.dlltool;
}
/* all done */
ctx->lconf.addr = addr;
ctx->lconf.size = st.st_size;
return 0;
}