/*
 * osutils.cxx
 *
 * Operating System utilities.
 *
 * Portable Windows Library
 *
 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): ______________________________________.
 *
 * $Log: osutils.cxx,v $
 * Revision 1.237  2005/12/04 22:43:30  csoutheren
 * Cleanup patches from Kilian Krause
 *
 * Revision 1.236  2005/11/30 12:47:42  csoutheren
 * Removed tabs, reformatted some code, and changed tags for Doxygen
 *
 * Revision 1.235  2005/11/09 09:11:39  csoutheren
 * Moved Windows-specific AttachThreadInput callsto seperate member function
 * on PThread. This removes a linearly increasing delay in creating new threads
 *
 * Revision 1.234  2005/10/22 04:50:23  csoutheren
 * Fixed hole in mutex locking of PTrace
 *
 * Revision 1.233  2005/08/30 06:36:39  csoutheren
 * Added ability to rotate output logs on a daily basis
 *
 * Revision 1.232  2005/03/19 02:52:55  csoutheren
 * Fix warnings from gcc 4.1-20050313 shapshot
 *
 * Revision 1.231  2005/01/31 08:05:40  csoutheren
 * More patches for MacOSX, thanks to Hannes Friederich
 *
 * Revision 1.230  2005/01/26 05:37:59  csoutheren
 * Added ability to remove config file support
 *
 * Revision 1.229  2005/01/04 07:44:03  csoutheren
 * More changes to implement the new configuration methodology, and also to
 * attack the global static problem
 *
 * Revision 1.228  2004/08/16 06:40:59  csoutheren
 * Added adapters template to make device plugins available via the abstract factory interface
 *
 * Revision 1.227  2004/06/30 12:17:06  rjongbloed
 * Rewrite of plug in system to use single global variable for all factories to avoid all sorts
 *   of issues with startup orders and Windows DLL multiple instances.
 *
 * Revision 1.226  2004/06/03 13:30:58  csoutheren
 * Renamed INSTANTIATE_FACTORY to avoid potential namespace collisions
 * Added documentaton on new PINSTANTIATE_FACTORY macro
 * Added generic form of PINSTANTIATE_FACTORY
 *
 * Revision 1.225  2004/06/03 12:47:59  csoutheren
 * Decomposed PFactory declarations to hopefully avoid problems with Windows DLLs
 *
 * Revision 1.224  2004/06/01 05:22:44  csoutheren
 * Restored memory check functionality
 *
 * Revision 1.223  2004/05/23 12:34:38  rjongbloed
 * Fixed PProcess startup up execution to after PProcess instance is created
 *   so does not crash if using startup technique to initialise tracing.
 *
 * Revision 1.222  2004/05/21 00:28:39  csoutheren
 * Moved PProcessStartup creation to PProcess::Initialise
 * Added PreShutdown function and called it from ~PProcess to handle PProcessStartup removal
 *
 * Revision 1.221  2004/05/18 21:49:25  csoutheren
 * Added ability to display trace output from program startup via environment
 * variable or by application creating a PProcessStartup descendant
 *
 * Revision 1.220  2004/05/18 12:43:31  csoutheren
 * Fixed compile problem on MSVC 6
 *
 * Revision 1.219  2004/05/18 06:01:06  csoutheren
 * Deferred plugin loading until after main has executed by using abstract factory classes
 *
 * Revision 1.218  2004/05/18 02:32:09  csoutheren
 * Fixed linking problems with PGenericFactory classes
 *
 * Revision 1.217  2004/05/13 15:05:43  csoutheren
 * Added <vector.h>
 *
 * Revision 1.216  2004/05/13 14:54:57  csoutheren
 * Implement PProcess startup and shutdown handling using abstract factory classes
 *
 * Revision 1.215  2004/04/24 06:27:56  rjongbloed
 * Fixed GCC 3.4.0 warnings about PAssertNULL and improved recoverability on
 *   NULL pointer usage in various bits of code.
 *
 * Revision 1.214  2004/04/12 07:33:46  csoutheren
 * Temporarily disabled removal of empty TRACE output on Linux
 *
 * Revision 1.213  2004/04/03 08:22:21  csoutheren
 * Remove pseudo-RTTI and replaced with real RTTI
 *
 * Revision 1.212  2004/04/03 06:54:29  rjongbloed
 * Many and various changes to support new Visual C++ 2003
 *
 * Revision 1.211  2004/03/22 10:15:27  rjongbloed
 * Added classes similar to PWaitAndSignal to automatically unlock a PReadWriteMutex
 *   when goes out of scope.
 *
 * Revision 1.210  2004/03/20 09:08:15  rjongbloed
 * Changed interaction between PTrace and PSystemLog so that the tracing code does
 *   not need to know about the system log, thus reducing the code footprint for most apps.
 *
 * Revision 1.209  2003/11/13 21:42:32  csoutheren
 * Fixed problem with thread name display under Windows thanks to Ted Szoczei
 *
 * Revision 1.208  2003/11/08 01:42:19  rjongbloed
 * Added thread names to DevStudio display, thanks Ted Szoczei
 *
 * Revision 1.207  2003/09/17 09:02:14  csoutheren
 * Removed memory leak detection code
 *
 * Revision 1.206  2003/09/17 01:18:03  csoutheren
 * Removed recursive include file system and removed all references
 * to deprecated coooperative threading support
 *
 * Revision 1.205  2003/07/24 22:01:42  dereksmithies
 * Add fixes from Peter Nixon  for fixing install problems. Thanks.
 *
 * Revision 1.204  2003/03/27 07:27:44  robertj
 * Added function to get a bunch of arguments as a string array.
 *
 * Revision 1.203  2003/01/24 10:21:06  robertj
 * Fixed issues in RTEMS support, thanks Vladimir Nesic
 *
 * Revision 1.202  2002/12/18 05:31:31  robertj
 * Moved PTimeInterval::GetInterval() to common code.
 *
 * Revision 1.201  2002/12/11 03:23:27  robertj
 * Fixed deadlock in read/write mutex, at price of not having seemless upgrading
 *   of read lock to write lock. There is now a window in which some other
 *   thread may gain write lock from the thread that was trying to upgrade.
 *
 * Revision 1.200  2002/12/10 02:39:07  robertj
 * Avoid odd trace output to stderr before trace file is set.
 *
 * Revision 1.199  2002/10/09 00:46:19  robertj
 * Changed PThread::Create() so does not return PThread pointer if the thread
 *   has been created auto-delete, the pointer is extremely dangerous to use
 *   as it could be deleted at any time, so to remove temptation ...
 *
 * Revision 1.198  2002/10/08 03:35:15  robertj
 * Fixed BSD warning
 *
 * Revision 1.197  2002/10/04 08:21:26  robertj
 * Changed read/write mutex so can be called by same thread without deadlock.
 *
 * Revision 1.196  2002/07/30 02:55:09  craigs
 * Added program start time to PProcess
 *
 * Revision 1.195  2002/06/27 06:38:58  robertj
 * Changes to remove memory leak display for things that aren't memory leaks.
 *
 * Revision 1.194  2002/06/15 02:16:36  robertj
 * Fixed bug (from rev 1.190) so can now use PTimer::Reset() after the timer
 *   had previously expired (resetTiem was being zeroed), thanks Ted Szoczei
 *
 * Revision 1.193  2002/06/05 12:29:15  craigs
 * Changes for gcc 3.1
 *
 * Revision 1.192  2002/06/04 00:25:31  robertj
 * Fixed incorrectly initialised trace indent, thanks Artis Kugevics
 *
 * Revision 1.191  2002/05/31 04:10:44  robertj
 * Fixed missing mutex in PTrace::SetStream, thanks Federico Pinna
 *
 * Revision 1.190  2002/05/28 13:05:26  robertj
 * Fixed PTimer::SetInterval so it restarts timer as per operator=()
 *
 * Revision 1.189  2002/05/22 00:42:03  craigs
 * Added GMTTime flag to tracing options
 *
 * Revision 1.188  2002/05/01 03:45:09  robertj
 * Added initialisation of PreadWriteMutex and changed slightly to agree
 *   with the text book definition of a semaphore for one of the mutexes.
 *
 * Revision 1.187  2002/04/30 06:21:38  robertj
 * Fixed PReadWriteMutex class to implement text book algorithm!
 *
 * Revision 1.186  2002/04/30 03:39:21  robertj
 * Changed PTimer::Stop() so does not return until timer is REALLY stopped, in
 *   particular when a possibly executing OnTimeout() function has completed.
 *
 * Revision 1.185  2002/04/24 01:19:07  robertj
 * Added milliseconds to PTRACE output timestamp
 *
 * Revision 1.184  2002/04/24 01:10:28  robertj
 * Fixed problem with PTRACE_BLOCK indent level being correct across threads.
 *
 * Revision 1.183  2002/04/19 00:43:17  craigs
 * Fixed problem with file modes
 *
 * Revision 1.182  2002/04/19 00:20:31  craigs
 * Added option to append to log file rather than create anew each time
 *
 * Revision 1.181  2002/02/14 05:14:51  robertj
 * Fixed possible deadlock if a timer is deleted (however indirectly) in the
 *   OnTimeout of another timer.
 *
 * Revision 1.180  2002/02/11 04:07:00  robertj
 * Fixed possibly race condition in PTRACE of first message. Consequence is
 *   that cannot PTRACE until have PProcess, ie before main() is executed.
 *
 * Revision 1.179  2002/01/31 08:14:16  robertj
 * Put back code taken out by GCC 3.0 patch. It really SHOULD be there!
 *
 * Revision 1.178  2002/01/26 23:57:45  craigs
 * Changed for GCC 3.0 compatibility, thanks to manty@manty.net
 *
 * Revision 1.177  2002/01/26 15:04:44  yurik
 * Fixed trace stream to a file
 *
 * Revision 1.176  2001/12/18 23:22:54  robertj
 * Fixed problem for if excecutable is "renamed" using unix exec() and the
 *   argv0 does not point to executable file.
 *
 * Revision 1.175  2001/12/15 04:49:17  robertj
 * Added stream I/O functions for argument list.
 *
 * Revision 1.174  2001/12/14 00:42:56  robertj
 * Fixed unix compatibility with trace of threads not created by pwlib.
 *
 * Revision 1.173  2001/12/13 09:21:43  robertj
 * Changed trace so shows thread id if current thread not created by PWLib.
 *
 * Revision 1.172  2001/11/30 04:19:26  robertj
 * Fixed correct setting of option bits in PTrace::Initialise()
 * Added date and time to first message in PTrace::Initialise()
 *
 * Revision 1.171  2001/11/14 06:06:26  robertj
 * Added functions on PTimer to get reset value and restart timer to it.
 *
 * Revision 1.170  2001/10/15 00:48:02  robertj
 * Fixed warning on later MSVC compilers.
 *
 * Revision 1.169  2001/09/03 08:08:31  robertj
 * Added tab so get extra "column" in trace output.
 *
 * Revision 1.168  2001/08/20 06:56:47  robertj
 * Fixed memory leak report which isn't a memory leak.
 *
 * Revision 1.167  2001/07/20 04:14:19  robertj
 * Removed GNU warning.
 *
 * Revision 1.166  2001/05/29 02:50:56  robertj
 * Fixed GNU compatibility
 *
 * Revision 1.165  2001/05/29 00:49:18  robertj
 * Added ability to put in a printf %x in thread name to get thread object
 *   address into user settable thread name.
 *
 * Revision 1.164  2001/05/03 06:26:22  robertj
 * Fixed strange problem that occassionally crashes on exit. Mutex cannot be
 *   destroyed before program exit.
 *
 * Revision 1.163  2001/04/27 01:05:26  yurik
 * Exit crash removal try
 *
 * Revision 1.162  2001/04/15 03:39:24  yurik
 * Removed shutdown flag. Use IsTerminated() instead
 *
 * Revision 1.161  2001/04/14 04:53:01  yurik
 * Got rid of init_seg pragma and added process shutdown flag
 *
 * Revision 1.160  2001/03/23 20:28:54  yurik
 * Got rid of pragma warning for WinCE port
 *
 * Revision 1.159  2001/03/09 05:50:48  robertj
 * Added ability to set default PConfig file or path to find it.
 *
 * Revision 1.158  2001/03/02 22:29:08  yurik
 * New pragma for WinCE related port which enables (de)construction of library static objects be before applications'
 * Eliminated nasty access violation stemmed from using static PMutex object in PTrace code. Thanks to Yuriy Gorvitovskiy.
 *
 * Revision 1.157  2001/02/22 22:31:44  robertj
 * Changed PProcess version display to show build number even if zero.
 *
 * Revision 1.156  2001/02/22 08:16:42  robertj
 * Added standard trace file setup subroutine.
 *
 * Revision 1.155  2001/02/19 03:44:20  robertj
 * Changed "pl" in version number string to a simple ".", Now like 1.1.19
 *
 * Revision 1.154  2001/01/28 00:53:00  yurik
 * WinCE port-related - streams refined
 *
 * Revision 1.153  2001/01/25 07:14:39  robertj
 * Fixed spurios memory leak message. Usual static global problem.
 *
 * Revision 1.152  2001/01/24 18:27:44  yurik
 * Added if !#defined WinCE when asm code used. Asm can't be used under WCE
 *
 * Revision 1.151  2001/01/02 07:47:44  robertj
 * Fixed very narrow race condition in timers (destroyed while in OnTimeout()).
 *
 * Revision 1.150  2000/12/21 12:37:03  craigs
 * Fixed deadlock problem with creating PTimer inside OnTimeout
 *
 * Revision 1.149  2000/11/28 12:55:37  robertj
 * Added static function to create a new thread class and automatically
 *   run a function on another class in the context of that thread.
 *
 * Revision 1.148  2000/10/20 05:31:32  robertj
 * Added function to change auto delete flag on a thread.
 *
 * Revision 1.147  2000/08/31 01:12:36  robertj
 * Fixed problem with no new lines in trace output to stderr.
 *
 * Revision 1.146  2000/08/30 05:56:07  robertj
 * Fixed free running timers broken by previous change.
 *
 * Revision 1.145  2000/08/30 03:17:00  robertj
 * Improved multithreaded reliability of the timers under stress.
 *
 * Revision 1.144  2000/06/26 11:17:20  robertj
 * Nucleus++ port (incomplete).
 *
 * Revision 1.143  2000/06/26 09:27:16  robertj
 * Added ability to get at the PTraceStream without timestamps etc, use UINT_MAX trace level.
 *
 * Revision 1.142  2000/06/02 01:38:07  craigs
 * Fixed typos
 *
 * Revision 1.141  2000/06/02 01:35:56  craigs
 * Added more guards for NULL PStrings in PConfigArg handling
 *
 * Revision 1.140  2000/05/25 14:45:07  robertj
 * Fixed detection of real argument over configured value.
 *
 * Revision 1.139  2000/05/25 13:47:51  robertj
 * Fixed warning with GNU.
 *
 * Revision 1.138  2000/05/25 11:05:55  robertj
 * Added PConfigArgs class so can save program arguments to config files.
 *
 * Revision 1.137  2000/05/05 10:08:29  robertj
 * Fixed some GNU compiler warnings
 *
 * Revision 1.136  2000/04/28 06:58:50  robertj
 * Fixed bug introduced when added Ashley Untts fix, forgot to take out old code!
 *
 * Revision 1.135  2000/04/27 04:19:27  robertj
 * Fixed bug in restarting free running timers, thanks Ashley Unitt.
 *
 * Revision 1.134  2000/04/03 18:42:40  robertj
 * Added function to determine if PProcess instance is initialised.
 *
 * Revision 1.133  2000/03/29 20:12:00  robertj
 * Fixed GNU C++ warning
 *
 * Revision 1.132  2000/03/29 01:55:52  robertj
 * Fixed infinite recursion on PProcess::Current() = NULL assertion.
 *
 * Revision 1.131  2000/03/02 05:43:12  robertj
 * Fixed handling of NULL pointer on current thread in PTRACE output.
 *
 * Revision 1.130  2000/02/29 12:26:14  robertj
 * Added named threads to tracing, thanks to Dave Harvey
 *
 * Revision 1.129  2000/02/17 11:34:28  robertj
 * Changed PTRACE output to help line up text after filename output.
 *
 * Revision 1.128  2000/01/06 14:09:42  robertj
 * Fixed problems with starting up timers,losing up to 10 seconds
 *
 * Revision 1.127  1999/10/19 09:21:30  robertj
 * Added functions to get current trace options and level.
 *
 * Revision 1.126  1999/10/14 08:08:27  robertj
 * Fixed problem, assuring millisecond accuracy in timestamp of trace output.
 *
 * Revision 1.125  1999/09/14 13:02:52  robertj
 * Fixed PTRACE to PSYSTEMLOG conversion problem under Unix.
 *
 * Revision 1.124  1999/09/13 13:15:07  robertj
 * Changed PTRACE so will output to system log in PServiceProcess applications.
 *
 * Revision 1.123  1999/08/22 12:54:35  robertj
 * Fixed warnings about inlines on older GNU compiler
 *
 * Revision 1.122  1999/06/23 14:19:46  robertj
 * Fixed core dump problem with SIGINT/SIGTERM terminating process.
 *
 * Revision 1.121  1999/06/14 07:59:38  robertj
 * Enhanced tracing again to add options to trace output (timestamps etc).
 *
 * Revision 1.120  1999/04/26 08:06:51  robertj
 * Added missing function in cooperative threading.
 *
 * Revision 1.119  1999/03/01 13:51:30  craigs
 * Fixed ugly little bug in the cooperative multithreading that meant that threads blocked
 * on timers didn't always get rescheduled.
 *
 * Revision 1.118  1999/02/23 10:13:31  robertj
 * Changed trace to only diplay filename and not whole path.
 *
 * Revision 1.117  1999/02/23 07:11:27  robertj
 * Improved trace facility adding trace levels and #define to remove all trace code.
 *
 * Revision 1.116  1998/11/30 12:45:54  robertj
 * Fissioned into pchannel.cxx and pconfig.cxx
 *
 * Revision 1.115  1998/11/24 01:17:33  robertj
 * Type discrepency between declaration and definition for PFile::SetPosition
 *
 * Revision 1.114  1998/11/06 02:37:53  robertj
 * Fixed the fix for semaphore timeout race condition.
 *
 * Revision 1.113  1998/11/03 10:52:19  robertj
 * Fixed bug in semaphores with timeout saying timed out when really signalled.
 *
 * Revision 1.112  1998/11/03 03:44:05  robertj
 * Fixed missng strings on multiple parameters of same letter.
 *
 * Revision 1.111  1998/11/02 10:13:01  robertj
 * Removed GNU warning.
 *
 * Revision 1.110  1998/11/01 04:56:53  robertj
 * Added BOOl return value to Parse() to indicate there are parameters available.
 *
 * Revision 1.109  1998/10/31 14:02:20  robertj
 * Removed StartImmediate capability as causes race condition in preemptive version.
 *
 * Revision 1.108  1998/10/31 12:47:10  robertj
 * Added conditional mutex and read/write mutex thread synchronisation objects.
 *
 * Revision 1.107  1998/10/30 12:24:15  robertj
 * Added ability to get all key values as a dictionary.
 * Fixed warnings in GNU C.
 *
 * Revision 1.106  1998/10/30 11:22:15  robertj
 * Added constructors that take strings as well as const char *'s.
 *
 * Revision 1.105  1998/10/30 05:25:09  robertj
 * Allow user to shift past some arguments before parsing for the first time.
 *
 * Revision 1.104  1998/10/29 05:35:17  robertj
 * Fixed porblem with GetCount() == 0 if do not call Parse() function.
 *
 * Revision 1.103  1998/10/28 03:26:43  robertj
 * Added multi character arguments (-abc style) and options precede parameters mode.
 *
 * Revision 1.102  1998/10/28 00:59:49  robertj
 * New improved argument parsing.
 *
 * Revision 1.101  1998/10/19 00:19:59  robertj
 * Moved error and trace stream functions to common code.
 *
 * Revision 1.100  1998/10/18 14:28:45  robertj
 * Renamed argv/argc to eliminate accidental usage.
 *
 * Revision 1.99  1998/10/13 14:06:28  robertj
 * Complete rewrite of memory leak detection code.
 *
 * Revision 1.98  1998/09/24 07:23:54  robertj
 * Moved structured fiel into separate module so don't need silly implementation file for GNU C.
 *
 * Revision 1.97  1998/09/23 06:22:24  robertj
 * Added open source copyright license.
 *
 * Revision 1.96  1998/06/13 15:11:56  robertj
 * Added stack check in Yield().
 * Added immediate schedule of semaphore timeout thread.
 *
 * Revision 1.95  1998/05/30 13:28:18  robertj
 * Changed memory check code so global statics are not included in leak check.
 * Fixed deadlock in cooperative threading.
 * Added PSyncPointAck class.
 *
 * Revision 1.94  1998/05/25 09:05:56  robertj
 * Fixed close of channels on destruction.
 *
 * Revision 1.93  1998/04/07 13:33:33  robertj
 * Changed startup code to support PApplication class.
 *
 * Revision 1.92  1998/03/29 06:16:45  robertj
 * Rearranged initialisation sequence so PProcess descendent constructors can do "things".
 *
 * Revision 1.91  1998/03/20 03:18:17  robertj
 * Added special classes for specific sepahores, PMutex and PSyncPoint.
 *
 * Revision 1.90  1998/02/05 13:33:12  robertj
 * Fixed close of non-autodelete PIndirectChannels
 *
 * Revision 1.89  1998/02/03 06:19:55  robertj
 * Added new function to read a block with minimum number of bytes.
 *
 * Revision 1.88  1998/01/26 00:47:13  robertj
 * Added functions to get/set 64bit integers from a PConfig.
 *
 * Revision 1.87  1998/01/04 07:22:16  robertj
 * Fixed bug in thread deletion not removing it from active thread list.
 *
 * Revision 1.86  1997/10/10 10:41:22  robertj
 * Fixed problem with cooperative threading and Sleep() function returning immediately.
 *
 * Revision 1.85  1997/08/28 12:49:00  robertj
 * Fixed possible assert on exit of application.
 *
 * Revision 1.84  1997/07/08 13:08:12  robertj
 * DLL support.
 *
 * Revision 1.83  1997/04/27 05:50:15  robertj
 * DLL support.
 *
 * Revision 1.82  1997/02/09 04:05:56  robertj
 * Changed PProcess::Current() from pointer to reference.
 *
 * Revision 1.81  1997/02/05 11:51:42  robertj
 * Changed current process function to return reference and validate objects descendancy.
 *
 * Revision 1.80  1996/12/21 05:54:38  robertj
 * Fixed possible deadlock in timers.
 *
 * Revision 1.79  1996/12/05 11:44:22  craigs
 * Made indirect close from different thread less likely to have
 * race condition
 *
 * Revision 1.78  1996/11/30 12:08:42  robertj
 * Removed extraneous compiler warning.
 *
 * Revision 1.77  1996/11/10 21:05:43  robertj
 * Fixed bug of missing flush in close of indirect channel.
 *
 * Revision 1.76  1996/10/08 13:07:07  robertj
 * Fixed bug in indirect channel being reopened double deleting subchannel.
 *
 * Revision 1.75  1996/09/14 13:09:37  robertj
 * Major upgrade:
 *   rearranged sockets to help support IPX.
 *   added indirect channel class and moved all protocols to descend from it,
 *   separating the protocol from the low level byte transport.
 *
 * Revision 1.74  1996/08/11 06:53:04  robertj
 * Fixed bug in Sleep() function (nonpreemptive version).
 *
 * Revision 1.73  1996/07/27 04:12:09  robertj
 * Fixed bug in timer thread going into busy loop instead of blocking.
 *
 * Revision 1.72  1996/07/15 10:36:12  robertj
 * Fixed bug in timer on startup, getting LARGE times timing out prematurely.
 *
 * Revision 1.71  1996/06/28 13:22:43  robertj
 * Rewrite of timers to make OnTimeout more thread safe.
 *
 * Revision 1.70  1996/06/13 13:31:05  robertj
 * Rewrite of auto-delete threads, fixes Windows95 total crash.
 *
 * Revision 1.69  1996/06/03 10:01:31  robertj
 * Fixed GNU support bug fix for the fix.
 *
 * Revision 1.68  1996/06/01 05:03:37  robertj
 * Fixed GNU compiler having difficulty with PTimeInterval *this.
 *
 * Revision 1.67  1996/05/26 03:46:56  robertj
 * Compatibility to GNU 2.7.x
 *
 * Revision 1.66  1996/05/23 23:05:07  robertj
 * Fixed process filename on MSOS platforms.
 *
 * Revision 1.65  1996/05/23 09:56:57  robertj
 * Added mutex to timer list.
 *
 * Revision 1.64  1996/05/18 09:18:33  robertj
 * Added mutex to timer list.
 *
 * Revision 1.63  1996/05/09 12:19:00  robertj
 * Resolved C++ problems with 64 bit PTimeInterval for Mac platform.
 *
 * Revision 1.62  1996/04/14 02:53:34  robertj
 * Split serial and pipe channel into separate compilation units for Linux executable size reduction.
 *
 * Revision 1.61  1996/04/10 12:51:29  robertj
 * Fixed startup race condtion in timer thread.
 *
 * Revision 1.60  1996/04/09 03:32:58  robertj
 * Fixed bug in config GetTime() cannot use PTime(0) in western hemisphere.
 *
 * Revision 1.59  1996/04/02 11:29:19  robertj
 * Eliminated printing of patch level in version when there isn't one.
 *
 * Revision 1.58  1996/03/31 09:06:14  robertj
 * Fixed WriteString() so works with sockets.
 * Changed PPipeSokcet argument string list to array.
 *
 * Revision 1.57  1996/03/16 04:51:50  robertj
 * Fixed yet another bug in the scheduler.
 *
 * Revision 1.56  1996/03/12 11:30:50  robertj
 * Moved PProcess destructor to platform dependent code.
 *
 * Revision 1.55  1996/03/05 14:05:51  robertj
 * Fixed some more bugs in scheduling.
 *
 * Revision 1.54  1996/03/04 12:22:46  robertj
 * Fixed threading for unix stack check and loop list start point.
 *
 * Revision 1.53  1996/03/03 07:39:51  robertj
 * Fixed bug in thread scheduler for correct termination of "current" thread.
 *
 * Revision 1.52  1996/03/02 03:24:48  robertj
 * Changed timer thread to update timers periodically, this allows timers to be
 *    views dynamically by other threads.
 * Added automatic deletion of thread object instances on thread completion.
 *
 * Revision 1.51  1996/02/25 11:15:27  robertj
 * Added platform dependent Construct function to PProcess.
 *
 * Revision 1.50  1996/02/25 03:09:46  robertj
 * Added consts to all GetXxxx functions in PConfig.
 *
 * Revision 1.49  1996/02/15 14:44:09  robertj
 * Used string constructor for PTime, more "efficient".
 *
 * Revision 1.48  1996/02/13 12:59:30  robertj
 * Changed GetTimeZone() so can specify standard/daylight time.
 * Split PTime into separate module after major change to ReadFrom().
 *
 * Revision 1.47  1996/02/08 12:26:55  robertj
 * Changed time for full support of time zones.
 *
 * Revision 1.46  1996/02/03 11:06:49  robertj
 * Added string constructor for times, parses date/time from string.
 *
 * Revision 1.45  1996/01/28 14:09:39  robertj
 * Fixed bug in time reading function for dates before 1980.
 * Fixed bug in time reading, was out by one month.
 * Added time functions to PConfig.
 *
 * Revision 1.44  1996/01/28 02:52:04  robertj
 * Added assert into all Compare functions to assure comparison between compatible objects.
 *
 * Revision 1.43  1996/01/23 13:16:30  robertj
 * Mac Metrowerks compiler support.
 * Fixed timers so background thread not created if a windows app.
 *
 * Revision 1.42  1996/01/03 23:15:39  robertj
 * Fixed some PTime bugs.
 *
 * Revision 1.41  1996/01/03 11:09:35  robertj
 * Added Universal Time and Time Zones to PTime class.
 *
 * Revision 1.39  1995/12/23 03:40:40  robertj
 * Changed version number system
 *
 * Revision 1.38  1995/12/10 11:41:12  robertj
 * Added extra user information to processes and applications.
 * Implemented timer support in text only applications with platform threads.
 * Fixed bug in non-platform threads and semaphore timeouts.
 *
 * Revision 1.37  1995/11/21 11:50:57  robertj
 * Added timeout on semaphore wait.
 *
 * Revision 1.36  1995/11/09 12:22:58  robertj
 * Fixed bug in stream when reading an FF (get EOF).
 *
 * Revision 1.35  1995/07/31 12:09:25  robertj
 * Added semaphore class.
 * Removed PContainer from PChannel ancestor.
 *
 * Revision 1.34  1995/06/04 12:41:08  robertj
 * Fixed bug in accessing argument strings with no argument.
 *
 * Revision 1.33  1995/04/25 11:30:06  robertj
 * Fixed Borland compiler warnings.
 *
 * Revision 1.32  1995/04/22 00:51:00  robertj
 * Changed file path strings to use PFilePath object.
 * Changed semantics of Rename().
 *
 * Revision 1.31  1995/04/02 09:27:31  robertj
 * Added "balloon" help.
 *
 * Revision 1.30  1995/04/01 08:30:58  robertj
 * Fixed bug in timeout code of timers.
 *
 * Revision 1.29  1995/01/27 11:15:17  robertj
 * Removed enum to int warning from GCC.
 *
 * Revision 1.28  1995/01/18  09:02:43  robertj
 * Added notifier to timer.
 *
 * Revision 1.27  1995/01/15  04:57:15  robertj
 * Implemented PTime::ReadFrom.
 * Fixed flush of iostream at end of file.
 *
 * Revision 1.26  1995/01/11  09:45:14  robertj
 * Documentation and normalisation.
 *
 * Revision 1.25  1995/01/10  11:44:15  robertj
 * Removed PString parameter in stdarg function for GNU C++ compatibility.
 *
 * Revision 1.24  1995/01/09  12:31:51  robertj
 * Removed unnecesary return value from I/O functions.
 *
 * Revision 1.23  1994/12/12  10:09:24  robertj
 * Fixed flotain point configuration variable format.
 *
 * Revision 1.22  1994/11/28  12:38:23  robertj
 * Async write functions should have const pointer.
 *
 * Revision 1.21  1994/10/30  11:36:58  robertj
 * Fixed missing space in tine format string.
 *
 * Revision 1.20  1994/10/23  03:46:41  robertj
 * Shortened OS error assert.
 *
 * Revision 1.19  1994/09/25  10:51:04  robertj
 * Fixed error conversion code to use common function.
 * Added pipe channel.
 *
 * Revision 1.18  1994/08/21  23:43:02  robertj
 * Moved meta-string transmitter from PModem to PChannel.
 * Added SuspendBlock state to cooperative multi-threading to fix logic fault.
 * Added "force" option to Remove/Rename etc to override write protection.
 * Added common entry point to convert OS error to PChannel error.
 *
 * Revision 1.17  1994/08/04  12:57:10  robertj
 * Changed CheckBlock() to better name.
 * Moved timer porcessing so is done at every Yield().
 *
 * Revision 1.16  1994/08/01  03:39:42  robertj
 * Fixed temporary variable problem with GNU C++
 *
 * Revision 1.15  1994/07/27  05:58:07  robertj
 * Synchronisation.
 *
 * Revision 1.14  1994/07/25  03:39:22  robertj
 * Fixed problems with C++ temporary variables.
 *
 * Revision 1.13  1994/07/21  12:33:49  robertj
 * Moved cooperative threads to common.
 *
 * Revision 1.12  1994/07/17  10:46:06  robertj
 * Fixed timer bug.
 * Moved handle from file to channel.
 * Changed args to use container classes.
 *
 * Revision 1.11  1994/07/02  03:03:49  robertj
 * Time interval and timer redesign.
 *
 * Revision 1.10  1994/06/25  11:55:15  robertj
 * Unix version synchronisation.
 *
 * Revision 1.9  1994/04/20  12:17:44  robertj
 * assert changes
 *
 * Revision 1.8  1994/04/01  14:05:06  robertj
 * Text file streams
 *
 * Revision 1.7  1994/03/07  07:47:00  robertj
 * Major upgrade
 *
 * Revision 1.6  1994/01/03  04:42:23  robertj
 * Mass changes to common container classes and interactors etc etc etc.
 *
 * Revision 1.5  1993/12/31  06:53:02  robertj
 * Made inlines optional for debugging purposes.
 *
 * Revision 1.4  1993/12/29  04:41:26  robertj
 * Mac port.
 *
 * Revision 1.3  1993/11/20  17:26:28  robertj
 * Removed separate osutil.h
 *
 * Revision 1.2  1993/08/31  03:38:02  robertj
 * G++ needs explicit casts for char * / void * interchange.
 *
 * Revision 1.1  1993/08/27  18:17:47  robertj
 * Initial revision
 *
 */

