#include <stdio.h>      // fprintf, perror, snprintf
#include <stdlib.h>     // exit
#include <string.h>     // strerror
#include <errno.h>      // errno
#include <unistd.h>     // fork, setsid, chdir, dup2, ftruncate, write
#include <fcntl.h>      // open, O_*
#include <sys/file.h>   // flock
#include <sys/stat.h>   // umask

#include "sysmond.h"

void daemonize( void )
{
   // Open (or create) the pid file and lock it.  The lock is the
   // "already running" test: it is released automatically when the
   // daemon exits for any reason, so a stale pid file is harmless.
   int pidfd = open( sysmond_args.pidFile,
                     O_RDWR | O_CREAT | O_CLOEXEC | O_NOFOLLOW, 0644 );
   if ( pidfd < 0 )
   {
      fprintf( stderr, "open(\"%s\"): %s\n", sysmond_args.pidFile, strerror( errno ) );
      exit( EXIT_FAILURE );
   }

   if ( flock( pidfd, LOCK_EX | LOCK_NB ) < 0 )
   {
      if ( errno == EWOULDBLOCK )
         fprintf( stderr, "sysmond is already running.\n" );
      else
         perror( "flock()" );
      exit( EXIT_FAILURE );
   }

   pid_t pid = fork();
   if ( pid < 0 )
   {
      perror( "fork()" );
      exit( EXIT_FAILURE );
   }

   if ( pid > 0 )
   {
      // Original process: record the daemon's pid, then exit.
      // The child shares pidfd, so it keeps holding the lock.
      char buf[ 16 ];
      int  len = snprintf( buf, sizeof( buf ), "%d\n", pid );
      if ( ftruncate( pidfd, 0 ) < 0 || write( pidfd, buf, len ) != len )
      {
         perror( "writing pid file" );
         exit( EXIT_FAILURE );
      }
      exit( EXIT_SUCCESS );
   }

   // Forked process: become a proper daemon
   setsid();                    // new session, no controlling terminal
   umask( 022 );                // predictable permissions for new files

   if ( chdir( "/" ) < 0 )      // don't keep any directory busy
      exit( EXIT_FAILURE );

   // Point stdin, stdout and stderr at /dev/null instead of closing
   // them, so later open() calls can't reuse descriptors 0-2.
   int nullfd = open( "/dev/null", O_RDWR );
   if ( nullfd >= 0 )
   {
      dup2( nullfd, STDIN_FILENO  );
      dup2( nullfd, STDOUT_FILENO );
      dup2( nullfd, STDERR_FILENO );
      if ( nullfd > STDERR_FILENO ) close( nullfd );
   }

   // pidfd stays open: closing it would release the lock.
}

========

flock() replaces the /proc search. A flock() lock belongs to the open file,
which the parent and child share after fork(). So the child keeps holding the
lock after the parent exits, and the kernel releases it whenever the daemon
dies, even from kill -9.

The parent still writes the PID before exiting, which is what Type=forking with
PIDFile= expects.

O_NOFOLLOW keeps it from following a symlink planted at the PID file’s path.

----------

  // Block SIGTERM and SIGUSR1 *before* creating the UDP thread, so the
  // thread inherits the mask and only sigtimedwait() below sees them.
  sigset_t sigs;
  sigemptyset( &sigs );
  sigaddset( &sigs, SIGTERM );
  sigaddset( &sigs, SIGUSR1 );
  pthread_sigmask( SIG_BLOCK, &sigs, NULL );

  pthread_t thread_handle;
  if ( pthread_create( &thread_handle, NULL, udp_thread, NULL ) != 0 )
  {
     dbg( ": cannot create UDP thread\n" );
     exit( EXIT_FAILURE );
  }

  const struct timespec wait = { .tv_sec = sysmond_args.scanTime };

  while ( true )
  {
      int inactive_string = ( data_available == 0 ) ? 1 : 0;
      get_data( data[ inactive_string ] );

      pthread_mutex_lock( &mutex );
      data_available = inactive_string;
      pthread_mutex_unlock( &mutex );

      // Sleep until the next scan or until a signal arrives
      int sig = sigtimedwait( &sigs, NULL, &wait );

      if ( sig == SIGTERM )
      {
         unlink( sysmond_args.pidFile );
         exit( EXIT_SUCCESS );
      }
      if ( sig == SIGUSR1 )
         dump_data();         // writes data[ data_available ] to dbgFile
  }

=========

