Skip to content

Commit 684e886

Browse files
committed
drivers/main-stub.c: separate from main.c and register_upsdrv_callbacks() to allow dynamic linking [#2800]
Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 30eb0a0 commit 684e886

File tree

6 files changed

+165
-33
lines changed

6 files changed

+165
-33
lines changed

drivers/Makefile.am

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ if ENABLE_SHARED_PRIVATE_LIBS
5151
LDADD_COMMON = \
5252
$(top_builddir)/common/libnutprivate-common-all.la
5353

54-
LDADD_DRIVERS = libnutprivate-drivers-common.la $(LDADD_COMMON)
54+
LDADD_DRIVERS = libdummy_main_stub.la libnutprivate-drivers-common.la $(LDADD_COMMON)
5555

5656
LDADD_DRIVERS_SERIAL = libnutprivate-drivers-serial.la $(LDADD_DRIVERS)
5757

@@ -508,14 +508,24 @@ dist_noinst_HEADERS = \
508508
# because per-object CFLAGS can only be specified for libraries, not
509509
# for object files. This library is used during the build process,
510510
# and is not meant to be installed.
511-
EXTRA_LTLIBRARIES = libdummy_main.la libdummy_serial.la libdummy_upsdrvquery.la
512-
511+
EXTRA_LTLIBRARIES = libdummy_main.la libdummy_main_stub.la libdummy_serial.la libdummy_upsdrvquery.la
512+
513+
# NOTE: main.c includes tons of shared logic and global variables
514+
# used by all NUT drivers, so is a prime candidate for being a
515+
# shared library if we ENABLE_SHARED_PRIVATE_LIBS. But then we
516+
# have a chicken-and-egg problem, that actual NUT driver program
517+
# sources, which declare upsdrv_*() methods and info, have no way
518+
# to make the shared library variant of main code to see them.
519+
# This is where the main-stub.c comes in, with a small bit of code
520+
# compiled and linked into each driver binary to marry the two worlds.
513521
libdummy_main_la_SOURCES = main.c dstate.c
522+
libdummy_main_stub_la_SOURCES = main-stub.c
514523
libdummy_serial_la_SOURCES = serial.c
515524
libdummy_upsdrvquery_la_SOURCES = upsdrvquery.c
516525

517526
if !ENABLE_SHARED_PRIVATE_LIBS
518527
libdummy_main_la_LDFLAGS = -no-undefined -static
528+
libdummy_main_stub_la_LDFLAGS = -no-undefined -static
519529
libdummy_serial_la_LDFLAGS = -no-undefined -static
520530
libdummy_upsdrvquery_la_LDFLAGS = -no-undefined -static
521531
endif !ENABLE_SHARED_PRIVATE_LIBS

drivers/main-stub.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* main-stub.c - Network UPS Tools driver core stub
2+
3+
This code is linked into each binary to ensure that even if the
4+
NUT drivers are built as dynamically-linked programs, the shared
5+
library can find needed methods and data provided by each specific
6+
driver program source file(s) and declared by main.h.
7+
8+
Copyright (C)
9+
2026 - Jim Klimov <jimklimov+nut@gmail.com>
10+
11+
This program is free software; you can redistribute it and/or modify
12+
it under the terms of the GNU General Public License as published by
13+
the Free Software Foundation; either version 2 of the License, or
14+
(at your option) any later version.
15+
16+
This program is distributed in the hope that it will be useful,
17+
but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
GNU General Public License for more details.
20+
21+
You should have received a copy of the GNU General Public License
22+
along with this program; if not, write to the Free Software
23+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24+
*/
25+
26+
#include "common.h"
27+
#include "main.h"
28+
29+
#if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS
30+
/* Provided by main.c in shared-mode builds */
31+
int drv_main(int argc, char **argv);
32+
33+
/* This source file is used in some unit tests to mock realistic driver
34+
* behavior - using a production driver skeleton, but their own main().
35+
*/
36+
# ifndef DRIVERS_MAIN_WITHOUT_MAIN
37+
int main(int argc, char **argv)
38+
{
39+
/* shared build, symbols should be visible to us right away,
40+
* but not to (library-stored copy of) main.c */
41+
default_register_upsdrv_callbacks();
42+
return drv_main(argc, argv);
43+
}
44+
# endif /* DRIVERS_MAIN_WITHOUT_MAIN */
45+
#else /* !ENABLE_SHARED_PRIVATE_LIBS */
46+
47+
/* Just avoid warning/error: ISO C forbids an empty translation unit [-Werror=pedantic] */
48+
int main (int argc, char ** argv);
49+
50+
#endif /* !ENABLE_SHARED_PRIVATE_LIBS */

drivers/main.c

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
/* main.c - Network UPS Tools driver core
1+
/* main.c - Network UPS Tools driver core logic and global variables
22
33
Copyright (C)
44
1999 Russell Kroll <rkroll@exploits.org>
55
2005 - 2017 Arnaud Quette <arnaud.quette@free.fr>
66
2017 Eaton (author: Emilien Kia <EmilienKia@Eaton.com>)
7-
2017 - 2025 Jim Klimov <jimklimov+nut@gmail.com>
7+
2017 - 2026 Jim Klimov <jimklimov+nut@gmail.com>
88
99
This program is free software; you can redistribute it and/or modify
1010
it under the terms of the GNU General Public License as published by
@@ -164,30 +164,39 @@ static int handle_reload_flag(void);
164164
/* Set in do_ups_confargs() for consumers like handle_reload_flag() */
165165
static int reload_requires_restart = -1;
166166

167+
static upsdrv_callback_t upsdrv_callbacks = {0};
168+
void register_upsdrv_callbacks(upsdrv_callback_t callbacks) {
169+
/*FIXME: mempcy of arbitrarily ordered list of function pointers
170+
* does not feel safe, better use named methods to initialize?
171+
* upsdrv_callbacks = callbacks;
172+
*/
173+
memcpy(&upsdrv_callbacks, &callbacks, sizeof(callbacks));
174+
}
175+
167176
/* print the driver banner */
168177
void upsdrv_banner (void)
169178
{
170179
int i;
171180

172181
printf("Network UPS Tools %s - %s%s %s\n",
173182
describe_NUT_VERSION_once(),
174-
upsdrv_info.name,
175-
strstr(upsdrv_info.name, "river") ? "" : " driver",
176-
upsdrv_info.version);
183+
upsdrv_callbacks.upsdrv_info.name,
184+
strstr(upsdrv_callbacks.upsdrv_info.name, "river") ? "" : " driver",
185+
upsdrv_callbacks.upsdrv_info.version);
177186

178187
/* process sub driver(s) information */
179-
for (i = 0; upsdrv_info.subdrv_info[i]; i++) {
188+
for (i = 0; upsdrv_callbacks.upsdrv_info.subdrv_info[i]; i++) {
180189

181-
if (!upsdrv_info.subdrv_info[i]->name) {
190+
if (!upsdrv_callbacks.upsdrv_info.subdrv_info[i]->name) {
182191
continue;
183192
}
184193

185-
if (!upsdrv_info.subdrv_info[i]->version) {
194+
if (!upsdrv_callbacks.upsdrv_info.subdrv_info[i]->version) {
186195
continue;
187196
}
188197

189-
printf("%s %s\n", upsdrv_info.subdrv_info[i]->name,
190-
upsdrv_info.subdrv_info[i]->version);
198+
printf("%s %s\n", upsdrv_callbacks.upsdrv_info.subdrv_info[i]->name,
199+
upsdrv_callbacks.upsdrv_info.subdrv_info[i]->version);
191200
}
192201

193202
fflush(stdout);
@@ -304,7 +313,7 @@ static void help_msg(void)
304313
}
305314
}
306315

307-
upsdrv_help();
316+
upsdrv_callbacks.upsdrv_help();
308317

309318
printf("\n%s", suggest_doc_links(progname, "ups.conf"));
310319
}
@@ -846,7 +855,7 @@ int do_loop_shutdown_commands(const char *sdcmds, char **cmdused) {
846855
sdcmds);
847856
/* TOTHINK : These logs are handled in driver codes */
848857
/* upslog_INSTCMD_POWERSTATE_CHANGE("shutdown.default", NULL); */
849-
upsdrv_shutdown();
858+
upsdrv_callbacks.upsdrv_shutdown();
850859
cmdret = STAT_INSTCMD_HANDLED;
851860
/* commented below */
852861
if (cmdused && !(*cmdused))
@@ -865,7 +874,7 @@ int do_loop_shutdown_commands(const char *sdcmds, char **cmdused) {
865874
/* TOTHINK : These logs are handled in driver codes */
866875
/* We are trying (if at all implemented), so "maybe"... */
867876
/* upslog_INSTCMD_POWERSTATE_MAYBE(s, NULL); */
868-
upsdrv_shutdown();
877+
upsdrv_callbacks.upsdrv_shutdown();
869878
cmdret = STAT_INSTCMD_HANDLED;
870879
} else {
871880
/* TOTHINK : These logs are handled in driver codes */
@@ -1017,7 +1026,7 @@ int main_instcmd(const char *cmdname, const char *extra, conn_t *conn) {
10171026
* called by default if none were passed (or this one
10181027
* was passed explicitly).
10191028
*/
1020-
upsdrv_shutdown();
1029+
upsdrv_callbacks.upsdrv_shutdown();
10211030
return STAT_INSTCMD_HANDLED;
10221031
}
10231032

@@ -1896,7 +1905,7 @@ void vartab_free(void)
18961905
static void exit_upsdrv_cleanup(void)
18971906
{
18981907
dstate_setinfo("driver.state", "cleanup.upsdrv");
1899-
upsdrv_cleanup();
1908+
upsdrv_callbacks.upsdrv_cleanup();
19001909
}
19011910

19021911
static void exit_cleanup(void)
@@ -2082,9 +2091,15 @@ void setup_signals(void)
20822091

20832092
/* This source file is used in some unit tests to mock realistic driver
20842093
* behavior - using a production driver skeleton, but their own main().
2094+
* It is called from main-stub.c in shared-mode builds.
20852095
*/
20862096
#ifndef DRIVERS_MAIN_WITHOUT_MAIN
2097+
# if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS
2098+
int drv_main(int argc, char **argv)
2099+
# else
2100+
/* Used right away */
20872101
int main(int argc, char **argv)
2102+
# endif
20882103
{
20892104
struct passwd *new_uid = NULL;
20902105
int i, do_forceshutdown = 0;
@@ -2110,6 +2125,9 @@ int main(int argc, char **argv)
21102125

21112126
#if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS
21122127
callback_upsconf_args = do_upsconf_args;
2128+
#else
2129+
/* static build, symbols should be visible to main.c right away */
2130+
default_register_upsdrv_callbacks();
21132131
#endif
21142132

21152133
/* init verbosity from default in common.c (0 probably) */
@@ -2206,21 +2224,21 @@ int main(int argc, char **argv)
22062224
}
22072225
#endif /* WIN32 */
22082226

2209-
upsdrv_tweak_prognames();
2227+
upsdrv_callbacks.upsdrv_tweak_prognames();
22102228

22112229
open_syslog(progname);
22122230

22132231
if (!banner_is_disabled()) {
22142232
upsdrv_banner();
22152233
}
22162234

2217-
if (upsdrv_info.status == DRV_EXPERIMENTAL) {
2235+
if (upsdrv_callbacks.upsdrv_info.status == DRV_EXPERIMENTAL) {
22182236
printf("Warning: This is an experimental driver.\n");
22192237
printf("Some features may not function correctly.\n\n");
22202238
}
22212239

22222240
/* build the driver's extra (-x) variable table */
2223-
upsdrv_makevartable();
2241+
upsdrv_callbacks.upsdrv_makevartable();
22242242

22252243
while ((i = getopt(argc, argv, optstring)) != -1) {
22262244
switch (i) {
@@ -2903,20 +2921,20 @@ int main(int argc, char **argv)
29032921
dstate_setinfo("device.type", "ups");
29042922

29052923
dstate_setinfo("driver.state", "init.device");
2906-
upsdrv_initups();
2924+
upsdrv_callbacks.upsdrv_initups();
29072925
dstate_setinfo("driver.state", "init.quiet");
29082926

29092927
/* UPS is detected now, cleanup upon exit */
29102928
atexit(exit_upsdrv_cleanup);
29112929

29122930
/* now see if things are very wrong out there */
2913-
if (upsdrv_info.status == DRV_BROKEN) {
2931+
if (upsdrv_callbacks.upsdrv_info.status == DRV_BROKEN) {
29142932
fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n");
29152933
}
29162934

29172935
/* publish the top-level data: version numbers, driver name */
29182936
dstate_setinfo("driver.version", "%s", UPS_VERSION);
2919-
dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version);
2937+
dstate_setinfo("driver.version.internal", "%s", upsdrv_callbacks.upsdrv_info.version);
29202938
dstate_setinfo("driver.name", "%s", progname);
29212939

29222940
/*
@@ -2929,7 +2947,7 @@ int main(int argc, char **argv)
29292947

29302948
/* get the base data established before allowing connections */
29312949
dstate_setinfo("driver.state", "init.info");
2932-
upsdrv_initinfo();
2950+
upsdrv_callbacks.upsdrv_initinfo();
29332951

29342952
/* Register a way to call upsdrv_shutdown() among `sdcommands` */
29352953
dstate_addcmd("shutdown.default");
@@ -2942,7 +2960,7 @@ int main(int argc, char **argv)
29422960
/* Note: a few drivers also call their upsdrv_updateinfo() during
29432961
* their upsdrv_initinfo(), possibly to impact the initialization */
29442962
dstate_setinfo("driver.state", "init.updateinfo");
2945-
upsdrv_updateinfo();
2963+
upsdrv_callbacks.upsdrv_updateinfo();
29462964
dstate_setinfo("driver.state", "init.quiet");
29472965

29482966
if (dstate_getinfo("driver.flag.ignorelb")) {
@@ -3183,7 +3201,7 @@ int main(int argc, char **argv)
31833201
}
31843202

31853203
dstate_setinfo("driver.state", "updateinfo");
3186-
upsdrv_updateinfo();
3204+
upsdrv_callbacks.upsdrv_updateinfo();
31873205
dstate_setinfo("driver.state", "quiet");
31883206

31893207
/* Dump the data tree (in upsc-like format) to stdout and exit */

drivers/main.h

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,24 @@ extern const char *prognames[MAX_PROGNAMES];
2828
extern char prognames_should_free[MAX_PROGNAMES];
2929
#define progname (prognames[0])
3030

31-
/* functions & variables required in each driver */
31+
int drv_main(int argc, char **argv);
32+
33+
/* functions & variables required in each driver
34+
* See also: register_upsdrv_callbacks()
35+
*/
3236
void upsdrv_tweak_prognames(void); /* optionally add aliases and/or set preferred name into [0] (for pipe name etc.); called just after populating prognames[0] and prognames_should_free[] entries */
3337
void upsdrv_initups(void); /* open connection to UPS, fail if not found */
3438
void upsdrv_initinfo(void); /* prep data, settings for UPS monitoring */
3539
void upsdrv_updateinfo(void); /* update state data if possible */
3640
void upsdrv_shutdown(void); /* make the UPS power off the load */
3741
void upsdrv_help(void); /* tack on anything useful for the -h text */
38-
void upsdrv_banner(void); /* print your version information */
3942
void upsdrv_cleanup(void); /* free any resources before shutdown */
43+
void upsdrv_makevartable(void); /* main calls this driver function - it needs to call addvar */
4044

4145
void set_exit_flag(int sig);
4246

47+
void upsdrv_banner(void); /* print your version information - shared in main.c */
48+
4349
/* --- details for the variable/value sharing --- */
4450

4551
/* Try each instant command in the comma-separated list of
@@ -79,9 +85,6 @@ int main_instcmd(const char *cmdname, const char *extra, conn_t *conn);
7985
*/
8086
int main_setvar(const char *varname, const char *val, conn_t *conn);
8187

82-
/* main calls this driver function - it needs to call addvar */
83-
void upsdrv_makevartable(void);
84-
8588
/* retrieve the value of variable <var> if possible */
8689
char *getval(const char *var);
8790

@@ -155,7 +158,9 @@ typedef struct upsdrv_info_s {
155158
* pass */
156159
/* FIXME: complete with mfr support, and other interesting info */
157160

158-
/* public driver information from the driver file */
161+
/* public driver information from the driver file
162+
* See also: register_upsdrv_callbacks()
163+
*/
159164
extern upsdrv_info_t upsdrv_info;
160165

161166
/* functions and data possibly used via libdummy_mockdrv.la for unit-tests */
@@ -197,4 +202,39 @@ void setup_signals(void);
197202
# define SIGCMD_RELOAD_OR_ERROR "driver.reload-or-error" /* NUT_WIN32_INCOMPLETE */
198203
#endif /* WIN32 */
199204

205+
/* allow main.c code in both static and shared driver builds
206+
* to see implementations defined by specific driver sources,
207+
* called by main-stub.c where used:
208+
*/
209+
typedef struct upsdrv_callback_s {
210+
void (*upsdrv_tweak_prognames)(void); /* optionally add aliases and/or set preferred name into [0] (for pipe name etc.); called just after populating prognames[0] and prognames_should_free[] entries */
211+
void (*upsdrv_initups)(void); /* open connection to UPS, fail if not found */
212+
void (*upsdrv_initinfo)(void); /* prep data, settings for UPS monitoring */
213+
void (*upsdrv_updateinfo)(void); /* update state data if possible */
214+
void (*upsdrv_shutdown)(void); /* make the UPS power off the load */
215+
void (*upsdrv_help)(void); /* tack on anything useful for the -h text */
216+
void (*upsdrv_cleanup)(void); /* free any resources before shutdown */
217+
void (*upsdrv_makevartable)(void); /* main calls this driver function - it needs to call addvar */
218+
upsdrv_info_t upsdrv_info; /* public driver information from the driver file */
219+
} upsdrv_callback_t;
220+
void register_upsdrv_callbacks(upsdrv_callback_t callbacks);
221+
222+
/* simple call to register implementations named as dictated
223+
* by this header, which (being a macro) can be called easily
224+
* from both static and shared builds: */
225+
#define default_register_upsdrv_callbacks() do { \
226+
upsdrv_callback_t callbacksTmp; \
227+
memset(&callbacksTmp, 0, sizeof(callbacksTmp)); \
228+
callbacksTmp.upsdrv_cleanup = upsdrv_cleanup; \
229+
callbacksTmp.upsdrv_help = upsdrv_help; \
230+
callbacksTmp.upsdrv_initups = upsdrv_initups; \
231+
callbacksTmp.upsdrv_initinfo = upsdrv_initinfo; \
232+
callbacksTmp.upsdrv_makevartable = upsdrv_makevartable; \
233+
callbacksTmp.upsdrv_shutdown = upsdrv_shutdown; \
234+
callbacksTmp.upsdrv_tweak_prognames = upsdrv_tweak_prognames; \
235+
callbacksTmp.upsdrv_updateinfo = upsdrv_updateinfo; \
236+
memcpy(&callbacksTmp.upsdrv_info, &upsdrv_info, sizeof(upsdrv_info)); \
237+
register_upsdrv_callbacks(callbacksTmp); \
238+
} while (0)
239+
200240
#endif /* NUT_MAIN_H_SEEN */

0 commit comments

Comments
 (0)