#include <ptlib.h>
#include <vector>

#include <ctype.h>

#ifdef __MACOSX__
namespace PWLibStupidOSXHacks {
  extern int loadShmVideoStuff;
  extern int loadCoreAudioStuff;
  extern int loadFakeVideoStuff;
};
#endif

class PSimpleThread : public PThread
{
    PCLASSINFO(PSimpleThread, PThread);
  public:
    PSimpleThread(
      const PNotifier & notifier,
      INT parameter,
      AutoDeleteFlag deletion,
      Priority priorityLevel,
      const PString & threadName,
      PINDEX stackSize
    );
    void Main();
  protected:
    PNotifier callback;
    INT parameter;
};

static PMutex * PTraceMutex = NULL;

#ifndef __NUCLEUS_PLUS__
static ostream * PErrorStream = &cerr;
#else
static ostream * PErrorStream = NULL;
#endif

ostream & PGetErrorStream()
{
  return *PErrorStream;
}


void PSetErrorStream(ostream * s)
{
#ifndef __NUCLEUS_PLUS__
  PErrorStream = s != NULL ? s : &cerr;
#else
  PErrorStream = s;
#endif
}

//////////////////////////////////////////////////////////////////////////////

#if !defined(__NUCLEUS_PLUS__) 
static ostream * PTraceStream = &cerr;
#else

