Saturday, February 4, 2012

How to monitor the contents of a directory and its subdirectories on Windows.

I need a function that is notified about changes in a directory tree. How can I implement it?

First of all we may use FindFirstChangeNotification / FindNextChangeNotification functions. They return a handle that can be used in one of the wait functions (for instance, WaitForSingleObject, WaitForMultipleObjects etc). An example can be found here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365261%28v=vs.85%29.aspx

This solution has a small problem: a caller is notified when a change occurs but this API does not provide any way to check what exactly was changed. Of course it is possible to store a list of files with their attributes (such as file size and file modification time) but probably there is a better solution?

I found the function ReadDirectoryChangesW a bit more useful. It provides information about what exactly was changed (file name and action description, i.e. add/change etc).

To use it, first of all we need to get a handle of the specified directory with the help of CreateFile (dwDesiredAccess argument should have FILE_LIST_DIRECTORY, dwFlagsAndAttributes should have FILE_FLAG_BACKUP_SEMANTICS). Pass this handle with a buffer into ReadDirectoryChangesW. The buffer should be large enough to hold one or several items of type FILE_NOTIFY_INFORMATION. The synchronous version of function returns when the content of directory is changed (actually it is possible to make asynchronous calls as well, but this is beyond the scope of this post). 

So a very simple synchronous sample looks like (Unicode version):


#include <windows.h>

void TestDirChanges(LPCWSTR path)
{
    /*
    FileName member of FILE_NOTIFY_INFORMATION has only one WCHAR according to definition. Most likely, this field will have more characters.
    So the expected size of one item is (sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR)).
    Prepare buffer for 256 items.
    */
    char buf[256 * (sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR))] = {0};
    DWORD bytesReturned = 0;
    BOOL result = FALSE;
    FILE_NOTIFY_INFORMATION *fni = NULL;

    HANDLE hDir = CreateFile(path,
        FILE_LIST_DIRECTORY | STANDARD_RIGHTS_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS,
        NULL);
         
    if (!hDir || hDir == INVALID_HANDLE_VALUE)
    {
        wprintf(L"CreateFile failed\n");
        return;
    }

    while (1)
    {
        result = ReadDirectoryChangesW(hDir,
            buf,
            sizeof(buf) / sizeof(*buf),
            TRUE, /* monitor the entire subtree */
            FILE_NOTIFY_CHANGE_FILE_NAME |
                FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_ATTRIBUTES |
                FILE_NOTIFY_CHANGE_SIZE |
                FILE_NOTIFY_CHANGE_LAST_WRITE |
                FILE_NOTIFY_CHANGE_LAST_ACCESS |
                FILE_NOTIFY_CHANGE_CREATION |
                FILE_NOTIFY_CHANGE_SECURITY,
            &bytesReturned,
            NULL,
            NULL);

        if (result && bytesReturned)
        {
            wchar_t filename[MAX_PATH];
            wchar_t action[256];
            for (fni = (FILE_NOTIFY_INFORMATION*)buf; fni; )
            {
                switch (fni->Action)
                {
                case FILE_ACTION_ADDED:
                    wcscpy_s(action, sizeof(action) / sizeof(*action), L"File added:");
                    break;

                case FILE_ACTION_REMOVED:
                    wcscpy_s(action, sizeof(action) / sizeof(*action), L"File removed:");
                    break;

                case FILE_ACTION_MODIFIED:
                    wcscpy_s(action, sizeof(action) / sizeof(*action), L"File modified:");
                    break;

                case FILE_ACTION_RENAMED_OLD_NAME:
                    wcscpy_s(action, sizeof(action) / sizeof(*action), L"File renamed, was:");
                    break;

                case FILE_ACTION_RENAMED_NEW_NAME:
                    wcscpy_s(action, sizeof(action) / sizeof(*action), L"File renamed, now is:");
                    break;

                default:
                    swprintf_s(action, sizeof(action) / sizeof(*action), L"Unkonwn action: %ld. File name is:", fni->Action);
                }

                if (fni->FileNameLength)
                {
                    wcsncpy_s(filename, MAX_PATH, fni->FileName, fni->FileNameLength / 2);
                    filename[fni->FileNameLength / 2] = 0;
                    wprintf(L"%s '%s'\n", action, filename);
                }
                else
                {
                    wprintf(L"%s <EMPTY>\n", action);
                }               

                if (fni->NextEntryOffset)
                {
                    char *p = (char*)fni;
                    fni = (FILE_NOTIFY_INFORMATION*)(p + fni->NextEntryOffset);
                }
                else
                {
                    fni = NULL;
                }
            }
        }
        else
        {
            wprintf(L"ReadDirectoryChangesW failed\n");
        }
    }

    CloseHandle(hDir);
}