signal.c then shrinks to dump_data(), and install_sighandler() and
signalHandler() go away. dump_data() doesn’t need the mutex, since it runs in
the same thread that writes the buffers.

SIGTERM is acted on immediately. A SIGUSR1 makes the next scan happen early,
which does no harm.

This is also a better demonstration for ABOUT’s “Handling signals” item: it is
the pattern recommended for multithreaded programs.
---------

3. The debug file in /tmp is a root symlink risk

sysmond runs as root and opens /tmp/sysmond.dbg for appending. Any local user can create /tmp/sysmond.dbg first as a symlink to, say, /etc/shadow, and the next SIGUSR1 or dbg() call appends to that file instead. Whether this works depends on fs.protected_symlinks. systemd turns it on, but a SysV LFS system may leave it at the kernel default, which is off.

The simplest fix is to change the default to /run/sysmond.dbg, since /run is writable only by root. To protect user-chosen paths too, open the file with O_NOFOLLOW through one helper in utils.c, used by both dbg() and dump_data():
c

FILE* open_dbg( void )
{
   int fd = open( sysmond_args.dbgFile,
                  O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0644 );
   return ( fd < 0 ) ? NULL : fdopen( fd, "a" );
}

-----------

4. sysmond.c: other changes

Delete is_running() and its prototype in sysmond.h; the lock replaces them.

Check geteuid() rather than getuid(). The effective UID is what decides permissions.

Send error messages to stderr. The “must be root” message on line 76 uses printf().

Line 96: use optind = 0, as discussed earlier.

pthread_create() return value is now checked, in the code above.
--------

5. utils.c

print_time() uses ctime(), which returns a static buffer shared by all threads, and dbg() is called from the UDP thread. The strcat() onto that buffer also fits only because a ctime() string happens to leave exactly one spare byte. A self-contained version:
c

void dbg( const char* txt )
{
   FILE* f = open_dbg();
   if ( f == NULL ) return;

   char      ts[ 32 ];
   time_t    now = time( NULL );
   struct tm tm;
   strftime( ts, sizeof( ts ), "%a %b %e %H:%M:%S %Y", localtime_r( &now, &tm ) );
   fprintf( f, "%s %s", ts, txt );
   fclose( f );
}

#print_time() and the time_string global can then be deleted.
---------------

6. Headers

    sysmond.h:
        Its comment says set_affinity is in sysmond.c, but it’s in 
        utils.c, and it’s also declared in utils.h. Keep one declaration.
        
        Remove is_running, install_sighandler and signalHandler, and add dump_data.
    
    daemonize.c: about half of the old #include lines weren’t used; 
    the version above lists only the ones it needs.

---------

7. Priority and CPU affinity

These are still in daemonize(). They fit better in main(), just before
pthread_create(). To quiet the unused-result warning, check nice() properly: c

errno = 0;
if ( nice( -1 ) == -1 && errno != 0 ) dbg( ": nice() failed\n" );

The hard-coded CPUs 0 and 1 can wait until we get to udp.c, since it pins the other thread.

Once these are in, the daemon steps in ABOUT become:

- Lock the PID file, and exit if another copy holds the lock.
- Fork. The parent writes the PID file and exits.
- The child starts a new session, sets its umask, changes to /, 
  and redirects stdin, stdout and stderr to /dev/null.
- It blocks SIGTERM and SIGUSR1, raises its priority, and creates the UDP thread.
- It loops: scan, then wait in sigtimedwait() for the scan interval or a signal.

==============


sysmond.h: add these, and change the udp_thread comment:
c

#include <pthread.h>

extern pthread_mutex_t mutex;

extern int   udp_open  ( void );    // udp.c
extern void* udp_thread( void* );   // udp.c, arg is the socket
-----
sysmond.c: open the socket before daemonize(), fill data[ 0 ] before the thread starts (so an early request doesn’t get Thread 0), and pass the socket to the thread:
c

  // Create and bind the socket while errors can still reach the terminal
  int sock = udp_open();

  daemonize();

  // Have real data ready before the UDP thread can answer anything
  get_data( data[ 0 ] );

  ...
  if ( pthread_create( &thread_handle, NULL, udp_thread,
                       (void*) (intptr_t) sock ) != 0 )

That also needs #include <stdint.h> in sysmond.c. Delete the two strcpy( data[ ... ], "Thread ..." ) lines.

Delete udp.h, and in the Makefile change to udp.o: udp.c sysmond.h utils.h.
