GlowWorm FW Lite
About Download Contact Support History Screenshots Developer
A basic operation upon which GlowWorm FW Lite depends is that of "mapping" a unix process id (pid) to the associated executable path. You can see this in action in a common utility like ps. However, ps also shows considerably more information than just the path to the executable. After several hours of digging around, the secret behind the magic which is ps was unlocked. The following code shows how to accomplish this pid-to-path feat.

pidToPath.c

 
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>

void
printAndExit (char *msg, int ret)
{
  printf("%s\n", msg);
  exit(ret);
}

int
main (int argc, char *argv[])
{
  size_t size;
  int mib[3], argmax, pid;
  char *procargs, *cp, *ep;
  
  if (argc != 2 || 0 > (pid = atoi(argv[1])))
    printAndExit("<Failed-00>", EXIT_FAILURE);
  
  mib[0] = CTL_KERN;
  mib[1] = KERN_ARGMAX;
  
  size = sizeof(argmax);
  
  // get the max size for arguments; last I checked this 
  // was 262,144 bytes.
  if (-1 == sysctl(mib, 2, &argmax, &size, NULL, 0))
    printAndExit("<Failed-01>", EXIT_FAILURE);
  
  // allocate memory to hold the target process' arguments
  if (NULL == (procargs = (char *)malloc(argmax)))
    printAndExit("<Failed-02>", EXIT_FAILURE);
  
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROCARGS2;
  mib[2] = pid;
  
  size = (size_t)argmax;
  
  // obtain the arguments for the target process. this will 
  // only work for processes that belong to the current uid,
  // so if you want it to work universally, you need to run
  // as root.
  if (-1 == sysctl(mib, 3, procargs, &size, NULL, 0)) {
    free(procargs);
    printAndExit("<Failed-03>", EXIT_FAILURE);
  }
  
  cp = procargs + sizeof(int);
  ep = &procargs[size];
  
  // advance past the saved exec path
  while (*cp != '\0' && cp++ < ep)
    ;
  
  // we should not already be at the end of the arguments
  if (cp == ep) {
    free(procargs);
    printAndExit("<Failed-04>", EXIT_FAILURE);
  }
  
  // advance until we hit the first argument
  while (*cp == '\0' && cp++ < ep)
    ;
  
  printAndExit(cp, EXIT_SUCCESS);
  
  free(procargs);
  
  return 0;
}

For the purposes of GlowWorm FW Lite, however, it is not feasible to re-execute a program such as this, for every connection event. For obvious reasons, performance is a big issue. Many possibilities were considered before deciding to write a daemon listening on a unix domain datagram socket. When GlowWorm FW Lite launches, it also launches this daemon (pid2pathd), which starts up its listener, reads \0-terminated process ids, and writes back \0-terminated executable paths. This method has proven acceptably efficient. The code for the daemon follows.

pidToPathDaemon.h

 
#ifndef __PID_TO_PATH_DAEMON_H__
#define __PID_TO_PATH_DAEMON_H__

#define PID2PATH_DG_FILE "/tmp/p2p.dg"
#define PID2PATH_DG_BUFF 4000


#endif /* __PID_TO_PATH_DAEMON_H__ */


pidToPathDaemon.c

 
#include "pidToPathDaemon.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

static char *procargs;
static int argmax;
static boolean_t asig = FALSE;

void
catch_signal (int s)
{
  if (FALSE == asig) {
    asig = TRUE;
    unlink(PID2PATH_DG_FILE);
  }
  
  exit(EXIT_SUCCESS);
}

int
pid2path (int pid, char *fpath, int pleng)
{
  size_t size;
  int mib[3];
  char *cp, *ep;
  
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROCARGS2;
  mib[2] = pid;
  
  size = (size_t)argmax;
  
  // obtain the arguments for the target process. this will 
  // only work for processes that belong to the current uid,
  // so if you want it to work universally, you need to run
  // as root.
  if (-1 == sysctl(mib, 3, procargs, &size, NULL, 0)) {
    printf("%s().. failed-03, %d\n", __PRETTY_FUNCTION__, errno);
    return -1;
  }
  
  procargs[size] = 0;
  
  cp = procargs + sizeof(int);
  ep = &procargs[size];
  
  // advance past the saved exec path
  while (*cp != '\0' && cp++ < ep)
    ;
  
  // we should not already be at the end of the arguments
  if (cp == ep) {
    printf("%s().. failed-04\n", __PRETTY_FUNCTION__);
    return -1;
  }
  
  // advance until we hit the first argument
  while (*cp == '\0' && cp++ < ep)
    ;
  
  memcpy(fpath, cp, strlen(cp));
  
  return strlen(cp);
}

