Skip to content

Commit

Permalink
linux: use dlopen for libnl3 instead of dynamic linking
Browse files Browse the repository at this point in the history
Instead of the current behavior of dynamic linking against libnl3 and
libnl-genl-3 when configured with --enable-delayacct, load the shared
libraries on request, if any delay accounting related process field is
active, via dlopen(3), similar to libsensors and libsystemd.

Distribution, who currently build htop with --enable-delayacct, need to
explicitly add libnl3 and libnl-genl-3 as runtime dependencies to
continue supporting delay accounting out-of-the-box.
  • Loading branch information
cgzones committed Apr 5, 2024
1 parent a782ef3 commit 24b1513
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 51 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ List of additional build-time dependencies (based on feature flags):
* `sensors`
* `hwloc`
* `libcap` (v2.21 or later)
* `libnl-3`
* `libnl-3` and `libnl-genl-3`

Install these and other required packages for C development from your package manager.

Expand Down Expand Up @@ -137,7 +137,7 @@ To install on the local system run `make install`. By default `make install` ins
- default: *no*
* `--enable-delayacct`:
enable Linux delay accounting support
- dependencies: *pkg-config*(build-time), *libnl-3* and *libnl-genl-3*
- dependencies: *libnl-3-dev*(build-time) and *libnl-genl-3-dev*(build-time), at runtime *libnl-3* and *libnl-genl-3* are loaded via `dlopen(3)` if available and requested
- default: *check*


Expand All @@ -153,6 +153,7 @@ To install on the local system run `make install`. By default `make install` ins
* `libcap`, user-space interfaces to POSIX 1003.1e capabilities, is always required when `--enable-capabilities` was used to configure `htop`.
* `libsensors`, readout of temperatures and CPU speeds, is optional even when `--enable-sensors` was used to configure `htop`.
* `libsystemd` is optional when `--enable-static` was not used to configure `htop`. If building statically and `libsystemd` is not found by `configure`, support for the systemd meter is disabled entirely.
* `libnl-3` and `libnl-genl-3`, if `htop` was configured with `--enable-delayacct` and delay accounting process fields are active.

`htop` checks for the availability of the actual runtime libraries as `htop` runs.

Expand Down
38 changes: 12 additions & 26 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -658,40 +658,26 @@ case "$enable_delayacct" in
elif test "$enable_static" = yes; then
enable_delayacct=no
else
m4_ifdef([PKG_PROG_PKG_CONFIG], [
enable_delayacct=yes
PKG_PROG_PKG_CONFIG()
PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [enable_delayacct=no])
PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [enable_delayacct=no])
if test "$enable_delayacct" = yes; then
AM_CFLAGS="$AM_CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
fi
], [
enable_delayacct=no
AC_MSG_NOTICE([Linux delay accounting support can not be enabled, cause pkg-config is required for checking its availability])
])
old_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -I/usr/include/libnl3"
AC_CHECK_HEADERS([netlink/attr.h netlink/handlers.h netlink/msg.h], [enable_delayacct=yes], [enable_delayacct=no])
CFLAGS="$old_CFLAGS"
fi
;;
yes)
m4_ifdef([PKG_PROG_PKG_CONFIG], [
PKG_PROG_PKG_CONFIG()
PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3])])
PKG_CHECK_MODULES(LIBNL3GENL, libnl-genl-3.0, [], [AC_MSG_ERROR([can not find required library libnl3genl])])
AM_CFLAGS="$AM_CFLAGS $LIBNL3_CFLAGS $LIBNL3GENL_CFLAGS"
LIBS="$LIBS $LIBNL3_LIBS $LIBNL3GENL_LIBS"
AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
], [
pkg_m4_absent=1
m4_warning([configure is generated without pkg.m4. 'make dist' target will be disabled.])
AC_MSG_ERROR([htop on Linux requires pkg-config for checking delayacct requirements. Please install pkg-config and run ./autogen.sh to rebuild the configure script.])
])
old_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -I/usr/include/libnl3"
AC_CHECK_HEADERS([netlink/attr.h netlink/handlers.h netlink/msg.h], [], [AC_MSG_ERROR([can not find required header files netlink/attr.h, netlink/handlers.h, netlink/msg.h])])
CFLAGS="$old_CFLAGS"
;;
*)
AC_MSG_ERROR([bad value '$enable_delayacct' for --enable-delayacct])
;;
esac
if test "$enable_delayacct" = yes; then
AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])
AM_CFLAGS="$AM_CFLAGS -I/usr/include/libnl3"
fi
AM_CONDITIONAL([HAVE_DELAYACCT], [test "$enable_delayacct" = yes])


Expand Down
159 changes: 136 additions & 23 deletions linux/LibNl.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,149 @@ in the source distribution for its full text.

#include "linux/LibNl.h"

#include <dlfcn.h>

#include <linux/netlink.h>
#include <linux/taskstats.h>

#include <netlink/attr.h>
#include <netlink/handlers.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>


static void* libnlHandle;
static void* libnlGenlHandle;

static void (*sym_nl_close)(struct nl_sock*);
static int (*sym_nl_connect)(struct nl_sock*, int);
static int (*sym_nl_recvmsgs_default)(struct nl_sock*);
static int (*sym_nl_send_sync)(struct nl_sock*, struct nl_msg*);
static struct nl_sock* (*sym_nl_socket_alloc)(void);
static void (*sym_nl_socket_free)(struct nl_sock*);
static int (*sym_nl_socket_modify_cb)(struct nl_sock*, enum nl_cb_type, enum nl_cb_kind, nl_recvmsg_msg_cb_t, void*);
static void* (*sym_nla_data)(const struct nlattr*);
static struct nlattr* (*sym_nla_next)(const struct nlattr*, int*);
static int (*sym_nla_put_u32)(struct nl_msg*, int, uint32_t);
static struct nl_msg* (*sym_nlmsg_alloc)(void);
static struct nlmsghdr* (*sym_nlmsg_hdr)(struct nl_msg*);
static void (*sym_nlmsg_free)(struct nl_msg*);

static int (*sym_genl_ctrl_resolve)(struct nl_sock*, const char*);
static int (*sym_genlmsg_parse)(struct nlmsghdr*, int, struct nlattr**, int, const struct nla_policy*);
static void* (*sym_genlmsg_put)(struct nl_msg*, uint32_t, uint32_t, int, int, int, uint8_t, uint8_t);


static void unload_libnl(void) {
sym_nl_close = NULL;
sym_nl_connect = NULL;
sym_nl_recvmsgs_default = NULL;
sym_nl_send_sync = NULL;
sym_nl_socket_alloc = NULL;
sym_nl_socket_free = NULL;
sym_nl_socket_modify_cb = NULL;
sym_nla_data = NULL;
sym_nla_next = NULL;
sym_nla_put_u32 = NULL;
sym_nlmsg_alloc = NULL;
sym_nlmsg_free = NULL;
sym_nlmsg_hdr = NULL;

sym_genl_ctrl_resolve = NULL;
sym_genlmsg_parse = NULL;
sym_genlmsg_put = NULL;

if (libnlGenlHandle) {
dlclose(libnlGenlHandle);
libnlGenlHandle = NULL;
}
if (libnlHandle) {
dlclose(libnlHandle);
libnlHandle = NULL;
}
}

