/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Cygwin-specific code. December 2002
   This is concatenated onto the generic src/os.c file.

   This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org>
*/

/* Special static variables */
static BOOL cygwin_debug = FALSE;
static BOOL cygwin_fake = FALSE; /* when not privileged, setuid = noop */

/* We need a special mkdir that
   allows names starting with // */
#undef mkdir
int cygwin_mkdir( const char *path, mode_t mode )
{
  const char * p = path;
  if (*p == '/') while(*(p+1) == '/') p++;
  return mkdir(p, mode);
}

#undef setuid
int cygwin_setuid(uid_t uid )
{
  int res;
  if (cygwin_fake) return 0;
  else {
    res = setuid(uid);
    if (cygwin_debug)
      fprintf(stderr, "setuid %lu %lu %d pid: %d\n",
              uid, getuid(),res, getpid());
  }
  return res;
}

#undef setgid
int cygwin_setgid(gid_t gid )
{
  int res;
  if (cygwin_fake) return 0;
  else {
    res = setgid(gid);
    if (cygwin_debug)
      fprintf(stderr, "setgid %lu %lu %d pid: %d\n",
	      gid, getgid(), res, getpid());
  }
  return res;
}

/* We have strsignal but cannot use #define
   because types don't match */
#define OS_STRSIGNAL /* src/os.c need not provide it */
char * os_strsignal(int sig)
{
  return (char *) strsignal(sig);
}

#ifndef COMPILE_UTILITY /* Utilities don't need special code */
#ifdef INCLUDE_MINIRES
#include "../minires/minires.c"
#include "../minires/os-interface.c"
#endif
unsigned int cygwin_WinVersion;

/* Conflict between Windows definitions and others */
#ifdef NOERROR
#undef NOERROR
#endif
#ifdef DELETE
#undef DELETE
#endif

#include <windows.h>
#include <sys/cygwin.h>

/* GetVersion()
   MSB: 1 for 95/98/ME; Next 7: build number, except for 95/98/ME
   Next byte: 0
   Next byte: minor version of OS
   Low  byte: major version of OS (3 or 4 for for NT, 5 for 2000 and XP) */
#define VERSION_IS_58M(x) (x & 0x80000000) /* 95, 98, Me   */
#define VERSION_IS_NT(x)  ((x & 0XFF) < 5) /* NT 4 or 3.51 */

/* We use a special routine to initialize
    cygwin_init is called from the OS_INIT macro in main(). */