#ifdef __NUCLEUS_PLUS__ 
static ostream * PTraceStream = 0L;
#endif

#endif

static unsigned PTraceOptions = PTrace::FileAndLine;
static unsigned PTraceLevelThreshold = 0;
static PTimeInterval ApplicationStartTick = PTimer::Tick();
unsigned PTraceCurrentLevel;
static const char * PTrace_Filename = NULL;
static int PTrace_lastDayOfYear = 0;

void PTrace::SetStream(ostream * s)
{
#ifndef __NUCLEUS_PLUS__
  if (s == NULL)
    s = &cerr;
#endif

  if (PTraceMutex == NULL)
    PTraceStream = s;
  else {
    PWaitAndSignal m(*PTraceMutex);
    PTraceStream = s;
  }
}

static void OpenTraceFile()
{
  PFilePath fn(PTrace_Filename);

  if ((PTraceOptions & PTrace::RotateDaily) != 0)
    fn = PFilePath(fn.GetDirectory() + (fn.GetTitle() + PTime((PTraceOptions&PTrace::GMTTime) ? PTime::GMT : PTime::Local).AsString("yyyy_MM_dd") + fn.GetType()));

  PTextFile * traceOutput;
  if (PTraceOptions & PTrace::AppendToFile) {
    traceOutput = new PTextFile(fn, PFile::ReadWrite);
    traceOutput->SetPosition(0, PFile::End);
  } else 
    traceOutput = new PTextFile(fn, PFile::WriteOnly);

  if (traceOutput->IsOpen())
    PTrace::SetStream(traceOutput);
  else {
    PTRACE(0, PProcess::Current().GetName() << "Could not open trace output file \"" << fn << '"');
    delete traceOutput;
  }
}

