3

This post answer almost my question: Linux API to list running processes?

But what is missing in the example code (C++/C - see below) is the argument of the process. For example the program called mypaint will only be listed as "python2.7" and not the full executable and arguments "python2.7 /usr/bin/mypaint".

From the man page of proc: /proc/[pid]/cmdline This holds the complete command line for the process, unless the process is a zombie. In the latter case, there is nothing in this file: that is, a read on this file will return 0 characters. The command-line arguments appear in this file as a set of strings separated by null bytes ('\0'), with a further null byte after the last string.

How can I modify this code to list also the argument of the process?

#ifndef __cplusplus
    #define _GNU_SOURCE
#endif

#include <unistd.h>
#include <dirent.h>
#include <sys/types.h> // for opendir(), readdir(), closedir()
#include <sys/stat.h> // for stat()

#ifdef __cplusplus
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdarg>
#else
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdarg.h>
#endif


#define PROC_DIRECTORY "/proc/"
#define CASE_SENSITIVE    1
#define CASE_INSENSITIVE  0
#define EXACT_MATCH       1
#define INEXACT_MATCH     0


int IsNumeric(const char* ccharptr_CharacterList)
{
    for ( ; *ccharptr_CharacterList; ccharptr_CharacterList++)
        if (*ccharptr_CharacterList < '0' || *ccharptr_CharacterList > '9')
            return 0; // false
    return 1; // true
}


int strcmp_Wrapper(const char *s1, const char *s2, int intCaseSensitive)
{
    if (intCaseSensitive)
        return !strcmp(s1, s2);
    else
        return !strcasecmp(s1, s2);
}

int strstr_Wrapper(const char* haystack, const char* needle, int intCaseSensitive)
{
    if (intCaseSensitive)
        return (int) strstr(haystack, needle);
    else
        return (int) strcasestr(haystack, needle);
}


#ifdef __cplusplus
pid_t GetPIDbyName(const char* cchrptr_ProcessName, int intCaseSensitiveness, int intExactMatch)
#else
pid_t GetPIDbyName_implements(const char* cchrptr_ProcessName, int intCaseSensitiveness, int intExactMatch)
#endif
{
    char chrarry_CommandLinePath[100]  ;
    char chrarry_NameOfProcess[300]  ;
    char* chrptr_StringToCompare = NULL ;
    pid_t pid_ProcessIdentifier = (pid_t) -1 ;
    struct dirent* de_DirEntity = NULL ;
    DIR* dir_proc = NULL ;

    int (*CompareFunction) (const char*, const char*, int) ;

    if (intExactMatch)
        CompareFunction = &strcmp_Wrapper;
    else
        CompareFunction = &strstr_Wrapper;


    dir_proc = opendir(PROC_DIRECTORY) ;
    if (dir_proc == NULL)
    {
        perror("Couldn't open the " PROC_DIRECTORY " directory") ;
        return (pid_t) -2 ;
    }

    // Loop while not NULL
    while ( (de_DirEntity = readdir(dir_proc)) )
    {
        if (de_DirEntity->d_type == DT_DIR)
        {
            if (IsNumeric(de_DirEntity->d_name))
            {
                strcpy(chrarry_CommandLinePath, PROC_DIRECTORY) ;
                strcat(chrarry_CommandLinePath, de_DirEntity->d_name) ;
                strcat(chrarry_CommandLinePath, "/cmdline") ;
                FILE* fd_CmdLineFile = fopen (chrarry_CommandLinePath, "rt") ;  // open the file for reading text
                if (fd_CmdLineFile)
                {
                    fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess) ; // read from /proc/<NR>/cmdline
                    fclose(fd_CmdLineFile);  // close the file prior to exiting the routine

                    if (strrchr(chrarry_NameOfProcess, '/'))
                        chrptr_StringToCompare = strrchr(chrarry_NameOfProcess, '/') +1 ;
                    else
                        chrptr_StringToCompare = chrarry_NameOfProcess ;

                    printf("Process name: %s\n", chrarry_NameOfProcess);
                    printf("Pure Process name: %s\n", chrptr_StringToCompare );

                    if ( CompareFunction(chrptr_StringToCompare, cchrptr_ProcessName, intCaseSensitiveness) )
                    {
                        pid_ProcessIdentifier = (pid_t) atoi(de_DirEntity->d_name) ;
                        closedir(dir_proc) ;
                        return pid_ProcessIdentifier ;
                    }
                }
            }
        }
    }
    closedir(dir_proc) ;
    return pid_ProcessIdentifier ;
}

#ifdef __cplusplus
    pid_t GetPIDbyName(const char* cchrptr_ProcessName)
    {
        return GetPIDbyName(cchrptr_ProcessName, CASE_INSENSITIVE, EXACT_MATCH) ;
    }
#else
    // C cannot overload functions - fixed
    pid_t GetPIDbyName_Wrapper(const char* cchrptr_ProcessName, ... )
    {
        int intTempArgument ;
        int intInputArguments[2] ;
        // intInputArguments[0] = 0 ;
        // intInputArguments[1] = 0 ;
        memset(intInputArguments, 0, sizeof(intInputArguments) ) ;
        int intInputIndex ;
        va_list argptr;

        va_start( argptr, cchrptr_ProcessName );
            for (intInputIndex = 0;  (intTempArgument = va_arg( argptr, int )) != 15; ++intInputIndex)
            {
                intInputArguments[intInputIndex] = intTempArgument ;
            }
        va_end( argptr );
        return GetPIDbyName_implements(cchrptr_ProcessName, intInputArguments[0], intInputArguments[1]);
    }

    #define GetPIDbyName(ProcessName,...) GetPIDbyName_Wrapper(ProcessName, ##__VA_ARGS__, (int) 15)