void cygwin_init(int argc, char ** argv, void * rup,
                 void * eup, void * egp)
{
  int i;
  BOOL ntsec;
  uid_t myuid;
  gid_t mygid;
  struct passwd * pwp;
  char *cygenv, win32_path[MAX_PATH];

  myuid = getuid();
  mygid = getgid();
  cygwin_WinVersion = GetVersion();
  if ((cygenv = getenv("CYGWIN")) == NULL) cygenv = "";
  /* Produce some debugging on stderr,
     cannot yet use exim's debug functions.
     Exim does not use -c and ignores -n */
  for (i = 1; i < argc; i++) {
    if ((argv[i][0] == '-') && (argv[i][1] == 'c')) {
      argv[i][1] = 'n';  /* Replace -c by -n */
      cygwin_debug = TRUE;
      fprintf(stderr, "CYGWIN = \"%s\".", cygenv);
      cygwin_conv_to_win32_path("/", win32_path);
      fprintf(stderr, " Root / mapped to %s.\n", win32_path);
      break;
    }
  }
  if (VERSION_IS_58M(cygwin_WinVersion)) {
    * (uid_t *) rup = myuid;  /* Pretend we are root */
    * (uid_t *) eup = myuid;  /* ... and exim */
    * (gid_t *) egp = mygid;
    return;
  }
  /* Nt/2000/XP
     We always set the root uid to the real uid
       to avoid useless execs following forks.
     We always set the exim uid & gid to those of the "real exim",
       or to the initial root uid (SYSTEM) and exim gid (ADMINS),
       to pass the access test for the config file.
     If ntsec is disabled, we fake all subsequent setuid,
       except if we remain root (SYSTEM).
     We also fake setuid's if we are unprivileged. */

  ntsec = cygwin_internal(CW_CHECK_NTSEC, NULL);

  if ((pwp = getpwnam("exim")) != NULL) {
    * (uid_t *) eup = pwp->pw_uid;  /* Set it according to passwd */
    * (gid_t *) egp = pwp->pw_gid;
  }
  if (myuid == * (uid_t *) rup) { /* Root as per Makefile ROOT_UID */
    if (pwp != NULL) {            /* See if the exim user exists */
      setgid(pwp->pw_gid);	  /* setuid to exim */
      setuid(pwp->pw_uid);
      * (uid_t *) rup = pwp->pw_uid;
      cygwin_fake = ! ntsec;
    }
    else { /* exim user doesn't exist. */
      setgid(* (gid_t *) egp); /* Change to Admininistrators */
    }
  }
  else if (pwp != NULL && myuid == * (uid_t *) eup)  {   /* exim user */
    * (uid_t *) rup = myuid;
    cygwin_fake = ! ntsec;
  }
  else { /* Pretend we are root. We are limited by file access rights */
    * (uid_t *) rup = myuid;
    cygwin_fake = TRUE;
  }
  if (cygwin_debug) {
    fprintf(stderr, "Starting uid %ld, gid %ld, ntsec %d.\n", myuid, mygid, ntsec);
    fprintf(stderr, "root_uid %ld, exim_uid %ld, exim_gid %ld, fake setuid %d.\n",
	    * (uid_t *) rup, * (uid_t *) eup, * (gid_t *) egp, cygwin_fake);
  }
  return;
}

/*****************************************************************
 *
 Functions for average load measurements

 Obtaining statistics in Windows is done at a low level by
 calling registry functions, in particular the key
 HKEY_PERFORMANCE_DATA on NT and successors.
 Something equivalent exists on Win95, see Microsoft article
 HOWTO: Access the Performance Registry Under Windows 95 (Q174631)
 but it is not implemented here.

 The list of objects to be polled is specified in the string
 passed to RegQueryValueEx in ReadStat() below.
 On NT, all objects are polled even if info about only one is
 required. This is fixed in Windows 2000. See articles
 INFO: Perflib Calling Close Procedure in Windows 2000 (Q270127)
 INFO: Performance Data Changes Between Windows NT 4.0 and Windows
 2000 (Q296523)

 It is unclear to me how the counters are primarily identified.
 Whether it's by name strings or by the offset of their strings
 as mapped in X:\Winnt\system32\perfc009.dat [or equivalently as
 reported by the registry functions in GetNameStrings( ) below].
 Microsoft documentation seems to say that both methods should
 work.

 In the interest of speed and language independence, the main
 code below relies on offsets. However if debug is enabled, the
 code verifies that the names of the corresponding strings are
 as expected.

*****************************************************************/
#ifndef OS_LOAD_AVERAGE /* Can be set on command line */
#define OS_LOAD_AVERAGE /* src/os.c need not provide it */

/* Object and counter indices and names */
#define PROCESSOR_OBJECT_INDEX 238
#define PROCESSOR_OBJECT_STRING "238"
#define PROCESSOR_OBJECT_NAME "Processor"
#define PROCESSOR_TIME_COUNTER 6
#define PROCESSOR_TIME_NAME "% Processor Time"