void PTrace::Initialise(unsigned level, const char * filename, unsigned options)
{
  // If we have a tracing version, then open trace file and set modes
#if PTRACING
  PProcess & process = PProcess::Current();
#endif

#if PMEMORY_CHECK
  int ignoreAllocations = -1;
#endif

  PTrace_Filename = filename;
  PTraceOptions = options;

  if (options & RotateDaily)
    PTrace_lastDayOfYear = PTime((PTraceOptions&GMTTime) ? PTime::GMT : PTime::Local).GetDayOfYear();
  else
    PTrace_lastDayOfYear = 0;

  if (filename != NULL) {
#if PMEMORY_CHECK
    ignoreAllocations = PMemoryHeap::SetIgnoreAllocations(TRUE) ? 1 : 0;
#endif
    OpenTraceFile();
  }

  PTraceLevelThreshold = level;

  PTRACE(1, process.GetName()
         << "\tVersion " << process.GetVersion(TRUE)
         << " by " << process.GetManufacturer()
         << " on " << process.GetOSClass() << ' ' << process.GetOSName()
         << " (" << process.GetOSVersion() << '-' << process.GetOSHardware()
         << ") at " << PTime().AsString("yyyy/M/d h:mm:ss.uuu"));

#if PMEMORY_CHECK
  if (ignoreAllocations >= 0)
    PMemoryHeap::SetIgnoreAllocations(ignoreAllocations != 0);
#endif
}


void PTrace::SetOptions(unsigned options)
{
  PTraceOptions |= options;
}


void PTrace::ClearOptions(unsigned options)
{
  PTraceOptions &= ~options;
}


unsigned PTrace::GetOptions()
{
  return PTraceOptions;
}


void PTrace::SetLevel(unsigned level)
{
  PTraceLevelThreshold = level;
}


unsigned PTrace::GetLevel()
{
  return PTraceLevelThreshold;
}


BOOL PTrace::CanTrace(unsigned level)
{
  return level <= PTraceLevelThreshold;
}


