Monday, October 8, 2012

Finding Running Processes by Executable Name using Linux


Finding Running Processes by Executable Name using Linux

 

Overview:

 

This post will illustrate a way for an application process to check if any particular known application process is currently running in a Linux operating environment.  Variations of the method could be used to find all running processes or all currently running executables.

 

This came up since I was completing the move of my Exploratory Project (EP) from running under Windows to running under Linux.  Recent posts have provided some of the Linux interfaces that I needed to replace Win32 interfaces.  These allowed me to almost complete the Linux version of the EP application. 

 

The EP applications communicate with each other via named pipes – Microsoft Pipes for Windows – which will become fifo named pipes for Linux.  In finishing up I came to a Win32 interface that I used to check if a remote application process in the configuration of applications is running prior to attempting to connect to the application. 

 

In my Linux named pipe development I found that I could have the applications connect (that is, open their common named pipe and communicate) without first determining whether the other (that is, remote) application of the a configuration pair was running – just continue to try until it was running and had opened its end of the pipe.  However, since I want to be able to have essentially the same code usable by Windows and Linux – other than switching the routines of an operating system/executive layer – I wanted to be able to detect if a specific remote application was running prior to attempting to connect to it.

 

Finding an appropriate Linux function proved to be a little more difficult than replacing the Win32 routines to create threads, enter a critical region, do disk file opens, reads, writes, etc or obtain the current time.  That is, my internet searches for how to accomplish this just kept returning how to do it from the console terminal rather than via an application.

 

There was even one set of blog postings in response to an individual's question on the subject that pretty much ridiculed him for wanting to while telling him it wasn't possible.

 

However, by using various hints that I came across and Linux man pages that some of them referenced in one way or another, I was able to work out an approach that was successful.  The resulting function will be presented below.

 

Design:

 

This particular function (Is_Running) is implemented in the way that it is because the EP has a configuration file that specified the applications that can make up the complete configuration.  One item of information in each application's record in the file is the directory/folder path of the application executable including the application's name.  This information can be used for two purposes; 1) for a running application to check whether the remote application of a potential application pair is running, and 2) with later versions of the Windows interface, to launch the applications that aren't running.  [So much for the question raiser's hecklers who couldn't figure out how such functions could be useful.]

 

In any case the Is_Running function is provided with an executable path that, in the EP case would come from the configuration file, that the function checks against the running processes to determine if it is one of them.  If so, it stops checking and returns true.  If not it returns false after checking all the running processes.  This, of course, allows a window where an application could be either launched just the check was completed or aborted just afterwards but which isn't important to the way I use it.

 

Thus, for instance, in the tests of the function, the test2 application makes the call

  if Exec_Itf.Is_Running( Path => "/home/clayton/Source/Test/test1.exe" ) then

    Console.Write( "Other app, test1, is running" );

  else

    Console.Write( "test1 is NOT running" );

  end if;

to check whether the test1 application is running.  If test1 has been started before test2 than the "test1, is running" appears on the terminal.  Otherwise, the "test1 is NOT running" text appears.

 

Implementation:

 

The Is_Running function was placed in the exec_itf Ada files (spec and body) as part of the operating system interface functions. 

 

It uses the following Linux interfaces:

 

1) opendir to open the "/proc" process directory,

2) readdir to return each entry in succession,

3) readlink to get the executable path name associated with the process, if any.

 

The /proc directory is, of course, a special use directory that is part of the Linux design and used to store information about the running processes.  The readdir returns a different directory entry each time it is called after the open until a null pointer is returned.

 

I found out that readlink uses various lookup name suffixes to return different kinds of information.  With a /exe suffix the executable path is returned (if the process has an executable).  That is, the process name is passed as /proc/pid/exe where pid is the directory entity process identifier (that is, name) of the structure pointed to by the directory entry pointer returned by readdir.

 

So, voila, just compare the returned executable path name (when its length matches that of the path being searched and hence is greater than 0 indicating that there is such a path) to the path supplied in the call to Is_Running.  If a match is found, the process is running.

 
is_running declaration:

  function Is_Running
  ( Path : String
    --| Full path of executable
  ) return Boolean;
  --| Return True if executable is currently running
  -- ++
  --| Overview:
  --|   Determine if the executable named by the Path is currently running.
  --
  --| Notes:
  --|   o It is expected that Path will be the full path including the
  --|     trailing ".exe" of the executable.
  --|   o The function iterates through the /proc directory to obtain the
  --|     process identifiers of the running processes and, for each one
  --|     for which ReadLink returns a value for an executable path, it
  --|     compares that value with the supplied Path.  If it matches, True
  --|     is returned.
  -- --

 