/* Structure to compute the load average efficiently */
static struct {
  long long Time100ns;       /* Last measurement time */
  long long IdleCount;       /* Latest cumulative idle time */
  long long LastCounter;     /* Last measurement counter */
  long long PerfFreq;        /* Perf counter frequency */
  PPERF_DATA_BLOCK PerfData; /* Pointer to a buffer to get the data */
  DWORD BufferSize;          /* Size of PerfData */
  int LastLoad;              /* Last reported load, or -1 */
  LPSTR * NamesArray;        /* Temporary (malloc) buffer for index */
  BOOL Init;                 /* True if initialized */
} cygwin_load = { 0, 0, 0, 0, NULL, 0, 0, NULL, FALSE};

#define BYTEINCREMENT 800    /* Block to add to PerfData */

/*****************************************************************
 *
 Macros to navigate through the performance data.

 *****************************************************************/
#define FirstObject(PerfData)\
  ((PPERF_OBJECT_TYPE)((PBYTE)PerfData + PerfData->HeaderLength))
#define NextObject(PerfObj)\
  ((PPERF_OBJECT_TYPE)((PBYTE)PerfObj + PerfObj->TotalByteLength))
#define ObjectCounterBlock(PerfObj)\
  ((PPERF_COUNTER_BLOCK)(PBYTE)PerfObj + PerfObj->DefinitionLength )
#define FirstInstance(PerfObj )\
  ((PPERF_INSTANCE_DEFINITION)((PBYTE)PerfObj + PerfObj->DefinitionLength))
#define InstanceCounterBlock(PerfInst)\
  ((PPERF_COUNTER_BLOCK) ((PBYTE)PerfInst + PerfInst->ByteLength ))
#define NextInstance(PerfInst )\
  ((PPERF_INSTANCE_DEFINITION)((PBYTE)InstanceCounterBlock(PerfInst) + \
        InstanceCounterBlock(PerfInst)->ByteLength) )
#define FirstCounter(PerfObj)\
  ((PPERF_COUNTER_DEFINITION) ((PBYTE)PerfObj + PerfObj->HeaderLength))
#define NextCounter(PerfCntr)\
  ((PPERF_COUNTER_DEFINITION)((PBYTE)PerfCntr + PerfCntr->ByteLength))

/*****************************************************************
 *
 Load the counter and object names from the registry
 to cygwin_load.NameStrings
 and index them in cygwin_load.NamesArray

 NameStrings seems to be taken from the file
 X:\Winnt\system32\perfc009.dat

 This is used only for name verification during initialization,
 if DEBUG(D_load) is TRUE.

*****************************************************************/
static BOOL GetNameStrings( )
{
  HKEY hKeyPerflib;      // handle to registry key
  DWORD dwArraySize;     // size for array
  DWORD dwNamesSize;     // size for strings
  LPSTR lpCurrentString; // pointer for enumerating data strings
  DWORD dwCounter;       // current counter index
  LONG  res;

  /* Get the number of Counter items into dwArraySize. */
  if ((res = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
			   "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib",
			   0,
			   KEY_QUERY_VALUE, /* KEY_READ, */
			   &hKeyPerflib))
      != ERROR_SUCCESS) {
    DEBUG(D_load) debug_printf("RegOpenKeyEx (1): error %ld (Windows)\n", res);
    return FALSE;
  }
  dwNamesSize = sizeof(dwArraySize); /* Temporary reuse */
  if ((res = RegQueryValueEx( hKeyPerflib,
			      "Last Counter",
			      NULL,
			      NULL,
			      (LPBYTE) &dwArraySize,
			      &dwNamesSize ))
      != ERROR_SUCCESS) {
    DEBUG(D_load) debug_printf("RegQueryValueEx (1): error %ld (Windows)\n", res);
    return FALSE;
  }
  RegCloseKey( hKeyPerflib );
  /* Open the key containing the counter and object names. */
  if ((res = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
			   "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009",
			   0,
			   KEY_READ,
			   &hKeyPerflib))
      != ERROR_SUCCESS) {
    DEBUG(D_load) debug_printf("RegOpenKeyEx (2): error %ld (Windows)\n", res);
    return FALSE;
  }
  /* Get the size of the Counter value in the key
     and then read the value in the tail of NamesArray */
  dwNamesSize = 0;
  lpCurrentString = NULL;
  while (1) {
    res = RegQueryValueEx( hKeyPerflib,
			   "Counter",
			   NULL,
			   NULL,
			   (unsigned char *) lpCurrentString,
			   &dwNamesSize);
    if ((res == ERROR_SUCCESS) && /* Bug (NT 4.0): SUCCESS was returned on first call */
	(cygwin_load.NamesArray != NULL)) break;
    if ((res == ERROR_SUCCESS) || /* but cygwin_load.NamesArrays == NULL */
        (res == ERROR_MORE_DATA)) {
      /* Allocate memory BOTH for the names array and for the counter and object names */
      if ((cygwin_load.NamesArray =
	   (LPSTR *) malloc( (dwArraySize + 1) * sizeof(LPSTR) + dwNamesSize * sizeof(CHAR)))
	  != NULL) {
        /* Point to area for the counter and object names */
	lpCurrentString = (LPSTR) & cygwin_load.NamesArray[dwArraySize + 1];
        continue;
      }
      DEBUG(D_load) debug_printf("Malloc: errno %d (%s)\n", errno, strerror(errno));
    }
    else { /* Serious error */
      DEBUG(D_load) debug_printf("RegQueryValueEx (2): error %ld (Windows)\n", res);
    }
    return FALSE;
  }
  RegCloseKey( hKeyPerflib );
  /* Index the names into an array. */
  while (*lpCurrentString) {
    dwCounter = atol( lpCurrentString );
    lpCurrentString += (lstrlen(lpCurrentString)+1);
    cygwin_load.NamesArray[dwCounter] = lpCurrentString;
    lpCurrentString += (strlen(lpCurrentString)+1);
  }
  return TRUE;
}

/*****************************************************************
 *
 Find the value of the Processor Time counter

*****************************************************************/
static BOOL ReadTimeCtr(PPERF_OBJECT_TYPE PerfObj,
			PPERF_COUNTER_DEFINITION CurCntr,
			PPERF_COUNTER_BLOCK PtrToCntr,
			unsigned long long * TimePtr){
  int j;
  /* Scan all counters. */
  for( j = 0; j < PerfObj->NumCounters; j++ ) {
    if (CurCntr->CounterNameTitleIndex == PROCESSOR_TIME_COUNTER) {
      /* Verify it is really the proc time counter */
      if ((CurCntr->CounterType != PERF_100NSEC_TIMER_INV) || /* Wrong type */
	  ((cygwin_load.NamesArray != NULL) &&		      /* Verify name */
	   (strcmp(cygwin_load.NamesArray[CurCntr->CounterNameTitleIndex],
		   PROCESSOR_TIME_NAME)))) {
	log_write(0, LOG_MAIN|LOG_PANIC,
                  "Incorrect Perf counter type or name %x %s",
		  (unsigned) CurCntr->CounterType,
		  cygwin_load.NamesArray[CurCntr->CounterNameTitleIndex]);
	return FALSE;
      }
      *TimePtr += *(unsigned long long int *) ((PBYTE) PtrToCntr + CurCntr->CounterOffset);
      return TRUE; /* return TRUE as soon as we found the counter */
    }
    /* Get the next counter. */
    CurCntr = NextCounter( CurCntr );
  }
  return FALSE;
}
/*****************************************************************
 *
 ReadStat()
 Measures current Time100ns and IdleCount
 Return TRUE if success.

 *****************************************************************/