ostream & PTrace::Begin(unsigned level, const char * fileName, int lineNum)
{
  if (PTraceMutex == NULL) {
    PAssertAlways("Cannot use PTRACE before PProcess constructed.");
    return *PTraceStream;
  }

  if (level == UINT_MAX)
    return *PTraceStream;

  PTraceMutex->Wait();

  // Save log level for this message so End() function can use. This is
  // protected by the PTraceMutex
  PTraceCurrentLevel = level;

  if ((PTrace_Filename != NULL) && (PTraceOptions&RotateDaily) != 0) {
    int day = PTime((PTraceOptions&GMTTime) ? PTime::GMT : PTime::Local).GetDayOfYear();
    if (day != PTrace_lastDayOfYear) {
      delete PTraceStream;
      PTraceStream = NULL;
      OpenTraceFile();
      if (PTraceStream == NULL) {
        PTraceMutex->Signal();
        return *PTraceStream;
      }
    }
  }

  if ((PTraceOptions&SystemLogStream) == 0) {
    if ((PTraceOptions&DateAndTime) != 0) {
      PTime now;
      *PTraceStream << now.AsString("yyyy/MM/dd hh:mm:ss.uuu\t", (PTraceOptions&GMTTime) ? PTime::GMT : PTime::Local);
    }

    if ((PTraceOptions&Timestamp) != 0)
      *PTraceStream << setprecision(3) << setw(10) << (PTimer::Tick()-ApplicationStartTick) << '\t';

    if ((PTraceOptions&Thread) != 0) {
      PThread * thread = PThread::Current();
      if (thread == NULL)
        *PTraceStream << "ThreadID=0x"
                      << setfill('0') << hex << setw(8)
                      << PThread::GetCurrentThreadId()
                      << setfill(' ') << dec;
      else {
        PString name = thread->GetThreadName();
        if (name.GetLength() <= 23)
          *PTraceStream << setw(23) << name;
        else
          *PTraceStream << name.Left(10) << "..." << name.Right(10);
      }
      *PTraceStream << '\t';
    }

    if ((PTraceOptions&ThreadAddress) != 0)
      *PTraceStream << hex << setfill('0')
                    << setw(7) << (void *)PThread::Current()
                    << dec << setfill(' ') << '\t';
  }

  if ((PTraceOptions&TraceLevel) != 0)
    *PTraceStream << level << '\t';

  if ((PTraceOptions&FileAndLine) != 0 && fileName != NULL) {
    const char * file = strrchr(fileName, '/');
    if (file != NULL)
      file++;
    else {
      file = strrchr(fileName, '\\');
      if (file != NULL)
        file++;
      else
        file = fileName;
    }

    *PTraceStream << setw(16) << file << '(' << lineNum << ")\t";
  }

  return *PTraceStream;
}


ostream & PTrace::End(ostream & s)
{
  /* Only output if there is something to output, this prevents some blank trace
     entries from appearing under some patholgical conditions. Unfortunately if
     stderr is used the unitbuf flag causes the out_waiting() not to work so we 
     must suffer with blank lines in that case.
   */
#if 0
#ifndef P_LINUX
  ::streambuf & rb = *s.rdbuf();
  if (((s.flags()&ios::unitbuf) != 0) ||
#ifdef __USE_STL__
          rb.pubseekoff(0, ios::cur, ios::out) > 0
#else
          rb.out_waiting() > 0
#endif
      )
#endif
#endif
    {
    if ((PTraceOptions&SystemLogStream) != 0) {
      // Get the trace level for this message and set the stream width to that
      // level so that the PSystemLog can extract the log level back out of the
      // ios structure. There could be portability issues with this though it
      // should work pretty universally.
      s.width(PTraceCurrentLevel+1);
      s.flush();
    }
    else
      s << endl;
  }

  PTraceMutex->Signal();

  return s;
}


PTrace::Block::Block(const char * fileName, int lineNum, const char * traceName)
{
  file = fileName;
  line = lineNum;
  name = traceName;

  if ((PTraceOptions&Blocks) != 0) {
    PThread * thread = PThread::Current();
    thread->traceBlockIndentLevel += 2;

    ostream & s = PTrace::Begin(1, file, line);
    s << "B-Entry\t";
    for (unsigned i = 0; i < thread->traceBlockIndentLevel; i++)
      s << '=';
    s << "> " << name << PTrace::End;
  }
}


PTrace::Block::~Block()
{
  if ((PTraceOptions&Blocks) != 0) {
    PThread * thread = PThread::Current();

    ostream & s = PTrace::Begin(1, file, line);
    s << "B-Exit\t<";
    for (unsigned i = 0; i < thread->traceBlockIndentLevel; i++)
      s << '=';
    s << ' ' << name << PTrace::End;

    thread->traceBlockIndentLevel -= 2;
  }
}


///////////////////////////////////////////////////////////////////////////////
// PDirectory

void PDirectory::CloneContents(const PDirectory * d)
{
  CopyContents(*d);
}


///////////////////////////////////////////////////////////////////////////////
// PTimeInterval

DWORD PTimeInterval::GetInterval() const
{
  if (milliseconds <= 0)
    return 0;

  if (milliseconds >= UINT_MAX)
    return UINT_MAX;

  return (DWORD)milliseconds;
}


///////////////////////////////////////////////////////////////////////////////
// PTimer

PTimer::PTimer(long millisecs, int seconds, int minutes, int hours, int days)
  : resetTime(millisecs, seconds, minutes, hours, days)
{
  Construct();
}


PTimer::PTimer(const PTimeInterval & time)
  : resetTime(time)
{
  Construct();
}


void PTimer::Construct()
{
  state = Stopped;

  timerList = PProcess::Current().GetTimerList();

  timerList->listMutex.Wait();
  timerList->Append(this);
  timerList->listMutex.Signal();

  timerList->processingMutex.Wait();
  StartRunning(TRUE);
}


PTimer & PTimer::operator=(DWORD milliseconds)
{
  timerList->processingMutex.Wait();
  resetTime.SetInterval(milliseconds);
  StartRunning(oneshot);
  return *this;
}


PTimer & PTimer::operator=(const PTimeInterval & time)
{
  timerList->processingMutex.Wait();
  resetTime = time;
  StartRunning(oneshot);
  return *this;
}


PTimer::~PTimer()
{
  timerList->listMutex.Wait();
  timerList->Remove(this);
  BOOL isCurrentTimer = this == timerList->currentTimer;
  timerList->listMutex.Signal();

  // Make sure that the OnTimeout for this timer has completed before
  // destroying the timer
  if (isCurrentTimer) {
    timerList->inTimeoutMutex.Wait();
    timerList->inTimeoutMutex.Signal();
  }
}


void PTimer::SetInterval(PInt64 milliseconds,
                         long seconds,
                         long minutes,
                         long hours,
                         int days)
{
  timerList->processingMutex.Wait();
  resetTime.SetInterval(milliseconds, seconds, minutes, hours, days);
  StartRunning(oneshot);
}


void PTimer::RunContinuous(const PTimeInterval & time)
{
  timerList->processingMutex.Wait();
  resetTime = time;
  StartRunning(FALSE);
}


void PTimer::StartRunning(BOOL once)
{
  PTimeInterval::operator=(resetTime);
  oneshot = once;
  state = (*this) != 0 ? Starting : Stopped;

  if (IsRunning())
    PProcess::Current().SignalTimerChange();

  // This must have been set by the caller
  timerList->processingMutex.Signal();
}


void PTimer::Stop()
{
  timerList->processingMutex.Wait();
  state = Stopped;
  milliseconds = 0;
  BOOL isCurrentTimer = this == timerList->currentTimer;
  timerList->processingMutex.Signal();

  // Make sure that the OnTimeout for this timer has completed before
  // retruning from Stop() function,
  if (isCurrentTimer) {
    timerList->inTimeoutMutex.Wait();
    timerList->inTimeoutMutex.Signal();
  }
}


void PTimer::Pause()
{
  timerList->processingMutex.Wait();
  if (IsRunning())
    state = Paused;
  timerList->processingMutex.Signal();
}


void PTimer::Resume()
{
  timerList->processingMutex.Wait();
  if (state == Paused)
    state = Starting;
  timerList->processingMutex.Signal();
}


void PTimer::Reset()
{
  timerList->processingMutex.Wait();
  StartRunning(oneshot);
}


void PTimer::OnTimeout()
{
  if (!callback.IsNULL())
    callback(*this, IsRunning());
}


void PTimer::Process(const PTimeInterval & delta, PTimeInterval & minTimeLeft)
{
  /*Ideally there should be a processingMutex for each individual timer, but
    that seems incredibly profligate of system resources as there  can be a
    LOT of PTimer instances about. So use one one mutex for all.
   */
  timerList->processingMutex.Wait();

  switch (state) {
    case Starting :
      state = Running;
      if (resetTime < minTimeLeft)
        minTimeLeft = resetTime;
      break;

    case Running :
      operator-=(delta);

      if (milliseconds > 0) {
        if (milliseconds < minTimeLeft.GetMilliSeconds())
          minTimeLeft = *this;
      }
      else {
        if (oneshot) {
          milliseconds = 0;
          state = Stopped;
        }
        else {
          PTimeInterval::operator=(resetTime);
          if (resetTime < minTimeLeft)
            minTimeLeft = resetTime;
        }

        timerList->processingMutex.Signal();

        /* This must be outside the mutex or if OnTimeout() changes the
           timer value (quite plausible) it deadlocks.
         */
        OnTimeout();
        return;
      }
      break;

    default : // Stopped or Paused, do nothing.
      break;
  }

  timerList->processingMutex.Signal();
}


///////////////////////////////////////////////////////////////////////////////
// PTimerList

PTimerList::PTimerList()
{
  DisallowDeleteObjects();
  currentTimer = NULL;
}


PTimeInterval PTimerList::Process()
{
  PINDEX i;
  PTimeInterval minTimeLeft = PMaxTimeInterval;

  listMutex.Wait();

  PTimeInterval now = PTimer::Tick();
  PTimeInterval sampleTime;
  if (lastSample == 0)
    sampleTime = 0;
  else {
    sampleTime = now - lastSample;
    if (now < lastSample)
      sampleTime += PMaxTimeInterval;
  }
  lastSample = now;

  for (i = 0; i < GetSize(); i++) {
    currentTimer = (PTimer *)GetAt(i);
    inTimeoutMutex.Wait();
    listMutex.Signal();
    currentTimer->Process(sampleTime, minTimeLeft);
    listMutex.Wait();
    inTimeoutMutex.Signal();
  }
  currentTimer = NULL;
  
  listMutex.Signal();

  return minTimeLeft;
}