#endif

int main()
{
    pid_t pid = GetPIDbyName("bash") ; // If -1 = not found, if -2 = proc fs access error
    printf("PID %d\n", pid);
    return EXIT_SUCCESS ;
}

(chrarry_NameOfProcess = only executable how to show also arguments)?

Community
  • 1
  • 1
psc
  • 81
  • 2
  • 4
  • 3
    The man page you quoted says you need to read the arguments out of the file as null-terminated strings. What have you tried? Where exactly are you stuck with this? – Vicky Nov 23 '11 at 21:19
  • How to read the file as null-terminated strings? I tried many things, but I never get the arguments. `while(fgets(line, sizeof line, fd_CmdLineFile)) { printf("PROCESS NAME WITH ARGS %s\n", line }` – psc Nov 23 '11 at 21:48

3 Answers3

4

Assuming you really don't want to parse each argument separately but just want to print them as you are currently doing, an easy way is:

(1) Substitute fread for fscanf and read some arbitrarily large number of bytes. (Or find the file length first and read amount that if you want to do it professionally.) This will read everything including the null bytes into your buffer.

(2) Loop through the buffer replacing nulls with spaces.

(3) Put a null at the end of bytes read to terminate the string.

(4) Then print it.

So in your GetPIDbyName() function something like:

const int BUFFERSIZE = 300;
int bytesread;
char chrarry_NameOfProcess[BUFFERSIZE] ;

//...........

    FILE* fd_CmdLineFile = fopen (chrarry_CommandLinePath, "rb") ; 

    if (fd_CmdLineFile)
    {
        //fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess) ; 
        bytesread = fread(chrarry_NameOfProcess, 1, BUFFERSIZE, fd_CmdLineFile);

        for (int i = 0; i < bytesread; ++i)
            if (chrarry_NameOfProcess[i] == '\0')
                chrarry_NameOfProcess[i] = ' ';

        chrarry_NameOfProcess[bytesread] = '\0';

        fclose(fd_CmdLineFile);  

      //... the rest
     }
Duck
  • 26,924
  • 5
  • 64
  • 92
2

To read NULL-terminated strings, you're going to have to do a little bit of trickery.

If you call, as you do, fgets(buffer, size, fp) and try to print the result, you're not going to get what you want. The reason is that when you use printf() to try and print the result, you're going to get the first NULL-terminated string. Once you've printed that, you need to go to the next NULL-terminated string, because a NULL indicates the end of a string.

So, some code to do this:

int len = 0;
while((len = read(fd, buffer, sizeof(buffer))) > 0) {
    /* NOTE: not sure if this condition is correct: might be -1. */
    if(len == sizeof(buffer)) { /* arguments too long for buffer, use what exists or print error message. */ } 
    int i = 0;
    while(i < len) {
        char *s = buffer + i;
        printf("Argument: %s\n", s);
        i += strlen(s) + 1; /* +1 skips over NULL at end of string. */
    }
}

A bit of clarification: buffer + i is equivalent to &buffer[i]. It's basically treating buffer as a character array that contains multiple NULL-terminated strings chained together.

The reason that I'm using read() here instead of fgets() is that when using fgets() it's harder to figure out how many bytes were really read from the file. strlen() doesn't help, the only way I know is to check the location of the newline character, which assumes that a newline exists in the first place . . . it's ugly.

EDIT: or you could use ftell(), I suppose, but that's even uglier!

kestrel
  • 1,314
  • 10
  • 31
  • The `read(2)` condition should probably be `while((len = read(...)) > 0)` -- `0` when you've consumed everything, `-1` for an error condition. – sarnold Nov 24 '11 at 01:16
0

openproc could be used to list full command line arguments, just make sure to query for PROC_FILLCOM. You'll need procps library in order to compile the code (e.g. apt install libprocps-dev on Debian).

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <proc/readproc.h>

// compile with:
// gcc procls.c -lprocps -o procls
int main(){

  PROCTAB* proc = openproc(PROC_FILLCOM | PROC_EDITCMDLCVT);
  proc_t proc_info;

  memset(&proc_info, 0, sizeof(proc_info));
  while (readproc(proc, &proc_info) != NULL) {
    if(proc_info.cmdline != NULL){
      printf("%s\n", *proc_info.cmdline);
    }
  }
  closeproc(proc);
}
Tombart
  • 30,520
  • 16
  • 123
  • 136
  • Your example doesn't show command line arguments, which is the objective of the OP. Tested on `sleep 3600`: your code only prints `sleep`. – Ruslan Sep 21 '21 at 22:25
  • To print the full command line, one should iterate over the `proc_info.cmdline` array until null pointer is encountered. Each entry `proc_info.cmdline[n]` corresponds to the process' `argv[n]`. – Ruslan Sep 21 '21 at 22:33
  • You're right, the cmdline it's loaded as an array. Adding `PROC_EDITCMDLCVT` flag should ensure that a single `**char` is returned. – Tombart Sep 21 '21 at 22:46