static BOOL ReadStat(long long int *Time100nsPtr,
				     long long int * IdleCountPtr)
{
  PPERF_OBJECT_TYPE PerfObj;
  PPERF_INSTANCE_DEFINITION PerfInst;
  PPERF_COUNTER_DEFINITION PerfCntr;
  PPERF_COUNTER_BLOCK PtrToCntr;
  DWORD i, k, res;

  /* Get the performance data for the Processor object
     There is no need to open a key.
     We may need to blindly increase the buffer size.
     BufferSize does not return info but may be changed */
  while (1) {
    DWORD BufferSize = cygwin_load.BufferSize;
    res = RegQueryValueEx( HKEY_PERFORMANCE_DATA,
			   PROCESSOR_OBJECT_STRING,
			   NULL,
			   NULL,
			   (LPBYTE) cygwin_load.PerfData,
			   &BufferSize );
    if (res == ERROR_SUCCESS) break;
    if (res == ERROR_MORE_DATA ) {
      /* Increment if necessary to get a buffer that is big enough. */
      cygwin_load.BufferSize += BYTEINCREMENT;
      if ((cygwin_load.PerfData =
	   (PPERF_DATA_BLOCK) realloc( cygwin_load.PerfData, cygwin_load.BufferSize ))
	  != NULL) continue;
      DEBUG(D_load) debug_printf("Malloc: errno %d (%s)\n", errno, strerror(errno));
    }
    else { /* Serious error */
      DEBUG(D_load) debug_printf("RegQueryValueEx (3): error %ld (Windows)\n", res);
    }
    return FALSE;
  }
  /* Initialize the counters */
  *Time100nsPtr = 0;
  *IdleCountPtr = 0;
  /* We should only have one object, but write general code just in case. */
  PerfObj = FirstObject( cygwin_load.PerfData );
  for( i = 0; i < cygwin_load.PerfData->NumObjectTypes; i++ ) {
    /* We are only interested in the processor object */
    if ( PerfObj->ObjectNameTitleIndex == PROCESSOR_OBJECT_INDEX) {
      /* Possibly verify it is really the Processor object. */
      if ((cygwin_load.NamesArray != NULL) &&
	  (strcmp(cygwin_load.NamesArray[PerfObj->ObjectNameTitleIndex],
		  PROCESSOR_OBJECT_NAME))) {
	log_write(0, LOG_MAIN|LOG_PANIC,
		  "Incorrect Perf object name %s",
		  cygwin_load.NamesArray[PerfObj->ObjectNameTitleIndex]);
	return FALSE;
      }
      /* Get the first counter */
      PerfCntr = FirstCounter( PerfObj );
      /* See if the object has instances.
	 It should, but write general code. */
      if( PerfObj->NumInstances != PERF_NO_INSTANCES ) {
	PerfInst = FirstInstance( PerfObj );
	for( k = 0; k < PerfObj->NumInstances; k++ ) {
	  /* There can be several processors.
             Accumulate both the Time100ns and the idle counter.
             On Win 2000 I have seen an instance named "_Total".
	     Do not use it.	We only use instances with a single
             character in the name.
             If we examine the object names, we also look at the instance
             names and their lengths and issue reports */
	  if ( cygwin_load.NamesArray != NULL) {
            CHAR ascii[30]; /* The name is in unicode */
	    wsprintf(ascii,"%.29lS",
		     (char *)((PBYTE)PerfInst + PerfInst->NameOffset));
	    log_write(0, LOG_MAIN,
                      "Perf: Found processor instance \"%s\", length %d",
                      ascii, PerfInst->NameLength);
	    if ((PerfInst->NameLength != 4) &&
                (strcmp(ascii, "_Total") != 0)) {
	      log_write(0, LOG_MAIN|LOG_PANIC,
                        "Perf: WARNING: Unexpected processor instance name");
	      return FALSE;
            }
          }
	  if (PerfInst->NameLength == 4) {
            *Time100nsPtr += cygwin_load.PerfData->PerfTime100nSec.QuadPart;
	    PtrToCntr = InstanceCounterBlock(PerfInst);
	    if (! ReadTimeCtr(PerfObj, PerfCntr, PtrToCntr, IdleCountPtr)) {
	      return FALSE;
	    }
          }
	  PerfInst = NextInstance( PerfInst );
	}
        return (*Time100nsPtr != 0); /* Something was read */
      }
      else { /* No instance, just the counter data */
	*Time100nsPtr = cygwin_load.PerfData->PerfTime100nSec.QuadPart;
	PtrToCntr = ObjectCounterBlock(PerfObj);
	return ReadTimeCtr(PerfObj, PerfCntr, PtrToCntr, IdleCountPtr);
      }
    }
    PerfObj = NextObject( PerfObj );
  }
  return FALSE; /* Did not find the Processor object */
}