///////////////////////////////////////////////////////////////////////////////
// PArgList

PArgList::PArgList(const char * theArgStr,
                   const char * theArgumentSpec,
                   BOOL optionsBeforeParams)
{
  // get the program arguments
  if (theArgStr != NULL)
    SetArgs(theArgStr);

  // if we got an argument spec - so process them
  if (theArgumentSpec != NULL)
    Parse(theArgumentSpec, optionsBeforeParams);
}


PArgList::PArgList(const PString & theArgStr,
                   const char * argumentSpecPtr,
                   BOOL optionsBeforeParams)
{
  // get the program arguments
  SetArgs(theArgStr);

  // if we got an argument spec - so process them
  if (argumentSpecPtr != NULL)
    Parse(argumentSpecPtr, optionsBeforeParams);
}


PArgList::PArgList(const PString & theArgStr,
                   const PString & argumentSpecStr,
                   BOOL optionsBeforeParams)
{
  // get the program arguments
  SetArgs(theArgStr);

  // if we got an argument spec - so process them
  Parse(argumentSpecStr, optionsBeforeParams);
}


PArgList::PArgList(int theArgc, char ** theArgv,
                   const char * theArgumentSpec,
                   BOOL optionsBeforeParams)
{
  // get the program arguments
  SetArgs(theArgc, theArgv);

  // if we got an argument spec - so process them
  if (theArgumentSpec != NULL)
    Parse(theArgumentSpec, optionsBeforeParams);
}


PArgList::PArgList(int theArgc, char ** theArgv,
                   const PString & theArgumentSpec,
                   BOOL optionsBeforeParams)
{
  // get the program name and path
  SetArgs(theArgc, theArgv);
  // we got an argument spec - so process them
  Parse(theArgumentSpec, optionsBeforeParams);
}


void PArgList::PrintOn(ostream & strm) const
{
  for (PINDEX i = 0; i < argumentArray.GetSize(); i++) {
    if (i > 0)
      strm << strm.fill();
    strm << argumentArray[i];
  }
}


void PArgList::ReadFrom(istream & strm)
{
  PString line;
  strm >> line;
  SetArgs(line);
}


void PArgList::SetArgs(const PString & argStr)
{
  argumentArray.SetSize(0);

  const char * str = argStr;

  for (;;) {
    while (isspace(*str)) // Skip leading whitespace
      str++;
    if (*str == '\0')
      break;

    PString & arg = argumentArray[argumentArray.GetSize()];
    while (*str != '\0' && !isspace(*str)) {
      switch (*str) {
        case '"' :
          str++;
          while (*str != '\0' && *str != '"')
            arg += *str++;
          if (*str != '\0')
            str++;
          break;

        case '\'' :
          str++;
          while (*str != '\0' && *str != '\'')
            arg += *str++;
          if (*str != '\0')
            str++;
          break;

        default :
          if (str[0] == '\\' && str[1] != '\0')
            str++;
          arg += *str++;
      }
    }
  }

  SetArgs(argumentArray);
}


void PArgList::SetArgs(const PStringArray & theArgs)
{
  argumentArray = theArgs;
  shift = 0;
  optionLetters = "";
  optionNames.SetSize(0);
  parameterIndex.SetSize(argumentArray.GetSize());
  for (PINDEX i = 0; i < argumentArray.GetSize(); i++)
    parameterIndex[i] = i;
}


BOOL PArgList::Parse(const char * spec, BOOL optionsBeforeParams)
{
  if (PAssertNULL(spec) == NULL)
    return FALSE;

  // Find starting point, start at shift if first Parse() call.
  PINDEX arg = optionLetters.IsEmpty() ? shift : 0;

  // If not in parse all mode, have been parsed before, and had some parameters
  // from last time, then start argument parsing somewhere along instead of start.
  if (optionsBeforeParams && !optionLetters && parameterIndex.GetSize() > 0)
    arg = parameterIndex[parameterIndex.GetSize()-1] + 1;

  // Parse the option specification
  optionLetters = "";
  optionNames.SetSize(0);
  PIntArray canHaveOptionString;

  PINDEX codeCount = 0;
  while (*spec != '\0') {
    if (*spec == '-')
      optionLetters += ' ';
    else
      optionLetters += *spec++;
    if (*spec == '-') {
      const char * base = ++spec;
      while (*spec != '\0' && *spec != '.' && *spec != ':' && *spec != ';')
        spec++;
      optionNames[codeCount] = PString(base, spec-base);
      if (*spec == '.')
        spec++;
    }
    if (*spec == ':' || *spec == ';') {
      canHaveOptionString.SetSize(codeCount+1);
      canHaveOptionString[codeCount] = *spec == ':' ? 2 : 1;
      spec++;
    }
    codeCount++;
  }

  // Clear and reset size of option information
  optionCount.SetSize(0);
  optionCount.SetSize(codeCount);
  optionString.SetSize(0);
  optionString.SetSize(codeCount);

  // Clear parameter indexes
  parameterIndex.SetSize(0);
  shift = 0;

  // Now work through the arguments and split out the options
  PINDEX param = 0;
  BOOL hadMinusMinus = FALSE;
  while (arg < argumentArray.GetSize()) {
    const PString & argStr = argumentArray[arg];
    if (hadMinusMinus || argStr[0] != '-' || argStr[1] == '\0') {
      // have a parameter string
      parameterIndex.SetSize(param+1);
      parameterIndex[param++] = arg;
    }
    else if (optionsBeforeParams && parameterIndex.GetSize() > 0)
      break;
    else if (argStr == "--") // ALL remaining args are parameters not options
      hadMinusMinus = TRUE;
    else if (argStr[1] == '-')
      ParseOption(optionNames.GetValuesIndex(argStr.Mid(2)), 0, arg, canHaveOptionString);
    else {
      for (PINDEX i = 1; i < argStr.GetLength(); i++)
        if (ParseOption(optionLetters.Find(argStr[i]), i+1, arg, canHaveOptionString))
          break;
    }

    arg++;
  }

  return param > 0;
}


BOOL PArgList::ParseOption(PINDEX idx, PINDEX offset, PINDEX & arg,
                           const PIntArray & canHaveOptionString)
{
  if (idx == P_MAX_INDEX) {
    UnknownOption(argumentArray[arg]);
    return FALSE;
  }

  optionCount[idx]++;
  if (canHaveOptionString[idx] == 0)
    return FALSE;

  if (!optionString[idx])
    optionString[idx] += '\n';

  if (offset != 0 &&
        (canHaveOptionString[idx] == 1 || argumentArray[arg][offset] != '\0')) {
    optionString[idx] += argumentArray[arg].Mid(offset);
    return TRUE;
  }

  if (++arg >= argumentArray.GetSize())
    return FALSE;

  optionString[idx] += argumentArray[arg];
  return TRUE;
}


PINDEX PArgList::GetOptionCount(char option) const
{
  return GetOptionCountByIndex(optionLetters.Find(option));
}


PINDEX PArgList::GetOptionCount(const char * option) const
{
  return GetOptionCountByIndex(optionNames.GetValuesIndex(PString(option)));
}


PINDEX PArgList::GetOptionCount(const PString & option) const
{
  return GetOptionCountByIndex(optionNames.GetValuesIndex(option));
}


PINDEX PArgList::GetOptionCountByIndex(PINDEX idx) const
{
  if (idx < optionCount.GetSize())
    return optionCount[idx];

  return 0;
}


PString PArgList::GetOptionString(char option, const char * dflt) const
{
  return GetOptionStringByIndex(optionLetters.Find(option), dflt);
}


PString PArgList::GetOptionString(const char * option, const char * dflt) const
{
  return GetOptionStringByIndex(optionNames.GetValuesIndex(PString(option)), dflt);
}


PString PArgList::GetOptionString(const PString & option, const char * dflt) const
{
  return GetOptionStringByIndex(optionNames.GetValuesIndex(option), dflt);
}


PString PArgList::GetOptionStringByIndex(PINDEX idx, const char * dflt) const
{
  if (idx < optionString.GetSize() && optionString.GetAt(idx) != NULL)
    return optionString[idx];

  if (dflt != NULL)
    return dflt;

  return PString();
}


PStringArray PArgList::GetParameters(PINDEX first, PINDEX last) const
{
  PStringArray array;

  last += shift;
  if (last < 0)
    return array;

  if (last >= parameterIndex.GetSize())
    last = parameterIndex.GetSize()-1;

  first += shift;
  if (first < 0)
    first = 0;

  if (first > last)
    return array;

  array.SetSize(last-first+1);

  PINDEX idx = 0;
  while (first <= last)
    array[idx++] = argumentArray[parameterIndex[first++]];

  return array;
}


PString PArgList::GetParameter(PINDEX num) const
{
  int idx = shift+(int)num;
  if (idx >= 0 && idx < (int)parameterIndex.GetSize())
    return argumentArray[parameterIndex[idx]];

  IllegalArgumentIndex(idx);
  return PString();
}


void PArgList::Shift(int sh) 
{
  shift += sh;
  if (shift < 0)
    shift = 0;
  else if (shift >= (int)parameterIndex.GetSize())
    shift = parameterIndex.GetSize() - 1;
}


void PArgList::IllegalArgumentIndex(PINDEX idx) const
{
  PError << "attempt to access undefined argument at index "
         << idx << endl;
}
 

void PArgList::UnknownOption(const PString & option) const
{
  PError << "unknown option \"" << option << "\"\n";
}


void PArgList::MissingArgument(const PString & option) const
{
  PError << "option \"" << option << "\" requires argument\n";
}

#ifdef P_CONFIG_FILE

///////////////////////////////////////////////////////////////////////////////
// PConfigArgs

