4545# include <signal.h>
4646/* #include <poll.h> */
4747# endif
48+ # ifdef HAVE_SYS_RESOURCE_H
49+ # include <sys/resource.h> /* for getrlimit() and struct rlimit */
50+ # endif
4851#else /* WIN32 */
4952/* Those 2 files for support of getaddrinfo, getnameinfo and freeaddrinfo
5053 on Windows 2000 and older versions */
@@ -94,12 +97,14 @@ int allow_no_device = 0;
9497 */
9598int allow_not_all_listeners = 0 ;
9699
97- /* preloaded to POSIX sysconf(_SC_OPEN_MAX) or WIN32 MAX_WAIT_OBJECTS in main
100+ /* Preloaded to POSIX sysconf(_SC_OPEN_MAX) or WIN32 MAX_WAIT_OBJECTS in main
98101 * and elsewhere, the run-time value can be overridden via upsd.conf `MAXCONN`
99102 * option (may cause partial waits chunk by chunk, if sysmaxconn is smaller).
103+ * The sysmaxconn_hard is derived from getrlimit() (aka `ulimit` on allowed
104+ * opened file descriptors) where available.
100105 */
101106nfds_t maxconn = 0 ;
102- static nfds_t sysmaxconn = 0 ;
107+ static nfds_t sysmaxconn = 0 , sysmaxconn_hard = 0 ;
103108
104109/* preloaded to STATEPATH in main, can be overridden via upsd.conf */
105110char * statepath = NULL ;
@@ -1258,12 +1263,68 @@ static void update_sysmaxconn(void)
12581263 char * s = getenv ("NUT_SYSMAXCONN_LIMIT" );
12591264
12601265#ifndef WIN32
1266+ # ifdef HAVE_SYS_RESOURCE_H
1267+ struct rlimit limit ;
1268+ # endif /* HAVE_SYS_RESOURCE_H */
1269+
12611270 /* default to system limit (may be overridden in upsd.conf) */
12621271 /* FIXME: Check for overflows (and int size of nfds_t vs. long) - see get_max_pid_t() for example */
12631272 l = sysconf (_SC_OPEN_MAX );
1273+
1274+ # ifdef HAVE_SYS_RESOURCE_H
1275+ /* Try to use getrlimit/setrlimit to detect and possibly increase the limit */
1276+ if (getrlimit (RLIMIT_NOFILE , & limit ) == 0 ) {
1277+ upsdebugx (2 , "%s: System file descriptor limits: soft=%ld, hard=%ld" ,
1278+ __func__ , (long )limit .rlim_cur , (long )limit .rlim_max );
1279+
1280+ /* If we requested a specific MAXCONN, try to ensure we have enough FDs */
1281+ if (maxconn > 0 ) {
1282+ rlim_t needed = (rlim_t )maxconn + RESERVE_FD_COUNT_UPSD ;
1283+
1284+ if (limit .rlim_cur < needed ) {
1285+ if (needed <= limit .rlim_max ) {
1286+ upslogx (LOG_INFO , "Increasing file descriptor limit to %ld" , (long )needed );
1287+
1288+ limit .rlim_cur = needed ;
1289+ if (setrlimit (RLIMIT_NOFILE , & limit ) != 0 ) {
1290+ upslog_with_errno (LOG_WARNING , "setrlimit(RLIMIT_NOFILE) to %ld failed" , (long )needed );
1291+ }
1292+ } else {
1293+ upslogx (LOG_WARNING , "WARNING: Requested MAXCONN %" PRIdMAX
1294+ " requires %ld FDs overall "
1295+ "(with %ld reserved for non-connection purposes), "
1296+ "but system hard limit is %ld" ,
1297+ (intmax_t )maxconn , (long )needed ,
1298+ (long )RESERVE_FD_COUNT_UPSD ,
1299+ (long )limit .rlim_max );
1300+
1301+ /* We might still try to bump to hard limit */
1302+ if (limit .rlim_cur < limit .rlim_max ) {
1303+ limit .rlim_cur = limit .rlim_max ;
1304+ setrlimit (RLIMIT_NOFILE , & limit );
1305+ }
1306+ }
1307+ }
1308+ }
1309+
1310+ /* Refresh limit after possible update */
1311+ getrlimit (RLIMIT_NOFILE , & limit );
1312+ sysmaxconn_hard = (long )limit .rlim_cur ;
1313+ } else {
1314+ # endif /* HAVE_SYS_RESOURCE_H */
1315+ /* Fallback to sysconf if getrlimit fails or is absent */
1316+ /* TOTHINK: Any other reasonable fallback hard limit? */
1317+ sysmaxconn_hard = (nfds_t )l ;
1318+ # ifdef HAVE_SYS_RESOURCE_H
1319+ }
1320+ # endif /* HAVE_SYS_RESOURCE_H */
1321+
12641322#else /* WIN32 */
12651323 /* hard-coded 64 (from ddk/wdm.h or winnt.h) */
12661324 l = (long )MAXIMUM_WAIT_OBJECTS ;
1325+
1326+ /* No known limit, do not check */
1327+ sysmaxconn_hard = 0 ;
12671328#endif /* WIN32 */
12681329
12691330 if (l < 1 ) {
@@ -1276,11 +1337,35 @@ static void update_sysmaxconn(void)
12761337 l );
12771338 }
12781339
1340+ if (sysmaxconn_hard > 0 && sysmaxconn_hard < RESERVE_FD_COUNT_UPSD + 10 ) {
1341+ fatalx (EXIT_FAILURE ,
1342+ "System reported an absurd value %ld (below the %ld reservation for\n"
1343+ "non-connection purposes and some 10 for driver/client/... connections)\n"
1344+ "as its hard maximum number of connections.\n"
1345+ "The server won't start until this problem is resolved.\n" ,
1346+ (long )sysmaxconn_hard , (long )RESERVE_FD_COUNT_UPSD );
1347+ }
1348+
12791349 /* Note this historically also serves as
12801350 * the initial/default MAXCONN setting
12811351 * (so site/platform-dependent).
12821352 */
1283- sysmaxconn = (nfds_t )l ;
1353+ if (sysmaxconn_hard > 0 ) {
1354+ if (l < RESERVE_FD_COUNT_UPSD + 10 ) {
1355+ fatalx (EXIT_FAILURE ,
1356+ "System reported an absurd value %ld (below the %ld reservation for\n"
1357+ "non-connection purposes and some 10 for driver/client/... connections)\n"
1358+ "as its sysconf maximum number of connections.\n"
1359+ "The server won't start until this problem is resolved.\n" ,
1360+ l , (long )RESERVE_FD_COUNT_UPSD );
1361+ }
1362+
1363+ sysmaxconn = (nfds_t )(l - RESERVE_FD_COUNT_UPSD );
1364+ } else {
1365+ /* No known limit on open FDs/handles, whether connections or files or other streams */
1366+ sysmaxconn = (nfds_t )l ;
1367+ }
1368+
12841369 if (maxconn < 1 ) {
12851370 upsdebugx (1 , "%s: defaulting maxconn to sysmaxconn: %ld" ,
12861371 __func__ , l );
@@ -1308,6 +1393,15 @@ static void poll_reload(void)
13081393 /* Not likely this would change, but refresh just in case */
13091394 update_sysmaxconn ();
13101395
1396+ if (sysmaxconn_hard > 0 && (maxconn > sysmaxconn_hard - RESERVE_FD_COUNT_UPSD )) {
1397+ fatalx (EXIT_FAILURE ,
1398+ "You requested %" PRIdMAX " as maximum number of connections,\n"
1399+ "but the system only allows %" PRIdMAX " and we need %d for ourselves.\n"
1400+ "The server won't start until this problem is resolved\n"
1401+ "(reduce MAXCONN or increase ulimit or similar settings).\n" ,
1402+ (intmax_t )maxconn , (intmax_t )sysmaxconn , RESERVE_FD_COUNT_UPSD );
1403+ }
1404+
13111405 if ((intmax_t )sysmaxconn < (intmax_t )maxconn ) {
13121406 upslogx (LOG_WARNING ,
13131407 "Your system limits the maximum number of connections to %" PRIdMAX "\n"
@@ -1593,6 +1687,7 @@ static void mainloop(void)
15931687 if (reload_flag ) {
15941688 upsnotify (NOTIFY_STATE_RELOADING , NULL );
15951689 conf_reload ();
1690+ /* Among other things, re-detect sysmaxconn after loading config, because MAXCONN might have changed */
15961691 poll_reload ();
15971692 reload_flag = 0 ;
15981693 upsnotify (NOTIFY_STATE_READY , NULL );
@@ -2419,6 +2514,22 @@ void check_perms(const char *fn)
24192514#endif /* WIN32 */
24202515}
24212516
2517+ void close_oldest_client (void )
2518+ {
2519+ nut_ctype_t * client , * oldest = NULL ;
2520+
2521+ for (client = firstclient ; client ; client = client -> next ) {
2522+ if (!oldest || client -> last_heard < oldest -> last_heard ) {
2523+ oldest = client ;
2524+ }
2525+ }
2526+
2527+ if (oldest ) {
2528+ upslogx (LOG_INFO , "Closing oldest client connection from %s to free up file descriptors" , oldest -> addr );
2529+ client_disconnect (oldest );
2530+ }
2531+ }
2532+
24222533int main (int argc , char * * argv )
24232534{
24242535 int opt_ret = 0 , cmdret = 0 , foreground = -1 ;
@@ -2708,6 +2819,9 @@ int main(int argc, char **argv)
27082819 /* handle upsd.conf */
27092820 load_upsdconf (0 ); /* 0 = initial */
27102821
2822+ /* Re-detect sysmaxconn after loading config, because MAXCONN might have changed */
2823+ update_sysmaxconn ();
2824+
27112825 /* CLI debug level can not be smaller than debug_min specified
27122826 * in upsd.conf. Note that non-zero debug_min does not impact
27132827 * foreground running mode.
0 commit comments