The call might look similar to
TestDirChanges(L"C:\\Documents and Settings\\SomeUser\\1\\").

In order to make the things simpler I didn't introduce any way to stop, so this sample runs forever (until Ctrl-C or kill).

Sunday, January 8, 2012

Useful Linux commands

Useful information about Linux commands.


1. The very famous "Argument list too long" issue.
When I try to copy too many files with command like
cp /blablabla/somedir/* ./
I got error like "-bash: /bin/cp: Argument list too long". The same problem appears in any command where asterisk applies to a large count of files.

An easy way to deal with it is to use find command. For instance,
find /blablabla/somedir/ -name "*" -exec cp -p {} ./ \;


2. Rather useful GUI to build find command:
http://find.unixpin.com/

3. Get information about executable files and/or shared libraries.

ldd prints the shared libraries required by each program or shared library specified on the command line.
For instance,
ldd a.out - prints out the list of *.so files on which a.out depends.

nm - list symbols from object files (for instance, can be used to get list of functions in *.so file).

file - determine file type.
For instance,
file a.out prints out the file format and target architecture (i.e. 32 or 64).

objdump - display information from object files.

For instance, 
objdump -f a.out
may be used to get file format as well;

objdump -x a.out
prints out a lot of useful information.

4. How to find out Linux distribution name and version.

cat /etc/*-release - for distribution name.
uname -mrs - prints the machine hardware name, the kernel release and the kernel name. This can be used to get CPU type: CPU is 64bit if you see x86_64.
lsb_release -a - prints Linux Standard Base and distribution-specific information.

More information is available here and here.

5. lsof - list open files.
lsof -i :8000 - who is listening on the port 8000.

Monday, January 2, 2012

How to list all users of the given group (Linux)

Let's say I have a group name and I want function that lists all users of this group.

From the first point of view, the solutions is obvious. There is a function getgrnam which returns a pointer to struct group which has array of strings gr_mem (type "man getgrnam" for details). So we need to call the function and iterate through gr_mem until we meet NULL.

However, this is not accurate.  The problem is that this function parses the group databases. Some users may be not present in the group database but may have group id set as a field in /etc/passwd. So we need to iterate through all users to find those of them who have group id equal to the id of the given group.

The following code illustrates this approach. Please note that it is a sample only. In the real project you will need to check for duplicates because some users may be listed twice.

#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>

void list_all_users(const char *groupname) {
  struct group *grp = getgrnam(groupname);

  if (grp) {
    unsigned int i = 0;
    struct passwd *user_info;

    printf("group %s has the following members:\n", groupname);

     /* iterate through groups database */
    while (grp->gr_mem[i]) {
      printf("  %s\n", grp->gr_mem[i]);
    }

    for (user_info = getpwent(); user_info; user_info = getpwent()) {
      if (user_info->pw_gid == grp->gr_gid) {
        printf("  %s\n", user_info->pw_name);
      }
    }

    endpwent();
  } else {
    printf("group %s not found", groupname);
  }

}

How to list all users / groups programmatically (Linux)

In my previous post I described how to list all users / groups on Windows machine.

This post describes how to list all users / groups of Linux server.

Use functions  getpwent for users and  getgrent() for groups (type man getpwent or man getgreent for details).

The sample code looks like:

#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>

void list_users(void) {
  struct passwd *p = getpwent();

  for (; p; p = getpwent()) {
    puts(p->pw_name);
  }

  endpwent();
}

void list_groups(void) {
  struct group *p = getgrent();

  for (; p; p = getgrent()) {
    puts(p->gr_name);
  }

  endgrent();
}

How to list all users / groups of domain programmatically (Windows)

 To list all users of domain in command line, use command "net users /domain". The command  "net users" without /domain switch returns all users of the local workstation.

 To list all groups of domain in command line, use command "net groups /domain". Again, the same command  without /domain switch returns all groups of the local workstation.