PConfigArgs::PConfigArgs(const PArgList & args)
  : PArgList(args),
    sectionName(config.GetDefaultSection()),
    negationPrefix("no-")
{
}


PINDEX PConfigArgs::GetOptionCount(char option) const
{
  PINDEX count;
  if ((count = PArgList::GetOptionCount(option)) > 0)
    return count;

  PString stropt = CharToString(option);
  if (stropt.IsEmpty())
    return 0;

  return GetOptionCount(stropt);
}


PINDEX PConfigArgs::GetOptionCount(const char * option) const
{
  return GetOptionCount(PString(option));
}


PINDEX PConfigArgs::GetOptionCount(const PString & option) const
{
  // if specified on the command line, use that option
  PINDEX count = PArgList::GetOptionCount(option);
  if (count > 0)
    return count;

  // if user has specified "no-option", then ignore config file
  if (PArgList::GetOptionCount(negationPrefix + option) > 0)
    return 0;

  return config.HasKey(sectionName, option) ? 1 : 0;
}


PString PConfigArgs::GetOptionString(char option, const char * dflt) const
{
  if (PArgList::GetOptionCount(option) > 0)
    return PArgList::GetOptionString(option, dflt);

  PString stropt = CharToString(option);
  if (stropt.IsEmpty()) {
    if (dflt != NULL)
      return dflt;
    return PString();
  }

  return GetOptionString(stropt, dflt);
}


PString PConfigArgs::GetOptionString(const char * option, const char * dflt) const
{
  return GetOptionString(PString(option), dflt);
}


PString PConfigArgs::GetOptionString(const PString & option, const char * dflt) const
{
  // if specified on the command line, use that option
  if (PArgList::GetOptionCount(option) > 0)
    return PArgList::GetOptionString(option, dflt);

  // if user has specified "no-option", then ignore config file
  if (PArgList::HasOption(negationPrefix + option)) {
    if (dflt != NULL)
      return dflt;
    return PString();
  }

  return config.GetString(sectionName, option, dflt != NULL ? dflt : "");
}


void PConfigArgs::Save(const PString & saveOptionName)
{
  if (PArgList::GetOptionCount(saveOptionName) == 0)
    return;

  config.DeleteSection(sectionName);

  for (PINDEX i = 0; i < optionCount.GetSize(); i++) {
    PString optionName = optionNames[i];
    if (optionCount[i] > 0 && optionName != saveOptionName) {
      if (optionString.GetAt(i) != NULL)
        config.SetString(sectionName, optionName, optionString[i]);
      else
        config.SetBoolean(sectionName, optionName, TRUE);
    }
  }
}


PString PConfigArgs::CharToString(char ch) const
{
  PINDEX index = optionLetters.Find(ch);
  if (index == P_MAX_INDEX)
    return PString();

  if (optionNames.GetAt(index) == NULL)
    return PString();

  return optionNames[index];
}

#endif // P_CONFIG_ARGS

///////////////////////////////////////////////////////////////////////////////
// PProcess

static PProcess * PProcessInstance;
int PProcess::p_argc;
char ** PProcess::p_argv;
char ** PProcess::p_envp;

typedef std::map<PString, PProcessStartup *> PProcessStartupList;

int PProcess::_main(void *)
{
  Main();
  return terminationValue;
}


void PProcess::PreInitialise(int c, char ** v, char ** e)
{
#if PMEMORY_CHECK
  PMemoryHeap::SetIgnoreAllocations(FALSE);
#endif

  p_argc = c;
  p_argv = v;
  p_envp = e;
}


static PProcessStartupList & GetPProcessStartupList()
{
  static PProcessStartupList list;
  return list;
}


PProcess::PProcess(const char * manuf, const char * name,
                           WORD major, WORD minor, CodeStatus stat, WORD build)
  : manufacturer(manuf), productName(name)
{
  PProcessInstance = this;
  terminationValue = 0;

  majorVersion = major;
  minorVersion = minor;
  status = stat;
  buildNumber = build;

  // This flag must never be destroyed before it is finished with. As we
  // cannot assure destruction at the right time we simply allocate it and
  // NEVER destroy it! This is OK as the only reason for its destruction is
  // the program is exiting and then who cares?
#if PMEMORY_CHECK
  BOOL ignoreAllocations = PMemoryHeap::SetIgnoreAllocations(TRUE);
#endif
  PTraceMutex = new PMutex;
#if PMEMORY_CHECK
  PMemoryHeap::SetIgnoreAllocations(ignoreAllocations);
#endif

#ifndef P_RTEMS
  if (p_argv != 0 && p_argc > 0) {
    arguments.SetArgs(p_argc-1, p_argv+1);

    executableFile = PString(p_argv[0]);
    if (!PFile::Exists(executableFile)) {
      PString execFile = executableFile + ".exe";
      if (PFile::Exists(execFile))
        executableFile = execFile;
    }

    if (productName.IsEmpty())
      productName = executableFile.GetTitle().ToLower();
  }
#else
  cout << "Enter program arguments:\n";
  arguments.ReadFrom(cin);
#endif

  InitialiseProcessThread();

  Construct();
  
#ifdef __MACOSX__
  
#ifdef HAS_VIDEO
  PWLibStupidOSXHacks::loadFakeVideoStuff = 1;
#ifdef USE_SHM_VIDEO_DEVICES
  PWLibStupidOSXHacks::loadShmVideoStuff = 1;
#endif // USE_SHM_VIDEO_DEVICES
#endif // HAS_VIDEO
  
#ifdef HAS_AUDIO
  PWLibStupidOSXHacks::loadCoreAudioStuff = 1;
#endif // HAS_AUDIO
  
#endif // __MACOSX__

  // create one instance of each class registered in the 
  // PProcessStartup abstract factory
  PProcessStartupList & startups = GetPProcessStartupList();
  {
    PProcessStartup * levelSet = PFactory<PProcessStartup>::CreateInstance("SetTraceLevel");
    if (levelSet != NULL) 
      levelSet->OnStartup();
    else {
      char * env = ::getenv("PWLIB_TRACE_STARTUP");
      if (env != NULL) 
        PTrace::Initialise(atoi(env), NULL, PTrace::Blocks | PTrace::Timestamp | PTrace::Thread | PTrace::FileAndLine);
    }

    PProcessStartupFactory::KeyList_T list = PProcessStartupFactory::GetKeyList();
    PProcessStartupFactory::KeyList_T::const_iterator r;
    for (r = list.begin(); r != list.end(); ++r) {
      if (*r != "SetTraceLevel") {
        PProcessStartup * instance = PProcessStartupFactory::CreateInstance(*r);
        instance->OnStartup();
        startups.insert(std::pair<PString, PProcessStartup *>(*r, instance));
      }
    }
  }
}


void PProcess::PreShutdown()
{
  PProcessStartupList & startups = GetPProcessStartupList();

  // call OnShutfdown for the PProcessInstances previously created
  // make sure we handle singletons correctly
  {
    while (startups.size() > 0) {
      PProcessStartupList::iterator r = startups.begin();
      PProcessStartup * instance = r->second;
      instance->OnShutdown();
      if (!PProcessStartupFactory::IsSingleton(r->first))
        delete instance;
      startups.erase(r);
    }
  }
}


PProcess & PProcess::Current()
{
  if (PProcessInstance == NULL) {
    cerr << "Catastrophic failure, PProcess::Current() = NULL!!\n";
#if defined(_MSC_VER) && defined(_DEBUG) && !defined(_WIN32_WCE)
    __asm int 3;
#endif
    _exit(1);
  }
  return *PProcessInstance;
}


BOOL PProcess::IsInitialised()
{
  return PProcessInstance != NULL;
}


PObject::Comparison PProcess::Compare(const PObject & obj) const
{
  PAssert(PIsDescendant(&obj, PProcess), PInvalidCast);
  return productName.Compare(((const PProcess &)obj).productName);
}


void PProcess::Terminate()
{
#ifdef _WINDLL
  FatalExit(terminationValue);
#else
  exit(terminationValue);
#endif
}


PString PProcess::GetThreadName() const
{
  return GetName(); 
}
 
 
void PProcess::SetThreadName(const PString & /*name*/)
{
}

PTime PProcess::GetStartTime() const
{ 
  return programStartTime; 
}

PString PProcess::GetVersion(BOOL full) const
{
  const char * const statusLetter[NumCodeStatuses] =
    { "alpha", "beta", "." };
  return psprintf(full ? "%u.%u%s%u" : "%u.%u",
                  majorVersion, minorVersion, statusLetter[status], buildNumber);
}


void PProcess::SetConfigurationPath(const PString & path)
{
  configurationPaths = path.Tokenise(";:", FALSE);
}


///////////////////////////////////////////////////////////////////////////////
// PThread

void PThread::PrintOn(ostream & strm) const
{
  strm << GetThreadName();
}


PString PThread::GetThreadName() const
{
  return threadName; 
}

#if defined(_DEBUG) && defined(_MSC_VER)

typedef struct tagTHREADNAME_INFO
{
  DWORD dwType ;                       // must be 0x1000
  LPCSTR szName ;                      // pointer to name (in user addr space)
  DWORD dwThreadID ;                   // thread ID (-1=caller thread, but seems to set more than one thread's name)
  DWORD dwFlags ;                      // reserved for future use, must be zero
} THREADNAME_INFO ;


void SetWinDebugThreadName (THREADNAME_INFO * info)
{
  __try
  {
    RaiseException (0x406D1388, 0, sizeof(THREADNAME_INFO)/sizeof(DWORD), (DWORD *) info) ;
  }                              // if not running under debugger exception comes back
  __except(EXCEPTION_CONTINUE_EXECUTION)
  {                              // just keep on truckin'
  }
}
#endif // defined(_DEBUG) && defined(_MSC_VER)