is_running code:

 
  type Dir_Entity_Struct_Type
  --| Linux note:
  --|  The only fields in the dirent structure that are mandated by POSIX.1 are:
  --|  o d_name[], of unspecified size, with at most NAME_MAX characters
  --|              preceding the terminating null byte; and
  --|  o (as an XSI extension) d_ino.
  --|  The other fields are unstandardized, and not present on all systems.
  is record
    d_ino    : Machine.Unsigned_Longword; -- File system i-node number
    -- The file serial number, which distinguishes this file from all other
    -- files on the same device.
    d_off    : Machine.Unsigned_Longword; -- i.e., of type off_t
    -- File offset, measured in bytes from the beginning of a file or device.
    -- off_t is normally defined as a signed, 32-bit integer. In the
    -- programming environment which enables large files, off_t is defined
    -- to be a signed, 64-bit integer.
    d_reclen : Machine.Unsigned_Word;
    d_type   : Machine.Unsigned_Byte;
    d_name   : String(1..256); -- null terminated
  end record;
  type Dir_Entity_Ptr_Type is access Dir_Entity_Struct_Type;
  function OpenDir
  ( File_Name : String
    --| Name of directory; null terminated
  ) return System.Address;
  --| Pointer to opened directory
  pragma Import (C, OpenDir, "__gnat_opendir");
  function ReadDir
  ( Directory : System.Address
    --| Pointer to opened directory
  ) return Dir_Entity_Ptr_Type;
  --| Return pointer to directory entity
  pragma Import( C, ReadDir, "readdir" );
  function ReadLink
  ( Proc   : System.Address;
    --| Pointer to null terminated string
    Buffer : System.Address;
    --| Pointer to buffer for path
    Length : Integer
    --| Length of buffer
  ) return Integer;
  --| Return number of characters output to Buffer
  pragma Import( C, ReadLink, "readlink" );
  function Is_Running
  ( Path : String
    --| Full path of executable
  ) return Boolean is
    Dir_Entity
    --| Pointer to process directory entity
    : Dir_Entity_Ptr_Type := null;
    Dir_Proc
    --| Pointer to process directory
    : System.Address := System.Null_Address;
    Lookup_Name
    --| Process id for /proc lookup
    : String(1..50);
    Name_Len
    --| Number of characters in name
    : Integer;
    Proc_Directory
    --| Null-terminated name of directory containing running processes
    : String(1..10) := ( others => ASCII.NUL );
    Running_Process
    --| Path of running process
    : String(1..256);
    use type String_Tools.Comparison_Type;
    use type System.Address;
  begin -- Is_Running
    --| Logic_Step:
    --|   Open the /proc directory.
    Proc_Directory(1..6) := "/proc/"; -- nul terminated
    Dir_Proc := OpenDir( File_Name => Proc_Directory );
    if Dir_Proc = System.Null_Address then
      Console.Write_Error( "Couldn't open the /proc/ directory" );
      return False;
    end if;
    --| Logic_Step:
    --|   Search through the processes looking for the one with the
    --|   specified path and executable name.
    loop
      -- Read next item
      Dir_Entity := ReadDir(Dir_Proc);
      exit when Dir_Entity = null;
      -- Form lookup name
      Name_Len := 0;
      for I in 1..Dir_Entity.d_name'length loop
        exit when Dir_Entity.d_name(I) = ASCII.NUL; -- trailing NUL
        Name_Len := I; -- set length of name to last non-NUL character
      end loop;
      if Name_Len > 0 then
        Lookup_Name(1..6) := "/proc/";
        Lookup_Name(7..6+Name_Len) := Dir_Entity.d_name(1..Name_Len);
        Lookup_Name(7+Name_Len..10+Name_Len) := "/exe";
        Lookup_Name(11+Name_Len) := ASCII.NUL; -- trailing NUL
        -- Get path
        Name_Len := ReadLink( Lookup_Name'address,
                              Running_Process'address,
                              Running_Process'length );
        -- Compare path of running executable with that input
        if Name_Len = Path'length and then
           String_Tools.Blind_Compare
           ( Left  => Running_Process(1..Name_Len),
             Right => Path ) = String_Tools.Equal
        then
          return True; -- process is running
        end if;
      end if; -- Name_Len > 0
    end loop;
    return False; -- Path not found in running processes
  end Is_Running;

No comments: