/* $Id: tlist.c,v 1.2 2002/09/03 18:44:18 chorns Exp $
 *
 * ReactOS Project
 * TList
 *
 * Copyright (c) 2000,2001 Emanuele Aliberti
 */
#include <reactos/buildno.h>
#define NTOS_USER_MODE
#include <ntos.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#define ALREADY_PROCESSED ((DWORD)-1)

LPWSTR ThreadStateName [] =
{
  L"Initialized",
  L"Ready",
  L"Running",
  L"Standby",
  L"Terminated",
  L"Wait",
  L"Transition",
  L"Unknown",
  NULL
};



int STDCALL PrintBanner (VOID)
{
  printf ("ReactOS "KERNEL_RELEASE_STR" T(ask)List\n");
  printf ("Copyright (c) 2000,2001 Emanuele Aliberti\n\n");
  return EXIT_SUCCESS;
}

int STDCALL PrintSynopsys (VOID)
{
  PrintBanner ();
  printf ("Usage: tlist [-t | PID | -l]\n\n"
          "  -t   print the task list tree\n"
          "  PID  print module information for this ID\n"
          "  -l   print license information\n");
  return EXIT_SUCCESS;
}

int STDCALL PrintLicense (VOID)
{
  PrintBanner ();
  printf (
"This program is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or\n"
"(at your option) any later version.\n\n");
  printf (
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"GNU General Public License for more details.\n\n");
  printf (
"You should have received a copy of the GNU General Public License\n"
"along with this program; if not, write to the Free Software\n"
"Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n");
  return EXIT_SUCCESS;
}

BOOL STDCALL AcquirePrivileges (VOID)
{
  /* TODO: implement it */
  return TRUE;
}

PSYSTEM_PROCESS_INFORMATION STDCALL
GetProcessAndThreadsInfo (PULONG Size)
{
  NTSTATUS                    Status = STATUS_SUCCESS;
  PSYSTEM_PROCESS_INFORMATION pInfo = NULL;
  ULONG                       Length = PAGE_SIZE;
  ULONG                       RequiredLength = 0;

  while (TRUE)
  {
    Status = NtAllocateVirtualMemory (
               NtCurrentProcess(),
	       (PVOID) & pInfo,
	       0,
	       & Length,
	       (MEM_RESERVE | MEM_COMMIT),
	       PAGE_READWRITE
	       );
    if (!NT_SUCCESS(Status) || (NULL == pInfo))
    {
      fprintf (stderr, "%s(%d): Status = 0x%08lx\n",__FUNCTION__,__LINE__,Status);
      return NULL;
    }
    /*
     *	Obtain required buffer size (well, try to...)
     */
    if (NtQuerySystemInformation (
          SystemProcessesAndThreadsInformation,
          pInfo,
          Length,
	  & RequiredLength
	  ) != STATUS_INFO_LENGTH_MISMATCH)
    {
      break;
    }
    NtFreeVirtualMemory (NtCurrentProcess(), (PVOID)&pInfo, & Length, MEM_RELEASE);
    Length += PAGE_SIZE;
  }
  if (!NT_SUCCESS(Status))
  {
    NtFreeVirtualMemory (NtCurrentProcess(), (PVOID)&pInfo, & Length, MEM_RELEASE);
    return NULL;
  }
  if (NULL != Size)
  {
    *Size = Length;
  }
  return pInfo;
}

int STDCALL
ProcessHasDescendants (
  ULONG                       Pid,
  PSYSTEM_PROCESS_INFORMATION pInfo
  )
{
  LONG Count = 0;

  if (NULL == pInfo) return 0;
  do {

      if (ALREADY_PROCESSED != pInfo->ParentProcessId)
      {
        if ((Pid != pInfo->ProcessId) && (Pid == pInfo->ParentProcessId))
        {
          ++ Count;
        }
      }
      (PBYTE) pInfo += pInfo->RelativeOffset;

  } while (0 != pInfo->RelativeOffset);

  return Count;
}


BOOL STDCALL
GetProcessInfo (
  PSYSTEM_PROCESS_INFORMATION pInfo,
  LPWSTR                      * Module,
  LPWSTR                      * Title
  )
{
      *Module = (pInfo->Name.Length ? pInfo->Name.Buffer : L"System process");
      *Title = L""; /* TODO: check if the process has any window */
      return TRUE;
}

int STDCALL PrintProcessInfoDepth (
  PSYSTEM_PROCESS_INFORMATION pInfo,
  LONG                        Depth
  )
{
  INT     d = 0;
  LPWSTR  Module = L"";
  LPWSTR  Title = L"";
  
  for (d = 0; d < Depth; d ++) printf ("  ");
  GetProcessInfo (pInfo, & Module, & Title);
  wprintf (
    L"%s (%d, %d) %s\n",
    Module,
    pInfo->ProcessId,
    pInfo->ParentProcessId,
    Title
    );
  return EXIT_SUCCESS;
}

int STDCALL
PrintProcessAndDescendants (
  PSYSTEM_PROCESS_INFORMATION pInfo,
  PSYSTEM_PROCESS_INFORMATION pInfoBase,
  LONG                        Depth
  )
{
  DWORD   Pid = 0;

  if (NULL == pInfo) return EXIT_FAILURE;
  /* Print current pInfo process */
  PrintProcessInfoDepth (pInfo, Depth ++);
  pInfo->ParentProcessId = ALREADY_PROCESSED;
  /* Save current process' PID */
  Pid = pInfo->ProcessId;
  /* Scan and print possible children */
  do {

    if (ALREADY_PROCESSED != pInfo->ParentProcessId)
    {
      if (Pid == pInfo->ParentProcessId)
      {
        if (ProcessHasDescendants (Pid, pInfoBase))
        {
          PrintProcessAndDescendants (
            pInfo,
            pInfoBase,
            Depth
            );
        }
	else
	{
          PrintProcessInfoDepth (pInfo, Depth);
	  pInfo->ParentProcessId = ALREADY_PROCESSED;
	}
      }
    }
    (PBYTE) pInfo += pInfo->RelativeOffset;

  } while (0 != pInfo->RelativeOffset);
  
  return EXIT_SUCCESS;
}

int STDCALL PrintProcessList (BOOL DisplayTree)
{
  PSYSTEM_PROCESS_INFORMATION pInfo = NULL;
  PSYSTEM_PROCESS_INFORMATION pInfoBase = NULL;
  LONG                        Length = 0;
  LPWSTR                      Module = L"";
  LPWSTR                      Title = L"";

  
  pInfo = GetProcessAndThreadsInfo (& Length);
  if (NULL == pInfo) return EXIT_FAILURE;
  pInfoBase = pInfo;
  do {
      if (FALSE == DisplayTree)
      {
        GetProcessInfo (pInfo, & Module, & Title);
        wprintf (
          L"%4d %-16s %s\n",
	  pInfo->ProcessId,
	  Module,
	  Title,
	  pInfo->ParentProcessId
	  );
      }
      else 
      {
	if (ALREADY_PROCESSED != pInfo->ParentProcessId)
	{
	  PrintProcessAndDescendants (pInfo, pInfoBase, 0);
	}
      }
      (PBYTE) pInfo += pInfo->RelativeOffset;

    } while (0 != pInfo->RelativeOffset);
  
    NtFreeVirtualMemory (
      NtCurrentProcess(),
      (PVOID) & pInfoBase,
      & Length,
      MEM_RELEASE
      );
  
    return EXIT_SUCCESS;
}


int STDCALL PrintThreads (PSYSTEM_PROCESS_INFORMATION pInfo)
{
  ULONG                    ThreadIndex = 0;
  NTSTATUS                 Status = STATUS_SUCCESS;
  HANDLE                   hThread = INVALID_HANDLE_VALUE;
  OBJECT_ATTRIBUTES        Oa = {0};
  PVOID                    Win32StartAddress = NULL;
  THREAD_BASIC_INFORMATION tInfo = {0};
  ULONG                    ReturnLength = 0;

  if (NULL == pInfo) return EXIT_FAILURE;

  wprintf (L"   NumberOfThreads: %d\n", pInfo->ThreadCount);

  for (ThreadIndex = 0; ThreadIndex < pInfo->ThreadCount; ThreadIndex ++)
  {
    Status = NtOpenThread (
	       & hThread,
	       THREAD_QUERY_INFORMATION,
	       & Oa,
	       & pInfo->ThreadSysInfo[ThreadIndex].ClientId
               );
    if (!NT_SUCCESS(Status))
    {
      continue;
    }
    
    Status = NtQueryInformationThread (
               hThread,
	       ThreadBasicInformation,
	       (PVOID) & tInfo,
	       sizeof tInfo,
	       & ReturnLength
               );
    if (!NT_SUCCESS(Status))
    {
      NtClose (hThread);
      continue;
    }

    Status = NtQueryInformationThread (
               hThread,
	       ThreadQuerySetWin32StartAddress,
	       (PVOID) & Win32StartAddress,
	       sizeof Win32StartAddress,
	       & ReturnLength
               );
    if (!NT_SUCCESS(Status))
    {
      NtClose (hThread);
      continue;
    }
    
    NtClose (hThread);

    /* Now print the collected information */
    wprintf (L"   %4d Win32StartAddr:0x%08x LastErr:0x%08x State:%s\n",
      pInfo->ThreadSysInfo[ThreadIndex].ClientId.UniqueThread,
      Win32StartAddress,
      0 /* FIXME: ((PTEB) tInfo.TebBaseAddress)->LastErrorValue */,
      ThreadStateName[pInfo->ThreadSysInfo[ThreadIndex].State]
      );
  } 
  return EXIT_SUCCESS;
}

int STDCALL PrintModules (VOID)
{
	/* TODO */
	return EXIT_SUCCESS;
}

PSYSTEM_PROCESS_INFORMATION STDCALL
GetProcessInfoPid (
  PSYSTEM_PROCESS_INFORMATION pInfoBase,
  DWORD                       Pid
  )
{
  if (NULL == pInfoBase) return NULL;
  do {

    if (Pid == pInfoBase->ProcessId)
    {
      return pInfoBase;
    }
    (PBYTE) pInfoBase += pInfoBase->RelativeOffset;

  } while (0 != pInfoBase->RelativeOffset);

  return NULL;
}

int STDCALL PrintProcess (char * PidStr)
{
  NTSTATUS                    Status = 0;
  HANDLE                      hProcess = 0;
  OBJECT_ATTRIBUTES           Oa = {0};
  CLIENT_ID                   ClientId = {0, 0};

  
  ClientId.UniqueProcess = (PVOID) atol (PidStr);
 
  if (FALSE == AcquirePrivileges ())
  {
    return EXIT_FAILURE;
  }
  
  Status = NtOpenProcess (
             & hProcess,
             PROCESS_QUERY_INFORMATION,
             & Oa,
             & ClientId
             );
  if (NT_SUCCESS(Status))
  {
    ULONG                       ReturnLength = 0;
    PROCESS_BASIC_INFORMATION   PsBasic;
    VM_COUNTERS                 PsVm;
    PSYSTEM_PROCESS_INFORMATION pInfo = NULL;
    PSYSTEM_PROCESS_INFORMATION pInfoBase = NULL;
    LONG                        pInfoBaseLength = 0;
    LPWSTR                      Module = L"";
    LPWSTR                      Title = L"";
    
    Status = NtQueryInformationProcess (
               hProcess,
	       ProcessBasicInformation,
	       & PsBasic,
	       sizeof (PsBasic),
	       & ReturnLength
               );
    if (!NT_SUCCESS(Status))
    {
      return EXIT_FAILURE;
    }
    Status = NtQueryInformationProcess (
               hProcess,
	       ProcessVmCounters,
	       & PsVm,
	       sizeof (PsVm),
	       & ReturnLength
               );
    if (!NT_SUCCESS(Status))
    {
      return EXIT_FAILURE;
    }

    pInfoBase = GetProcessAndThreadsInfo (& pInfoBaseLength);
    if (NULL == pInfoBase) return EXIT_FAILURE;

    pInfo = GetProcessInfoPid (pInfoBase, (DWORD) ClientId.UniqueProcess);
    if (NULL == pInfo) return EXIT_FAILURE;

    GetProcessInfo (pInfo, & Module, & Title);
    
    wprintf (L"%4d %s\n", ClientId.UniqueProcess, Module);
#if 0
    printf ("   CWD:     %s\n", ""); /* it won't appear if empty */
    printf ("   CmdLine: %s\n", ""); /* it won't appear if empty */
#endif
    printf ("   VirtualSize:     %5ld kb   PeakVirtualSize:     %5ld kb\n",
      ((LONG) PsVm.VirtualSize / 1024),
      ((LONG) PsVm.PeakVirtualSize / 1024)
      );
    printf ("   WorkingSetSize:  %5ld kb   PeakWorkingSetSize:  %5ld kb\n",
      ((LONG) PsVm.WorkingSetSize / 1024),
      ((LONG) PsVm.PeakWorkingSetSize / 1024)
      );
    
    PrintThreads (pInfo);

    PrintModules ();
    
    NtFreeVirtualMemory (
      NtCurrentProcess(),
      (PVOID) & pInfoBase,
      & pInfoBaseLength,
      MEM_RELEASE
      );
  
    NtClose (hProcess);
    
    return EXIT_SUCCESS;
  }
  return EXIT_FAILURE;
}


int main (int argc, char * argv [])
{
  if (1 == argc)
  {
    return PrintProcessList (FALSE);
  }
  if (2 == argc)
  {
    if (('-' == argv [1][0]) && ('\0' == argv [1][2]))
    {
      if ('t' == argv [1][1])
      {
        return PrintProcessList (TRUE);
      }
      if ('l' == argv [1][1])
      {
        return PrintLicense ();
      }
    }
    if (isdigit(argv[1][0]))
    {
      return PrintProcess (argv[1]);
    }
  }
  return PrintSynopsys ();
}

/* EOF */
