Managing Linux via OMI: Implementation (2)

PowerShell-Control-Linux-Implementation-2

This is fifth part of the series. You can find outline of the whole series here. We already have OMI server up, running and accessible from outside and we have Lin_Process class implemented, with two properties: PID and cmdline. Today we will extend our schema to few other properties and we will add a method to it. Unfortunately, it means more amateur C++ code, but also more fun and information retrieved remotely from our test, CentOS box.

Add this, and that, and that too…

When I was done with two basic properties I started to look around in /proc folders for more clues. First thing that hit me was the fact, that many folders had link to executable there. I decided that I would love to add this property next. There were the same steps involved: modifying schema, generating new files, modifying file containing my class. I had used readlink to find the target here:

// Declarations...
char Target[512];

// code running for each process found
(void)sprintf(Path,"/proc/%d/exe", proc.PID_value());
Length = readlink(Path,Target,sizeof(Target));
if (Length >= 0)
{
    Target[Length] = '';
    proc.exe_value(Target);
}

At this point I decided that it will probably be easier to use regular expressions and just parse /proc/PID/status file. All the properties added with it are programmed in the same way: I read status file once for all properties and than:

  • reset UID to value of buffer
  • move UID to a position where regular expression’s match group start

Next operations depends on type of value that will be retrieved:

  • use atoi() directly on resulting string for values that are integers
  • use strncpy to get only part of the string that matches

For example, getting OwnerID and Name:

// Few new declarations...
regex_t  regId, regName;
char *uid, *Property; 

// Creating regular expressions...
const char *regexId =   "Uid:[^0-9]*([0-9]+)";
const char *regexName = "Name:[^a-zA-Z0-9]*([a-zA-Z0-9-]+)";

regcomp(&regId,     regexId,     REG_EXTENDED);
regcomp(&regName,   regexName,   REG_EXTENDED);

// Commond part for all "status" stuff...
(void)sprintf(Path,"/proc/%d/status", proc.PID_value());
file = fopen(Path,"r");
fread(buffer, 1, 4096, file);
fclose(file);
uid = buffer;
 
// adding OwnerID - only atoi(uid) needed...
if (regexec(&regId, buffer, 2, matches, 0) == 0)
{
    uid = buffer;
    uid += matches[1].rm_so;
    proc.OwnerID_value(atoi(uid));
}

// adding Name - more work required...
if (regexec(&regName, buffer, 2, matches, 0) == 0)
{
    uid = buffer;
    uid += matches[1].rm_so;
    Length = matches[1].rm_eo - matches[1].rm_so;
    Property = (char*)malloc(Length);
    strncpy(Property, uid, Length);
    Property[Length] = '';
    proc.Name_value(Property);
}

Using this file gave me a few interesting properties to play with. And few others that I could add relatively easily, by simply adding correct regex syntax that would let me read the value. Again: probably not the best solution that exists, but it worked fine for me. I ended up with following schema (most of property names match fields in “status” file):

class Lin_Process
{
    [Key] Uint32 PID;
    Uint32 ParentID;
    Uint64 VmSize;
    Uint64 VmHWM;
    String cmdline;
    String exe;
    String Name;
    String State;
    Uint32 OwnerID;
};

Get one, enumerate all…

At this point I decided that it would be great to implement another method: GetInstance. It’s purpose is to retrieve selected instance only rather than enumerate thru all of them. Looking at my code I decided that rewriting it would probably be worse idea ever. I mean: from practical point of view I need to have a method, that would perform a work on any PID I will throw at it. I thought that calling GetInstance inside EnumerateInstances would be a good idea but it wasn’t. Two possible issues:

  • If I would context.Post(MI_RESULT_OK) inside GetInstance – I would get only first object back
  • If I would not do it – my operation would froze (that’s when I learned why posting MI_RESULT is important)

Eventually I decided to pick gate number 3: create another function and just call it either from GetInstance or from EnumerateInstances. These build in methods would be responsible for handling my MI_RESULTs. Obviously, I had to modify header (Process_Class_Provider.h) to include my own method, GetSingleInstance. My code for EnumerateInstances after this change (part that was previously adding properties other that processes PID):

if (keysOnly)
{
    context.Post(proc);
}
else
{
    GetSingleInstance(context, nameSpace, proc, propertySet);
}

And the code that is responsible for getting information about single instance:

void Process_Class_Provider::GetInstance(
    Context& context,
    const String& nameSpace,
    const Process_Class& instanceName,
    const PropertySet& propertySet)
{
    DIR *pdir;
    char Path[20]; 
 
    (void)sprintf(Path,"/proc/%d",instanceName.PID_value());
    pdir = opendir(Path);
    if (!pdir)
    {
        context.Post(MI_RESULT_NOT_FOUND);
    }
    else
    {
        GetSingleInstance(context, nameSpace, instanceName, propertySet);
        context.Post(MI_RESULT_OK);
    }
    closedir(pdir);
}

That made any changes to schema/ class properties a lot easier, as there was only one method I had to change (GetSingleInstance). So I got properties covered. Next on my plate was implementing support for methods. As a minimum I needed one method that was perfect for demonstration: you can hardly miss destruction of the process… Puszczam oczko

Time to kill

Now that we know who we talk to, it’s time to pass few orders. Implementing methods in OMI starts the same as anything else: by adding the method to the class schema:

class Lin_Process
{
    (...) 
    Uint32 Kill (
        [In] Uint32 Signal
    );
};

As you can see – it accepts one parameter, signal. This obviously matches the definition of kill:

int kill(pid_t pid, int sig);

Once we modify the schema, new method shows up in files responsible for implementation, both in c++ and header file:

void Invoke_Kill(
    Context& context,
    const String& nameSpace,
    const Process_Class& instanceName,
    const Process_Kill_Class& in);

Writing c++ code that would actually do the work and kill the process was not as hard as I expected. All I had to do was to read PID value from instance object and call kill on it using Signal that user passed (or if he didn’t – just make sure it dies by using signal 9). Whole method was only a few lines long:

void Process_Class_Provider::Invoke_Kill(
    Context& context,
    const String& nameSpace,
    const Process_Class& instanceName,
    const Process_Kill_Class& in)
{
    int sig = 9;
    int ret = 0;
    Process_Kill_Class out;
    if (in.Signal_exists())
    {
        sig = in.Signal_value();
    }
   
    ret = kill (instanceName.PID_value(), sig);
    out.MIReturn_value(ret);
    context.Post(out);
    context.Post(MI_RESULT_OK);
}

With this method I could kill process at will, both locally and remotely, simply by calling this OMI method. My work for this self-defined class was almost complete:

PowerShell-OMI-Killing-VIM-remotely

Next on my agenda was creating CMDXML module. With CDXML we can make functions that will behave almost like cmdlets. It requires very little work and will allow us to use more PowerShell syntax both for searching processes that we are interested in, and for stopping them whenever we choose to do so. But that’s a story for another part of this series, we had enough already today. Puszczam oczko

Advertisement

2 thoughts on “Managing Linux via OMI: Implementation (2)

  1. You know this would be a lot more helpful if you explained where the code went. Is it a stand alone file? Where do you place it in the generated C files from the omigen? Put that stuff in there and this will be of far more value. Right now its almost worthless.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s