void PThread::SetThreadName(const PString & name)
{
  if (name.IsEmpty())
    threadName = psprintf("%s:%08x", GetClass(), (INT)this);
  else
    threadName = psprintf(name, (INT)this);

#if defined(_DEBUG) && defined(_MSC_VER)
  if (threadId) {       // make thread name known to debugger
    THREADNAME_INFO Info = { 0x1000, (const char *) threadName, threadId, 0 } ;
    SetWinDebugThreadName (&Info) ;
  }
#endif // defined(_DEBUG) && defined(_MSC_VER)
}
 
PThread * PThread::Create(const PNotifier & notifier,
                          INT parameter,
                          AutoDeleteFlag deletion,
                          Priority priorityLevel,
                          const PString & threadName,
                          PINDEX stackSize)
{
  PThread * thread = new PSimpleThread(notifier,
                                       parameter,
                                       deletion,
                                       priorityLevel,
                                       threadName,
                                       stackSize);
  if (deletion != AutoDeleteThread)
    return thread;

  // Do not return a pointer to the thread if it is auto-delete as this
  // pointer is extremely dangerous to use, it could be deleted at any moment
  // from now on so using the pointer could crash the program.
  return NULL;
}


PSimpleThread::PSimpleThread(const PNotifier & notifier,
                             INT param,
                             AutoDeleteFlag deletion,
                             Priority priorityLevel,
                             const PString & threadName,
                             PINDEX stackSize)
  : PThread(stackSize, deletion, priorityLevel, threadName),
    callback(notifier),
    parameter(param)
{
  Resume();
}


void PSimpleThread::Main()
{
  callback(*this, parameter);
}

/////////////////////////////////////////////////////////////////////////////

void PSyncPointAck::Signal()
{
  PSyncPoint::Signal();
  ack.Wait();
}


void PSyncPointAck::Signal(const PTimeInterval & wait)
{
  PSyncPoint::Signal();
  ack.Wait(wait);
}


void PSyncPointAck::Acknowledge()
{
  ack.Signal();
}


/////////////////////////////////////////////////////////////////////////////

void PCondMutex::WaitCondition()
{
  for (;;) {
    Wait();
    if (Condition())
      return;
    PMutex::Signal();
    OnWait();
    syncPoint.Wait();
  }
}


void PCondMutex::Signal()
{
  if (Condition())
    syncPoint.Signal();
  PMutex::Signal();
}


void PCondMutex::OnWait()
{
  // Do nothing
}


/////////////////////////////////////////////////////////////////////////////

PIntCondMutex::PIntCondMutex(int val, int targ, Operation op)
{
  value = val;
  target = targ;
  operation = op;
}


void PIntCondMutex::PrintOn(ostream & strm) const
{
  strm << '(' << value;
  switch (operation) {
    case LT :
      strm << " < ";
    case LE :
      strm << " <= ";
    case GE :
      strm << " >= ";
    case GT :
      strm << " > ";
    default:
      strm << " == ";
  }
  strm << target << ')';
}


BOOL PIntCondMutex::Condition()
{
  switch (operation) {
    case LT :
      return value < target;
    case LE :
      return value <= target;
    case GE :
      return value >= target;
    case GT :
      return value > target;
    default :
      break;
  }
  return value == target;
}


PIntCondMutex & PIntCondMutex::operator=(int newval)
{
  Wait();
  value = newval;
  Signal();
  return *this;
}


PIntCondMutex & PIntCondMutex::operator++()
{
  Wait();
  value++;
  Signal();
  return *this;
}


PIntCondMutex & PIntCondMutex::operator+=(int inc)
{
  Wait();
  value += inc;
  Signal();
  return *this;
}


PIntCondMutex & PIntCondMutex::operator--()
{
  Wait();
  value--;
  Signal();
  return *this;
}


PIntCondMutex & PIntCondMutex::operator-=(int dec)
{
  Wait();
  value -= dec;
  Signal();
  return *this;
}


/////////////////////////////////////////////////////////////////////////////

PReadWriteMutex::PReadWriteMutex()
  : readerSemaphore(1, 1),
    writerSemaphore(1, 1)
{
  readerCount = 0;
  writerCount = 0;
}


PReadWriteMutex::Nest * PReadWriteMutex::GetNest() const
{
  PWaitAndSignal mutex(nestingMutex);
  return nestedThreads.GetAt(POrdinalKey((INT)PThread::GetCurrentThreadId()));
}


void PReadWriteMutex::EndNest()
{
  nestingMutex.Wait();
  nestedThreads.RemoveAt(POrdinalKey((INT)PThread::GetCurrentThreadId()));
  nestingMutex.Signal();
}


PReadWriteMutex::Nest & PReadWriteMutex::StartNest()
{
  POrdinalKey threadId = (INT)PThread::GetCurrentThreadId();

  nestingMutex.Wait();

  Nest * nest = nestedThreads.GetAt(threadId);

  if (nest == NULL) {
    nest = new Nest;
    nestedThreads.SetAt(threadId, nest);
  }

  nestingMutex.Signal();

  return *nest;
}


void PReadWriteMutex::StartRead()
{
  // Get the nested thread info structure, create one it it doesn't exist
  Nest & nest = StartNest();

  // One more nested call to StartRead() by this thread, note this does not
  // need to be mutexed as it is always in the context of a single thread.
  nest.readerCount++;

  // If this is the first call to StartRead() and there has not been a
  // previous call to StartWrite() then actually do the text book read only
  // lock, otherwise we leave it as just having incremented the reader count.
  if (nest.readerCount == 1 && nest.writerCount == 0)
    InternalStartRead();
}


void PReadWriteMutex::InternalStartRead()
{
  // Text book read only lock

  starvationPreventer.Wait();
   readerSemaphore.Wait();
    readerMutex.Wait();

     readerCount++;
     if (readerCount == 1)
       writerSemaphore.Wait();

    readerMutex.Signal();
   readerSemaphore.Signal();
  starvationPreventer.Signal();
}


void PReadWriteMutex::EndRead()
{
  // Get the nested thread info structure for the curent thread
  Nest * nest = GetNest();

  // If don't have an active read or write lock or there is a write lock but
  // the StartRead() was never called, then assert and ignore call.
  if (nest == NULL || nest->readerCount == 0) {
    PAssertAlways("Unbalanced PReadWriteMutex::EndRead()");
    return;
  }

  // One less nested lock by this thread, note this does not
  // need to be mutexed as it is always in the context of a single thread.
  nest->readerCount--;

  // If this is a nested read or a write lock is present then we don't do the
  // real unlock, the decrement is enough.
  if (nest->readerCount > 0 || nest->writerCount > 0)
    return;

  // Do text book read lock
  InternalEndRead();

  // At this point all read and write locks are gone for this thread so we can
  // reclaim the memory.
  EndNest();
}


void PReadWriteMutex::InternalEndRead()
{
  // Text book read only unlock

  readerMutex.Wait();

  readerCount--;
  if (readerCount == 0)
    writerSemaphore.Signal();

  readerMutex.Signal();
}


void PReadWriteMutex::StartWrite()
{
  // Get the nested thread info structure, create one it it doesn't exist
  Nest & nest = StartNest();

  // One more nested call to StartWrite() by this thread, note this does not
  // need to be mutexed as it is always in the context of a single thread.
  nest.writerCount++;

  // If is a nested call to StartWrite() then simply return, the writer count
  // increment is all we haev to do.
  if (nest.writerCount > 1)
    return;

  // If have a read lock already in this thread then do the "real" unlock code
  // but do not change the lock count, calls to EndRead() will now just
  // decrement the count instead of doing the unlock (its already done!)
  if (nest.readerCount > 0)
    InternalEndRead();

  // Note in this gap another thread could grab the write lock, thus

  // Now do the text book write lock
  writerMutex.Wait();

  writerCount++;
  if (writerCount == 1)
    readerSemaphore.Wait();

  writerMutex.Signal();

  writerSemaphore.Wait();
}


void PReadWriteMutex::EndWrite()
{
  // Get the nested thread info structure for the curent thread
  Nest * nest = GetNest();

  // If don't have an active read or write lock or there is a read lock but
  // the StartWrite() was never called, then assert and ignore call.
  if (nest == NULL || nest->writerCount == 0) {
    PAssertAlways("Unbalanced PReadWriteMutex::EndWrite()");
    return;
  }

  // One less nested lock by this thread, note this does not
  // need to be mutexed as it is always in the context of a single thread.
  nest->writerCount--;

  // If this is a nested write lock then the decrement is enough and we
  // don't do the actual write unlock.
  if (nest->writerCount > 0)
    return;

  // Begin text book write unlock
  writerSemaphore.Signal();

  writerMutex.Wait();

  writerCount--;
  if (writerCount == 0)
    readerSemaphore.Signal();

  writerMutex.Signal();
  // End of text book write unlock

  // Now check to see if there was a read lock present for this thread, if so
  // then reacquire the read lock (not changing the count) otherwise clean up the
  // memory for the nested thread info structure
  if (nest->readerCount > 0)
    InternalStartRead();
  else
    EndNest();
}


/////////////////////////////////////////////////////////////////////////////

PReadWaitAndSignal::PReadWaitAndSignal(const PReadWriteMutex & rw, BOOL start)
  : mutex((PReadWriteMutex &)rw)
{
  if (start)
    mutex.StartRead();
}


PReadWaitAndSignal::~PReadWaitAndSignal()
{
  mutex.EndRead();
}


/////////////////////////////////////////////////////////////////////////////

PWriteWaitAndSignal::PWriteWaitAndSignal(const PReadWriteMutex & rw, BOOL start)
  : mutex((PReadWriteMutex &)rw)
{
  if (start)
    mutex.StartWrite();
}


PWriteWaitAndSignal::~PWriteWaitAndSignal()
{
  mutex.EndWrite();
}


// End Of File ///////////////////////////////////////////////////////////////


syntax highlighted by Code2HTML, v. 0.9.1