int
main (int argc, char *argv[])
{
  socklen_t alen;
  int ssock, pid, pleng;
  int rleng, wleng;
  struct sockaddr_un saddr, caddr;
  char rmesg[PID2PATH_DG_BUFF];     // message buffer for receiving
  char smesg[PID2PATH_DG_BUFF];     // message buffer for sending
  char fpath[PID2PATH_DG_BUFF];     // pid2path file path buffer
  
  signal(SIGINT, catch_signal);
  signal(SIGTERM, catch_signal);
  
  {
    size_t size;
    int mib[3];
    
    mib[0] = CTL_KERN;
    mib[1] = KERN_ARGMAX;
    
    size = sizeof(argmax);
    
    // get the max size for arguments; last I checked this 
    // was 262,144 bytes.
    if (-1 == sysctl(mib, 2, &argmax, &size, NULL, 0)) {
      printf("%s().. failed-01, %d\n", __PRETTY_FUNCTION__, errno);
      return EXIT_FAILURE;
    }
    
    // allocate memory to hold the target process' arguments
    if (NULL == (procargs = (char *)malloc(argmax))) {
      printf("%s().. failed-02, %d\n", __PRETTY_FUNCTION__, errno);
      return EXIT_FAILURE;
    }
  }
  
  // remove the unix domain target file, if it already 
  // exists. since we're not taking this file name as an
  // argument, then it is safe (imo) to blindly unlink.
  // and we should not check for an error return value,
  // because this will return an error if the file does
  // not already exist.
  //
  unlink(PID2PATH_DG_FILE);
  
  // create the unix datagram socket. this is not a stream
  // socket, so we shouldn't have to worry about being 
  // multi-threaded or anything like that. very nifty.
  //
  if (0 >= (ssock = socket(AF_LOCAL, SOCK_DGRAM, 0))) {
    printf("%s().. failed to socket\n", __PRETTY_FUNCTION__);
    return EXIT_FAILURE;
  }
  
  // clear out the address structure
  //
  bzero(&saddr, sizeof(saddr));
  
  // something
  //
  saddr.sun_family = AF_LOCAL;
  
  // copy in the file path that we're going to use as the
  // endpoint for the unix datagram socket
  //
  strcpy(saddr.sun_path, PID2PATH_DG_FILE);
  
  // bind the socket to the file we just specified
  //
  if (0 != bind(ssock, (struct sockaddr *)&saddr, sizeof(saddr))) {
    printf("%s().. failed to bind()\n", __PRETTY_FUNCTION__);
    return EXIT_FAILURE;
  }
  
  // set RWX for "other" so that non-root processes can
  // send requests to us via the unix domain socket.
  //
  if (0 != chmod(PID2PATH_DG_FILE, S_IRWXU | S_IRWXG | S_IRWXO)) {
    printf("%s().. failed to chmod()\n", __PRETTY_FUNCTION__);
    return EXIT_FAILURE;
  }
  
  while (1)
  {
    bzero(rmesg, PID2PATH_DG_BUFF);
    bzero(smesg, PID2PATH_DG_BUFF);
    bzero(fpath, PID2PATH_DG_BUFF);
    
    alen = sizeof(caddr);
    
    if (-1 == (rleng = recvfrom(ssock, rmesg, PID2PATH_DG_BUFF, 0, 
                                (struct sockaddr *)&caddr, &alen))) {
      printf("%s().. recvfrom() returned -1. %d\n", 
             __PRETTY_FUNCTION__, errno);
      break;
    }
    else if (alen != sizeof(caddr)) {
      printf("%s().. recvfrom() did not read a sender address\n",
             __PRETTY_FUNCTION__);
      break;
    }
    
    printf("%s().. read %d bytes ('%s') from %s\n", 
           __PRETTY_FUNCTION__, rleng, rmesg, caddr.sun_path);
    
    if (0 != rleng && 0 < (pid = atoi(rmesg)))
      if (PID2PATH_DG_BUFF >= (pleng = pid2path(pid, (char *)&fpath, 
                                  PID2PATH_DG_BUFF)) && pleng > 0)
        memcpy(&smesg, &fpath, pleng);
    
    printf("%s().. %d -> '%s'\n", 
           __PRETTY_FUNCTION__, pid, fpath);
    
    if (-1 == (wleng = sendto(ssock, smesg, pleng, 0, 
                              (struct sockaddr *)&caddr, alen)))
      printf("%s().. sendto() return -1. that sucks. %d\n", 
             __PRETTY_FUNCTION__, errno);
  }
  
  free(procargs);
  
  unlink(PID2PATH_DG_FILE);
  
  return 0;
}

Copyright 2004-2006 Symphonic Systems, Inc. <cjones@glowworm.us>. All Rights Reserved.