How to get the same reports programmatically?

Use the function NetQueryDisplayInformation in both cases. MSDN provides example code of how to use it. Depending on parameter the function returns array of NET_DISPLAY_USER or NET_DISPLAY_GROUP structures. Pass NULL for the first parameter, this will list all users / groups from local computer.

How to deal with the domain? It seems that we may use the function NetServerEnum with SV_TYPE_DOMAIN_CTRL as servertype parameter (see MSDN for the sample). Note that you need to run the program as administrator. This returns domain controller, which can be used as the first parameter for NetQueryDisplayInformation.

Don't be surprised: depending on the configuration of your network, domain controller name may be equal or not equal to the domain name.

But wait! The command "net users /domain" does not require to be run under administrator account, while our program does. So it seems there could be a better solution.

For this case you may find the DsEnumerateDomainTrusts function more useful. Pass NULL as the first parameter and DS_DOMAIN_PRIMARY as a flag. This function exists since Windows 2000, so in case of old good Windows NT you may still choose NetServerEnum... but I don't find this information useful nowadays.

Now lets put it all together. NetQueryDisplayInformation works with Unicode strings only. DsEnumerateDomainTrusts exists in both versions. To make the things easier I assume that Unicode is set. So the sample code looks like:

#include <Dsgetdc.h>
#include <lm.h>

DWORD GetAllUsersOfPrimaryDomain()
{
  PNET_DISPLAY_USER pBuff, p;
  DWORD res, dwRec, i = 0;
  ULONG domainsCount;
  PDS_DOMAIN_TRUSTS domain;

  res = DsEnumerateDomainTrusts(NULL, DS_DOMAIN_PRIMARY, &domain, &domainsCount);

  if (res != ERROR_SUCCESS)
  {
    printf("DsEnumerateDomainTrusts failed with error %ld\n", res);
    return res;
  }

  printf("DsEnumerateDomainTrusts returns %ld domain, this test will print users for the first one (%S) ",
domainsCount, domain->DnsDomainName);

  do // begin do
  {
      //
      // Call the NetQueryDisplayInformation function;
      //   specify information level 3 (group account information).
      //
      res = NetQueryDisplayInformation(domain->DnsDomainName, 1, i, 1000, MAX_PREFERRED_LENGTH, &dwRec, (PVOID*)&pBuff);
      //
      // If the call succeeds,
      //
      if((res==ERROR_SUCCESS) || (res==ERROR_MORE_DATA))
      {
         p = pBuff;
         for(;dwRec>0;dwRec--)
         {
            //
            // Print the retrieved group information.
            //
            printf("Name:      %S\n"
                  "Comment:   %S\n"
                  "Full name:  %u\n"
                  "--------------------------------\n",
                  p->usri1_name,
                  p->usri1_comment,
                  p->usri1_full_name);
            //
            // If there is more data, set the index.
            //
            i = p->usri1_next_index;
            p++;
         }
         //
         // Free the allocated memory.
         //
         NetApiBufferFree(pBuff);
      }
      else
         printf("Error: %u\n", res);
   //
   // Continue while there is more data.
   //
  } while (res==ERROR_MORE_DATA); // end do

    NetApiBufferFree(domain);

  return ERROR_SUCCESS;
}



(NetQueryDisplayInformation part of this sample was copied from MSDN).

One more question is left. Suppose you have a user with fullname "DOMAINNAME\USERNAME" and you want to list all users of DOMAINNAME. As I mentioned before domain controller name may be equal or not equal to domain name. So if you pass DOMAINNAME as an argument into NetQueryDisplayInformation you may get error. How to get domain controller of DOMAINNAME?

In this case use the function NetGetAnyDCName like in the following sample:

LPWSTR  controller = NULL;
NetGetAnyDCName(null, L“DOMAINNAME”, &controller);
/* ...now pass controller to  NetQueryDisplayInformation as above... */
NetApiBufferFree(domain_controller);

Wednesday, December 7, 2011

How to check password expiration date programmatically (Windows, Linux, Solaris, AIX)

Many operating systems allow you to set maximum password age. After it is reached, user gets the message like “Your password has expired and must be changed”. For instance, in case of Windows 7 you can set maximum password age in the following way:

1. Run “secpol.msc”
2. In the left pane, expand “Account Policies”, and click on “Password Policy”. Edit the values in the right pane.
3. Make sure that user account has password expiration enabled: run lusrmgr.msc, find the required user account, click proiperties, ensure that “Password never expires” is uchecked.

(see http://www.sevenforums.com/tutorials/7539-local-users-groups-manager-open.html, http://www.sevenforums.com/tutorials/73210-password-expiration-enable-disable.html for details).

The post describes the API that allows you to find password expiration date for a specified user account.

1. Windows: use NetUserGetInfo function. For a given domain controller name, user name and information level it returns user information. In our case we need information level 2 (and probably higher). In this case information is returned in USER_INFO_2 which has a field called usri2_password_age (the number of seconds that have elapsed since the password was last changed).

Now check the NetUserModalsGet fuction. For a given domain controller name it returns global information about users (see struct USER_MODALS_INFO_0, which has a usrmod0_max_passwd_age member). Now just subtract usri2_password_age from usrmod0_max_passwd_age and divide by 60*60*24 (the number of seconds in a day) to get count of days left.

To get the domain contoller you may use NetGetAnyDCName fuction (for instance, call NetGetAnyDCName(null, L“MYDOMAIN”, &controller) to get the domain controller of MYDOMAIN).

All of the functions mentioned above allocate memory, so do not forget to free it using the NetApiBufferFree function.

Samples can be found in MSDN.

2. Linux and Solaris provide a set of shadow fuctions (#include <shadow.h>).
We are interested in getspnam function.
For a given user name It returns a pointer to struct spwd. Interesting members are sp_lstchg (days since Jan 1, 1970 password was last changed) and sp_max (days after which password must be changed). So (spwd.sp_lstchg + spwd.sp_max) is a date when user’s password must be changed.
More information is available in manual.

3. AIX. I haven’t found a reliable way to find password expiration date. A possible solution is to call passwdexpired function. For a given user name it returns a character string like “Your password will expire: Wed Nov 2 10:30:35 EDT 2011”. Now it is possible to parse the string. However I am not sure in the format of message and hence I dislike this solution.
If you know any way to get the password expiration date, please let me know!

Friday, December 2, 2011

How to get process full name programmatically (Solaris)

In the previous post I described how to get the command line and the full name of process executable file on Linux. However, the described solution does not work on Solaris.

Solaris has the /proc pseudo-filesystem directory as well, but it’s structure is different.

On Solaris 10 there is a link /proc/PID/path/a.out which points to the executable file. So it’s enough to call readlink function.
Prior to Solaris 10, things are more complex. I haven’t found a reliable way to get a full name of the executable file. But the following notes may be useful.

There is a binary /proc/<PID>/psinfo file, which stores data as in struct psinfo (#include <procfs.h>). This struct has pr_fname and pr_psargs fields. But it seems that pr_fname usually holds the file name of executable only, without the absolute path. The second problem is that both pr_fname and pr_psargs can be truncated.

There is a function called getexecname(), which returns file name of the executable. But it has the same problem: it may hold the file name part only, without the absolute path. Manual recommends getcwd function which returns the current working directory. However, current working directory may differ from the starting directory so this solution is not reliable.

So the following program illustrates all of these approaches:

#include <stdio.h>
#include <unistd.h>
#include <procfs.h>
#include <stdlib.h>

int main() {
ssize_t linknamelen;
FILE *f;
char linkname[256], filename[256];
pid_t pid = getpid();

/* try to readlink the file /proc/PID/path/a.out */
sprintf(linkname, "/proc/%d/path/a.out", pid);
linknamelen = readlink(linkname, filename, sizeof(filename) / sizeof(*filename) - 1);
if (linknamelen > 0) {
 filename[linknamelen] = 0;
 printf("Full name is %s\n", filename);
} else {
 getcwd(filename, sizeof(filename) / sizeof(*filename) - 1);
 printf("Current directory is %s. Name is %s\n", filename, getexecname());
}

/* read the command line from /proc/PID/psinfo */
sprintf(filename, "/proc/%d/psinfo", pid);

f = fopen(filename, "r");
if (f) {
psinfo_t info;
if (fread(&info, sizeof(info), 1, f) > 0) {
    printf("fname=%s, args=%s\n", info.pr_fname, info.pr_psargs);
}
fclose(f);
}

return 0;
}