/* gen-acct
 * Generate fake acct(5) data that looks vaguely plausible to the
 * running system by using the system's binary names. This application
 * daemonises, writes its pid into a lock file and populates the log file
 * in the background.
 *  -- Andrew Bower <andrew@bower.uk>  Thu, 19 Jun 2025 07:27:24 +0100
 */

#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/acct.h>

struct evt_src {
  char comm[ACCT_COMM];
  double cumulative_probability;
};

const uid_t uids[] = { 0, 1000, 1001 };
constexpr int num_uids = sizeof uids / sizeof *uids;

struct evt_src *sources = NULL;
int num_sources = 0;

static int file_filter(const struct dirent *d) {
  return d->d_type == DT_REG || d->d_type == DT_UNKNOWN;
}

int populate_sources(void) {
  struct dirent **files;
  int i;
  double p = 0.1;
  double pp = 0.0;
  int rc = 0;

  num_sources = scandir("/usr/bin", &files, file_filter, alphasort);
  if (num_sources == -1) {
    num_sources = 0;
    rc = errno;
    goto fail;
  }

  sources = malloc(num_sources * sizeof *sources);
  if (sources == NULL) {
    rc = errno;
    goto fail1;
  }

  for (i = 0; i < num_sources; i++) {
    struct evt_src *s = sources + i;

    strncpy(s->comm, files[i]->d_name, sizeof s->comm);
    s->cumulative_probability = pp += (1 - pp) * p;
  }

fail1:
  free(files);
fail:
  return rc;
}

void free_sources(void) {
  sources = NULL;
  num_sources = 0;
}

int main(int argc, char *argv[]) {
  const char *lockpath;
  const char *fnam;
  FILE *file;
  size_t sz;
  pid_t pid = -1;

  if (argc != 3) {
    fprintf(stderr, "usage: %s <lock-file> <acct-file>\n", argv[0]);
    return EXIT_FAILURE;
  }
  lockpath = argv[1];
  fnam = argv[2];

  if (populate_sources() != 0) {
    perror("populating sources");
    goto finish;
  }

  /* Check access first because we want to fail on missing file */
  if (access(fnam, W_OK) != 0) {
    perror("access");
    goto finish;
  }

  /* Spawn daemon */
  pid = fork();
  if (pid == -1) {
    perror("fork");
    goto finish;
  }

  /* Parent actions - write PID to lock file */
  if (pid != 0) {
    FILE *lock = fopen(lockpath, "w");
    if (lock == NULL) {
      perror("fopen(lock)");
      goto finish;
    }
    fprintf(lock, "%d\n", pid);
    fflush(lock);
    goto finish;
  }

  /* Child actions - open log file */
  file = fopen(fnam, "a");
  if (file == NULL) {
    perror("fopen(log)");
    goto child_fail;
  }

  srand48(time(nullptr));

  while (true) {
    struct acct_v3 r;
    struct evt_src *s;
    double p = drand48();
    time_t t = time(nullptr);

    for (s = sources; s - sources < num_sources && p > s->cumulative_probability; s++);
    if (s - sources < num_sources) {
      memset(&r, '\0', sizeof r);
      r.ac_version = 3;
      r.ac_etime = sqrt(3600.0 * drand48());
      r.ac_btime = t - ceil(r.ac_etime);
      r.ac_uid = uids[(int) (num_uids * drand48())];
      r.ac_gid = r.ac_uid;
      memcpy(&r.ac_comm, s->comm, sizeof r.ac_comm);
      sz = fwrite(&r, sizeof r, 1, file);
      if (sz != 1) {
	perror("fwrite");
        fclose(file);
	goto child_fail;
      }
    }
    usleep(20000);
  }

child_fail:
  unlink(lockpath);

finish:
  free(sources);
  return pid > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