static int load_libnl(void) {
if (libnlHandle && libnlGenlHandle)
return 0;

libnlHandle = dlopen("libnl-3.so", RTLD_LAZY);
if (!libnlHandle) {
libnlHandle = dlopen("libnl-3.so.200", RTLD_LAZY);
if (!libnlHandle) {
goto dlfailure;
}
}

libnlGenlHandle = dlopen("libnl-genl-3.so", RTLD_LAZY);
if (!libnlGenlHandle) {
libnlGenlHandle = dlopen("libnl-genl-3.so.200", RTLD_LAZY);
if (!libnlGenlHandle) {
goto dlfailure;
}
}

/* Clear any errors */
dlerror();

#define resolve(handle, symbolname) do { \
*(void **)(&sym_##symbolname) = dlsym(handle, #symbolname); \
if (!sym_##symbolname || dlerror() != NULL) { \
goto dlfailure; \
} \
} while(0)

resolve(libnlHandle, nl_close);
resolve(libnlHandle, nl_connect);
resolve(libnlHandle, nl_recvmsgs_default);
resolve(libnlHandle, nl_send_sync);
resolve(libnlHandle, nl_socket_alloc);
resolve(libnlHandle, nl_socket_free);
resolve(libnlHandle, nl_socket_modify_cb);
resolve(libnlHandle, nla_data);
resolve(libnlHandle, nla_next);
resolve(libnlHandle, nla_put_u32);
resolve(libnlHandle, nlmsg_alloc);
resolve(libnlHandle, nlmsg_free);
resolve(libnlHandle, nlmsg_hdr);

resolve(libnlGenlHandle, genl_ctrl_resolve);
resolve(libnlGenlHandle, genlmsg_parse);
resolve(libnlGenlHandle, genlmsg_put);

#undef resolve

return 0;

dlfailure:
unload_libnl();
return -1;
}

static void initNetlinkSocket(LinuxProcessTable* this) {
this->netlink_socket = nl_socket_alloc();
if (load_libnl() < 0) {
return;
}

this->netlink_socket = sym_nl_socket_alloc();
if (this->netlink_socket == NULL) {
return;
}
if (nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {
if (sym_nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {
return;
}
this->netlink_family = genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);
this->netlink_family = sym_genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);
}

void LibNl_destroyNetlinkSocket(LinuxProcessTable* this) {
if (!this->netlink_socket)
return;
if (this->netlink_socket) {
assert(libnlHandle);

sym_nl_close(this->netlink_socket);
sym_nl_socket_free(this->netlink_socket);
this->netlink_socket = NULL;
}

nl_close(this->netlink_socket);
nl_socket_free(this->netlink_socket);
this->netlink_socket = NULL;
unload_libnl();
}

static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
Expand All @@ -53,14 +166,14 @@ static int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {
int rem;
LinuxProcess* lp = (LinuxProcess*) linuxProcess;

nlhdr = nlmsg_hdr(nlmsg);
nlhdr = sym_nlmsg_hdr(nlmsg);

if (genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {
if (sym_genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {
return NL_SKIP;
}

if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {
memcpy(&stats, nla_data(nla_next(nla_data(nlattr), &rem)), sizeof(stats));
memcpy(&stats, sym_nla_data(sym_nla_next(sym_nla_data(nlattr), &rem)), sizeof(stats));
assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid);

// The xxx_delay_total values wrap around on overflow.
Expand Down Expand Up @@ -90,27 +203,27 @@ void LibNl_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) {
}
}

if (nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
if (sym_nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {
goto delayacct_failure;
}

if (! (msg = nlmsg_alloc())) {
if (! (msg = sym_nlmsg_alloc())) {
goto delayacct_failure;
}

if (! genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {
nlmsg_free(msg);
if (! sym_genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {
sym_nlmsg_free(msg);
}

if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) {
nlmsg_free(msg);
if (sym_nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) {
sym_nlmsg_free(msg);
}

if (nl_send_sync(this->netlink_socket, msg) < 0) {
if (sym_nl_send_sync(this->netlink_socket, msg) < 0) {
goto delayacct_failure;
}

if (nl_recvmsgs_default(this->netlink_socket) < 0) {
if (sym_nl_recvmsgs_default(this->netlink_socket) < 0) {
goto delayacct_failure;
}

Expand Down

0 comments on commit 24b1513

Please sign in to comment.