/*****************************************************************
 *
 InitLoadAvg()
 Initialize the cygwin_load structure.
 and set cygwin_load.Flag to TRUE if successful.
 This is called the first time os_getloadavg is called
 *****************************************************************/
static void InitLoadAvg()
{
  BOOL success = TRUE;
  cygwin_load.Init = TRUE;        /* We have run */
  /* Get perf frequency and counter */
  QueryPerformanceFrequency((LARGE_INTEGER *)& cygwin_load.PerfFreq);
  QueryPerformanceCounter((LARGE_INTEGER *)& cygwin_load.LastCounter);
  DEBUG(D_load) {
    /* Get the name strings through the registry
       to verify that the object and counter numbers
       have the names we expect */
    success = GetNameStrings();
  }
  /* Get initial values for Time100ns and IdleCount
     and possibly verify the names */
  //  success = success &&
  success = ReadStat( & cygwin_load.Time100ns,
		      & cygwin_load.IdleCount);
  /* If success, set the Load to 0, else to -1 */
  if (success) cygwin_load.LastLoad = 0;
  else {
    log_write(0, LOG_MAIN, "Cannot obtain Load Average");
    cygwin_load.LastLoad = -1;
  }
  /* Free the buffer created for debug name verification */
  if (cygwin_load.NamesArray != NULL) {
    free(cygwin_load.NamesArray);
    cygwin_load.NamesArray = NULL;
  }
}
/*****************************************************************
 *
 os_getloadavg()

 Return -1 if not available;
 Return the previous value if less than AVERAGING sec old.
 else return the processor load on a [0 - 1000] scale.

 The first time we are called we initialize the counts
 and return 0 or -1.
 The load cannot be measured because we use the processor 100%
*****************************************************************/
#define AVERAGING 10
int os_getloadavg()
{
  long long Time100ns, IdleCount, CurrCounter;
  int value;

  if (! cygwin_load.Init) InitLoadAvg();
  else if (cygwin_load.LastLoad >= 0) { /* Initialized OK */
    /* Get the current time (PerfCounter) */
    QueryPerformanceCounter((LARGE_INTEGER *)& CurrCounter);
    /* Calls closer than AVERAGING sec apart use the previous value */
    if (CurrCounter - cygwin_load.LastCounter >
	AVERAGING * cygwin_load.PerfFreq) {
      /* Get Time100ns and IdleCount */
      if (ReadStat( & Time100ns, & IdleCount)) { /* Success */
	/* Return processor load on 1000 scale */
	value = 1000 - ((1000 * (IdleCount - cygwin_load.IdleCount)) /
			(Time100ns - cygwin_load.Time100ns));
	cygwin_load.Time100ns = Time100ns;
	cygwin_load.IdleCount = IdleCount;
	cygwin_load.LastCounter = CurrCounter;
	cygwin_load.LastLoad = value;
      }
      else { /* Something bad happened.
		Refuse to measure the load anymore
		but don't bother releasing the buffer */
        log_write(0, LOG_MAIN, "Cannot obtain Load Average");
	cygwin_load.LastLoad = -1;
      }
    }
  }
  DEBUG(D_load)
    debug_printf("Perf: load average = %d\n", cygwin_load.LastLoad);
  return cygwin_load.LastLoad;
}
#endif /* OS_LOAD_AVERAGE */
#endif /* COMPILE_UTILITY */
