home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / xwphescr.zip / XWPH0208.ZIP / src / helpers / dosh.c < prev    next >
C/C++ Source or Header  |  2002-08-11  |  140KB  |  4,247 lines

  1.  
  2. /*
  3.  *@@sourcefile dosh.c:
  4.  *      dosh.c contains Control Program helper functions.
  5.  *
  6.  *      This file has miscellaneous system functions,
  7.  *      drive helpers, file helpers, and partition functions.
  8.  *
  9.  *      Usage: All OS/2 programs.
  10.  *
  11.  *      Function prefixes (new with V0.81):
  12.  *      --  dosh*   Dos (Control Program) helper functions
  13.  *
  14.  *      These funcs are forward-declared in dosh.h, which
  15.  *      must be #include'd first.
  16.  *
  17.  *      The resulting dosh.obj object file can be linked
  18.  *      against any application object file. As opposed to
  19.  *      the code in dosh2.c, it does not require any other
  20.  *      code from the helpers.
  21.  *
  22.  *      dosh.obj can also be used with the VAC subsystem
  23.  *      library (/rn compiler option).
  24.  *
  25.  *      Note: Version numbering in this file relates to XWorkplace version
  26.  *            numbering.
  27.  *
  28.  *@@header "helpers\dosh.h"
  29.  */
  30.  
  31. /*
  32.  *      This file Copyright (C) 1997-2000 Ulrich Möller.
  33.  *      This file is part of the "XWorkplace helpers" source package.
  34.  *      This is free software; you can redistribute it and/or modify
  35.  *      it under the terms of the GNU General Public License as published
  36.  *      by the Free Software Foundation, in version 2 as it comes in the
  37.  *      "COPYING" file of the XWorkplace main distribution.
  38.  *      This program is distributed in the hope that it will be useful,
  39.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  40.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  41.  *      GNU General Public License for more details.
  42.  */
  43.  
  44. #define OS2EMX_PLAIN_CHAR
  45.     // this is needed for "os2emx.h"; if this is defined,
  46.     // emx will define PSZ as _signed_ char, otherwise
  47.     // as unsigned char
  48.  
  49. #define INCL_DOSMODULEMGR
  50. #define INCL_DOSPROCESS
  51. #define INCL_DOSEXCEPTIONS
  52. #define INCL_DOSSESMGR
  53. #define INCL_DOSQUEUES
  54. #define INCL_DOSSEMAPHORES
  55. #define INCL_DOSMISC
  56. #define INCL_DOSDEVICES
  57. #define INCL_DOSDEVIOCTL
  58. #define INCL_DOSERRORS
  59.  
  60. #define INCL_KBD
  61. #include <os2.h>
  62.  
  63. #include <stdlib.h>
  64. #include <string.h>
  65. #include <stdio.h>
  66. #include <stdarg.h>
  67. #include <ctype.h>
  68.  
  69. #include "setup.h"                      // code generation and debugging options
  70.  
  71. #include "helpers\dosh.h"
  72. #include "helpers\standards.h"
  73.  
  74. #pragma hdrstop
  75.  
  76. // static const CHAR  G_acDriveLetters[28] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  77.  
  78. /*
  79.  *@@category: Helpers\Control program helpers\Wrappers
  80.  */
  81.  
  82. /* ******************************************************************
  83.  *
  84.  *   Wrappers
  85.  *
  86.  ********************************************************************/
  87.  
  88. #ifdef DOSH_STANDARDWRAPPERS
  89.  
  90.     /*
  91.      *@@ doshSleep:
  92.      *
  93.      *@@added V0.9.16 (2002-01-26) [umoeller]
  94.      */
  95.  
  96.     APIRET doshSleep(ULONG msec)
  97.     {
  98.         // put the call in brackets so the macro won't apply here
  99.         return (DosSleep)(msec);
  100.     }
  101.  
  102.     /*
  103.      *@@ doshCreateMutexSem:
  104.      *
  105.      *@@added V0.9.16 (2002-01-26) [umoeller]
  106.      */
  107.  
  108.     APIRET doshCreateMutexSem(PSZ pszName,
  109.                               PHMTX phmtx,
  110.                               ULONG flAttr,
  111.                               BOOL32 fState)
  112.     {
  113.         // put the call in brackets so the macro won't apply here
  114.         return (DosCreateMutexSem)(pszName, phmtx, flAttr, fState);
  115.     }
  116.  
  117.     /*
  118.      *@@ doshRequestMutexSem:
  119.      *
  120.      *@@added V0.9.16 (2002-01-26) [umoeller]
  121.      */
  122.  
  123.     APIRET doshRequestMutexSem(HMTX hmtx, ULONG ulTimeout)
  124.     {
  125.         return (DosRequestMutexSem)(hmtx, ulTimeout);
  126.     }
  127.  
  128.     /*
  129.      *@@ doshReleaseMutexSem:
  130.      *
  131.      *@@added V0.9.16 (2002-01-26) [umoeller]
  132.      */
  133.  
  134.     APIRET doshReleaseMutexSem(HMTX hmtx)
  135.     {
  136.         return (DosReleaseMutexSem)(hmtx);
  137.     }
  138.  
  139.     /*
  140.      *@@ doshSetExceptionHandler:
  141.      *
  142.      *@@added V0.9.16 (2002-01-26) [umoeller]
  143.      */
  144.  
  145.     APIRET doshSetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD pERegRec)
  146.     {
  147.         // put the call in brackets so the macro won't apply here
  148.         return (DosSetExceptionHandler)(pERegRec);
  149.     }
  150.  
  151.     /*
  152.      *@@ doshUnsetExceptionHandler:
  153.      *
  154.      *@@added V0.9.16 (2002-01-26) [umoeller]
  155.      */
  156.  
  157.     APIRET doshUnsetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD pERegRec)
  158.     {
  159.         // put the call in brackets so the macro won't apply here
  160.         return (DosUnsetExceptionHandler)(pERegRec);
  161.     }
  162.  
  163. #endif
  164.  
  165. /*
  166.  *@@category: Helpers\Control program helpers\Miscellaneous
  167.  *      Miscellaneous helpers in dosh.c that didn't fit into any other
  168.  *      category.
  169.  */
  170.  
  171. /* ******************************************************************
  172.  *
  173.  *   Miscellaneous
  174.  *
  175.  ********************************************************************/
  176.  
  177. /*
  178.  *@@ doshGetChar:
  179.  *      reads a single character from the keyboard.
  180.  *      Useful for VIO sessions, since there's great
  181.  *      confusion between the various C dialects about
  182.  *      how to use getc(), and getc() doesn't work
  183.  *      with the VAC subsystem library.
  184.  *
  185.  *@@added V0.9.4 (2000-07-27) [umoeller]
  186.  */
  187.  
  188. CHAR doshGetChar(VOID)
  189. {
  190.     // CHAR    c;
  191.     // ULONG   ulRead = 0;
  192.  
  193.     KBDKEYINFO kki;
  194.     KbdCharIn(&kki,
  195.               0, // wait
  196.               0);
  197.  
  198.     return (kki.chChar);
  199. }
  200.  
  201. /*
  202.  *@@ doshQueryShiftState:
  203.  *      returns TRUE if any of the SHIFT keys are
  204.  *      currently pressed. Useful for checks during
  205.  *      WM_COMMAND messages from menus.
  206.  *
  207.  *@@changed V0.9.5 (2000-09-27) [umoeller]: added error checking
  208.  */
  209.  
  210. BOOL doshQueryShiftState(VOID)
  211. {
  212.     BOOL            brc = FALSE;
  213.     APIRET          arc = NO_ERROR;
  214.     HFILE           hfKbd;
  215.     ULONG           ulAction;
  216.  
  217.     if (!(arc = DosOpen("KBD$", &hfKbd, &ulAction, 0,
  218.                         FILE_NORMAL,
  219.                         FILE_OPEN,
  220.                         OPEN_ACCESS_READONLY | OPEN_SHARE_DENYWRITE,
  221.                         (PEAOP2)NULL)))
  222.     {
  223.         SHIFTSTATE      ShiftState;
  224.         ULONG           cbDataLen = sizeof(ShiftState);
  225.  
  226.         if (!(arc = DosDevIOCtl(hfKbd, IOCTL_KEYBOARD, KBD_GETSHIFTSTATE,
  227.                                 NULL, 0, NULL,      // no parameters
  228.                                 &ShiftState, cbDataLen, &cbDataLen)))
  229.             brc = ((ShiftState.fsState & 3) != 0);
  230.  
  231.         DosClose(hfKbd);
  232.     }
  233.  
  234.     return brc;
  235. }
  236.  
  237. /*
  238.  *@@ doshIsWarp4:
  239.  *      checks the OS/2 system version number.
  240.  *
  241.  *      Returns:
  242.  *
  243.  *      -- 0 (FALSE): OS/2 2.x or Warp 3 is running.
  244.  *
  245.  *      -- 1: Warp 4.0 is running.
  246.  *
  247.  *      -- 2: Warp 4.5 is running (WSeB or Warp 4 FP 13+ or eCS
  248.  *            or ACP/MCP), or even something newer.
  249.  *
  250.  *@@changed V0.9.2 (2000-03-05) [umoeller]: reported TRUE on Warp 3 also; fixed
  251.  *@@changed V0.9.6 (2000-10-16) [umoeller]: patched for speed
  252.  *@@changed V0.9.9 (2001-04-04) [umoeller]: now returning 2 for Warp 4.5 and above
  253.  */
  254.  
  255. ULONG doshIsWarp4(VOID)
  256. {
  257.     static BOOL     s_fQueried = FALSE;
  258.     static ULONG    s_ulrc = 0;
  259.  
  260.     if (!s_fQueried)
  261.     {
  262.         // first call:
  263.         ULONG       aulBuf[3];
  264.  
  265.         DosQuerySysInfo(QSV_VERSION_MAJOR,      // 11
  266.                         QSV_VERSION_MINOR,      // 12
  267.                         &aulBuf, sizeof(aulBuf));
  268.         // Warp 3 is reported as 20.30
  269.         // Warp 4 is reported as 20.40
  270.         // Aurora is reported as 20.45 (regardless of convenience packs)
  271.  
  272.         if     (    (aulBuf[0] > 20)        // major > 20; not the case with Warp 3, 4, 5
  273.                  || (   (aulBuf[0] == 20)   // major == 20 and minor >= 45
  274.                      && (aulBuf[1] >= 45)
  275.                     )
  276.                )
  277.             // Warp 4.5 or newer:
  278.             s_ulrc = 2;
  279.         else if (   (aulBuf[0] == 20)   // major == 20 and minor == 40
  280.                  && (aulBuf[1] == 40)
  281.                 )
  282.             // Warp 4:
  283.             s_ulrc = 1;
  284.  
  285.         s_fQueried = TRUE;
  286.     }
  287.  
  288.     return (s_ulrc);
  289. }
  290.  
  291. /*
  292.  *@@ doshQuerySysErrorMsg:
  293.  *      this retrieves the error message for a system error
  294.  *      (APIRET) from the system error message file (OSO001.MSG).
  295.  *      This file better be on the DPATH (it normally is).
  296.  *
  297.  *      This returns the string in the "SYSxxx: blahblah" style,
  298.  *      which is normally displayed on the command line when
  299.  *      errors occur.
  300.  *
  301.  *      The error message is returned in a newly allocated
  302.  *      buffer, which should be free()'d afterwards.
  303.  *
  304.  *      Returns NULL upon errors.
  305.  */
  306.  
  307. PSZ doshQuerySysErrorMsg(APIRET arc)    // in: DOS error code
  308. {
  309.     PSZ     pszReturn = 0;
  310.     CHAR    szDosError[1000];
  311.     ULONG   cbDosError = 0;
  312.     DosGetMessage(NULL, 0,       // no string replacements
  313.                   szDosError, sizeof(szDosError),
  314.                   arc,
  315.                   "OSO001.MSG",        // default OS/2 message file
  316.                   &cbDosError);
  317.     if (cbDosError > 2)
  318.     {
  319.         szDosError[cbDosError - 2] = 0;
  320.         pszReturn = strdup(szDosError);
  321.     }
  322.     return (pszReturn);
  323. }
  324.  
  325. /*
  326.  *@@ doshQuerySysUptime:
  327.  *      returns the system uptime in milliseconds.
  328.  *      This can be used for time comparisons.
  329.  *
  330.  *@@added V0.9.12 (2001-05-18) [umoeller]
  331.  */
  332.  
  333. ULONG doshQuerySysUptime(VOID)
  334. {
  335.     ULONG ulms;
  336.     DosQuerySysInfo(QSV_MS_COUNT,
  337.                     QSV_MS_COUNT,
  338.                     &ulms,
  339.                     sizeof(ulms));
  340.     return (ulms);
  341. }
  342.  
  343. /*
  344.  *@@ doshDevIOCtl:
  345.  *
  346.  *      Works with those IOCtls where the buffer
  347.  *      size parameters are always the same anyway,
  348.  *      which applies to all IOCtls I have seen
  349.  *      so far.
  350.  *
  351.  *@@added V0.9.13 (2001-06-14) [umoeller]
  352.  */
  353.  
  354. APIRET doshDevIOCtl(HFILE hf,
  355.                     ULONG ulCategory,
  356.                     ULONG ulFunction,
  357.                     PVOID pvParams,
  358.                     ULONG cbParams,
  359.                     PVOID pvData,
  360.                     ULONG cbData)
  361. {
  362.     return (DosDevIOCtl(hf,
  363.                         ulCategory,
  364.                         ulFunction,
  365.                         pvParams, cbParams, &cbParams,
  366.                         pvData, cbData, &cbData));
  367. }
  368.  
  369. /*
  370.  *@@category: Helpers\Control program helpers\Shared memory management
  371.  *      helpers for allocating and requesting shared memory.
  372.  */
  373.  
  374. /* ******************************************************************
  375.  *
  376.  *   Memory helpers
  377.  *
  378.  ********************************************************************/
  379.  
  380. /*
  381.  *@@ doshMalloc:
  382.  *      wrapper around malloc() which automatically
  383.  *      sets ERROR_NOT_ENOUGH_MEMORY.
  384.  *
  385.  *@@added V0.9.16 (2001-10-19) [umoeller]
  386.  */
  387.  
  388. PVOID doshMalloc(ULONG cb,
  389.                  APIRET *parc)
  390. {
  391.     PVOID pv;
  392.     *parc = NO_ERROR;
  393.     if (!(pv = malloc(cb)))
  394.         *parc = ERROR_NOT_ENOUGH_MEMORY;
  395.  
  396.     return (pv);
  397. }
  398.  
  399. /*
  400.  *@@ doshAllocArray:
  401.  *      allocates c * cbArrayItem bytes.
  402.  *      Similar to calloc(), but returns
  403.  *      error codes:
  404.  *
  405.  *      --  NO_ERROR: *ppv and *pcbAllocated were set.
  406.  *
  407.  *      --  ERROR_NO_DATA: either c or cbArrayItem are
  408.  *          zero.
  409.  *
  410.  *      --  ERROR_NOT_ENOUGH_MEMORY: malloc() failed.
  411.  *
  412.  *@@added V0.9.16 (2001-12-08) [umoeller]
  413.  */
  414.  
  415. APIRET doshAllocArray(ULONG c,              // in: array item count
  416.                       ULONG cbArrayItem,    // in: size of one array item
  417.                       PBYTE *ppv,           // out: memory ptr if NO_ERROR is returned
  418.                       PULONG pcbAllocated)  // out: # of bytes allocated
  419. {
  420.     if (!c || !cbArrayItem)
  421.         return ERROR_NO_DATA;
  422.  
  423.     *pcbAllocated = c * cbArrayItem;
  424.     if (!(*ppv = (PBYTE)malloc(*pcbAllocated)))
  425.         return ERROR_NOT_ENOUGH_MEMORY;
  426.  
  427.     return NO_ERROR;
  428. }
  429.  
  430. /*
  431.  *@@ doshAllocSharedMem:
  432.  *      wrapper for DosAllocSharedMem which has
  433.  *      a malloc()-like syntax. Just due to my
  434.  *      lazyness.
  435.  *
  436.  *      Note that ulSize is always rounded up to the
  437.  *      next 4KB value, so don't use this hundreds of times.
  438.  *
  439.  *      Returns NULL upon errors. Possible errors include
  440.  *      that a memory block calle pcszName has already been
  441.  *      allocated.
  442.  *
  443.  *      Use DosFreeMem(pvrc) to free the memory. The memory
  444.  *      will only be freed if no other process has requested
  445.  *      access.
  446.  *
  447.  *@@added V0.9.3 (2000-04-18) [umoeller]
  448.  */
  449.  
  450. PVOID doshAllocSharedMem(ULONG ulSize,      // in: requested mem block size (rounded up to 4KB)
  451.                          const char* pcszName) // in: name of block ("\\SHAREMEM\\xxx") or NULL
  452. {
  453.     PVOID   pvrc = NULL;
  454.     APIRET  arc = DosAllocSharedMem((PVOID*)&pvrc,
  455.                                     (PSZ)pcszName,
  456.                                     ulSize,
  457.                                     PAG_COMMIT | PAG_READ | PAG_WRITE);
  458.     if (arc == NO_ERROR)
  459.         return (pvrc);
  460.  
  461.     return NULL;
  462. }
  463.  
  464. /*
  465.  *@@ doshRequestSharedMem:
  466.  *      requests access to a block of named shared memory
  467.  *      allocated by doshAllocSharedMem.
  468.  *
  469.  *      Returns NULL upon errors.
  470.  *
  471.  *      Use DosFreeMem(pvrc) to free the memory. The memory
  472.  *      will only be freed if no other process has requested
  473.  *      access.
  474.  *
  475.  *@@added V0.9.3 (2000-04-19) [umoeller]
  476.  */
  477.  
  478. PVOID doshRequestSharedMem(PCSZ pcszName)
  479. {
  480.     PVOID pvrc = NULL;
  481.     APIRET arc = DosGetNamedSharedMem((PVOID*)pvrc,
  482.                                       (PSZ)pcszName,
  483.                                       PAG_READ | PAG_WRITE);
  484.     if (arc == NO_ERROR)
  485.         return (pvrc);
  486.  
  487.     return NULL;
  488. }
  489.  
  490. /*
  491.  *@@category: Helpers\Control program helpers\Drive management
  492.  *      functions for managing drives... enumerating, testing,
  493.  *      querying etc.
  494.  */
  495.  
  496. /* ******************************************************************
  497.  *
  498.  *   Drive helpers
  499.  *
  500.  ********************************************************************/
  501.  
  502. /*
  503.  *@@ doshIsFixedDisk:
  504.  *      checks whether a disk is fixed or removeable.
  505.  *      ulLogicalDrive must be 1 for drive A:, 2 for B:, ...
  506.  *      The result is stored in *pfFixed.
  507.  *      Returns DOS error code.
  508.  *
  509.  *      From my testing, this function does _not_ provoke
  510.  *      "drive not ready" popups, even if the disk is not
  511.  *      ready.
  512.  *
  513.  *      Warning: This uses DosDevIOCtl, which has proved
  514.  *      to cause problems with some device drivers for
  515.  *      removeable disks.
  516.  *
  517.  *      Returns:
  518.  *
  519.  *      --  NO_ERROR: *pfFixed was set.
  520.  *
  521.  *      --  ERROR_INVALID_DRIVE: drive letter invalid
  522.  *
  523.  *      --  ERROR_NOT_SUPPORTED (50): for network drives.
  524.  *
  525.  *@@changed V0.9.14 (2001-08-03) [umoeller]: added extra fix for A: and B:
  526.  */
  527.  
  528. APIRET doshIsFixedDisk(ULONG ulLogicalDrive,   // in: 1 for A:, 2 for B:, 3 for C:, ...
  529.                        PBOOL pfFixed)          // out: TRUE for fixed disks
  530. {
  531.     APIRET arc = ERROR_INVALID_DRIVE;
  532.  
  533.     if (    (ulLogicalDrive == 1)
  534.          || (ulLogicalDrive == 2)
  535.        )
  536.     {
  537.         // drive A: and B: can never be fixed V0.9.14 (2001-08-03) [umoeller]
  538.         *pfFixed = FALSE;
  539.         return NO_ERROR;
  540.     }
  541.  
  542.     if (ulLogicalDrive)
  543.     {
  544.         // parameter packet
  545.         #pragma pack(1)
  546.         struct {
  547.             UCHAR   command,
  548.                     drive;
  549.         } parms;
  550.         #pragma pack()
  551.  
  552.         // data packet
  553.         UCHAR ucNonRemoveable;
  554.  
  555.         parms.drive = (UCHAR)(ulLogicalDrive - 1);
  556.         if (!(arc = doshDevIOCtl((HFILE)-1,
  557.                                  IOCTL_DISK,                  // 0x08
  558.                                  DSK_BLOCKREMOVABLE,          // 0x20
  559.                                  &parms, sizeof(parms),
  560.                                  &ucNonRemoveable, sizeof(ucNonRemoveable))))
  561.             *pfFixed = (BOOL)ucNonRemoveable;
  562.     }
  563.  
  564.     return arc;
  565. }
  566.  
  567. /*
  568.  *@@ doshQueryDiskParams:
  569.  *      this retrieves more information about a given drive,
  570.  *      which is stored in the specified BIOSPARAMETERBLOCK
  571.  *      structure.
  572.  *
  573.  *      BIOSPARAMETERBLOCK is defined in the Toolkit headers,
  574.  *      and from my testing, it's the same with the Toolkits
  575.  *      3 and 4.5.
  576.  *
  577.  *      If NO_ERROR is returned, the bDeviceType field can
  578.  *      be one of the following (according to CPREF):
  579.  *
  580.  *      --  0:  48 TPI low-density diskette drive
  581.  *      --  1:  96 TPI high-density diskette drive
  582.  *      --  2:  3.5-inch 720KB diskette drive
  583.  *      --  3:  8-Inch single-density diskette drive
  584.  *      --  4:  8-Inch double-density diskette drive
  585.  *      --  5:  Fixed disk
  586.  *      --  6:  Tape drive
  587.  *      --  7:  Other (includes 1.44MB 3.5-inch diskette drive)
  588.  *      --  8:  R/W optical disk
  589.  *      --  9:  3.5-inch 4.0MB diskette drive (2.88MB formatted)
  590.  *
  591.  *      From my testing, this function does _not_ provoke
  592.  *      "drive not ready" popups, even if the disk is not
  593.  *      ready.
  594.  *
  595.  *      Warning: This uses DosDevIOCtl, which has proved
  596.  *      to cause problems with some device drivers for
  597.  *      removeable disks.
  598.  *
  599.  *      This returns the DOS error code of DosDevIOCtl.
  600.  *      This will be:
  601.  *
  602.  *      --  NO_ERROR for all local disks;
  603.  *
  604.  *      --  ERROR_NOT_SUPPORTED (50) for network drives.
  605.  *
  606.  *@@added V0.9.0 [umoeller]
  607.  *@@changed V0.9.13 (2001-06-14) [umoeller]: changed prototype to use BIOSPARAMETERBLOCK directly
  608.  *@@changed V0.9.13 (2001-06-14) [umoeller]: now querying standard media, no redetermine
  609.  */
  610.  
  611. APIRET doshQueryDiskParams(ULONG ulLogicalDrive,        // in:  1 for A:, 2 for B:, 3 for C:, ...
  612.                            PBIOSPARAMETERBLOCK pdp)     // out: drive parameters
  613. {
  614.     APIRET arc = ERROR_INVALID_DRIVE;
  615.  
  616.     if (ulLogicalDrive)
  617.     {
  618.         #pragma pack(1)
  619.         // parameter packet
  620.         struct {
  621.             UCHAR   ucCommand,
  622.                     ucDrive;
  623.         } parms;
  624.         #pragma pack()
  625.  
  626.         parms.ucCommand = 0;    // 0 = return standard media,
  627.                                 // 1 = read currently inserted media
  628.                                 // (1 doesn't work any more, returns arc 87
  629.                                 // V0.9.13 (2001-06-14) [umoeller])
  630.         parms.ucDrive=(UCHAR)(ulLogicalDrive-1);
  631.  
  632.         // zero the structure V0.9.13 (2001-06-14) [umoeller]
  633.         memset(pdp, 0, sizeof(BIOSPARAMETERBLOCK));
  634.  
  635.         arc = doshDevIOCtl((HFILE)-1,
  636.                            IOCTL_DISK,                  // 0x08
  637.                            DSK_GETDEVICEPARAMS,         // 0x63
  638.                            &parms, sizeof(parms),
  639.                            pdp,   sizeof(BIOSPARAMETERBLOCK));
  640.  
  641.         /* if (!arc)
  642.         {
  643.             _Pmpf(("      bDeviceType: %d", pdp->bDeviceType));
  644.             _Pmpf(("      bytes per sector: %d", pdp->usBytesPerSector));
  645.             _Pmpf(("      sectors per track: %d", pdp->usSectorsPerTrack));
  646.         } */
  647.     }
  648.  
  649.     return arc;
  650. }
  651.  
  652. /*
  653.  *@@ doshQueryDriveType:
  654.  *      tests the specified BIOSPARAMETERBLOCK
  655.  *      for whether it represents a CD-ROM or
  656.  *      some other removeable drive type.
  657.  *
  658.  *      Returns one of:
  659.  *
  660.  *      --  DRVTYPE_HARDDISK (0)
  661.  *
  662.  *      --  DRVTYPE_PARTITIONABLEREMOVEABLE
  663.  *
  664.  *      --  DRVTYPE_CDROM
  665.  *
  666.  *      --  DRVTYPE_TAPE
  667.  *
  668.  *      --  DRVTYPE_VDISK
  669.  *
  670.  *      --  DRVTYPE_FLOPPY
  671.  *
  672.  *      --  DRVTYPE_UNKNOWN (255)
  673.  *
  674.  *      The BIOSPARAMETERBLOCK must be filled
  675.  *      first using doshQueryDiskParams.
  676.  *
  677.  *@@added V0.9.16 (2002-01-13) [umoeller]
  678.  */
  679.  
  680. BYTE doshQueryDriveType(ULONG ulLogicalDrive,
  681.                         PBIOSPARAMETERBLOCK pdp,
  682.                         BOOL fFixed)
  683. {
  684.     if (pdp)
  685.     {
  686.         if (pdp->fsDeviceAttr & DEVATTR_PARTITIONALREMOVEABLE) // 0x08
  687.             return DRVTYPE_PARTITIONABLEREMOVEABLE;
  688.         else if (fFixed)
  689.             return DRVTYPE_HARDDISK;
  690.         else if (    (pdp->bDeviceType == 7)     // "other"
  691.                   && (pdp->usBytesPerSector == 2048)
  692.                   && (pdp->usSectorsPerTrack == (USHORT)-1)
  693.                 )
  694.                  return DRVTYPE_CDROM;
  695.         else switch (pdp->bDeviceType)
  696.         {
  697.             case DEVTYPE_TAPE: // 6
  698.                 return DRVTYPE_TAPE;
  699.  
  700.             case DEVTYPE_48TPI:     // 0, 360k  5.25" floppy
  701.             case DEVTYPE_96TPI:     // 1, 1.2M  5.25" floppy
  702.             case DEVTYPE_35:        // 2, 720k  3.5" floppy
  703.             case DEVTYPE_OTHER:     // 7, 1.44  3.5" floppy
  704.                                     //    1.84M 3.5" floppy
  705.             case DEVTYPE_35_288MB:
  706.                 if (    (ulLogicalDrive == 1)
  707.                      || (ulLogicalDrive == 2)
  708.                    )
  709.                     return DRVTYPE_FLOPPY;
  710.                 else
  711.                     return DRVTYPE_VDISK;
  712.  
  713.             case DEVTYPE_RWOPTICAL: // 8, what is this?!?
  714.                 return DRVTYPE_FLOPPY;
  715.         }
  716.     }
  717.  
  718.     return (DRVTYPE_UNKNOWN);
  719. }
  720.  
  721. /*
  722.  *@@ doshHasAudioCD:
  723.  *      sets *pfAudio to whether ulLogicalDrive
  724.  *      currently has an audio CD inserted.
  725.  *
  726.  *      Better call this only if you're sure that
  727.  *      ulLogicalDrive is a CD-ROM drive. Use
  728.  *      doshQueryRemoveableType to check.
  729.  *
  730.  *@@added V0.9.14 (2001-08-01) [umoeller]
  731.  */
  732.  
  733. APIRET doshHasAudioCD(ULONG ulLogicalDrive,
  734.                       HFILE hfDrive,            // in: DASD open
  735.                       BOOL fMixedModeCD,
  736.                       PBOOL pfAudio)
  737. {
  738.     APIRET  arc = NO_ERROR;
  739.  
  740.     ULONG ulAudioTracks = 0,
  741.           ulDataTracks = 0;
  742.  
  743.     CHAR cds1[4] = { 'C', 'D', '0', '1' };
  744.     CHAR cds2[4];
  745.  
  746.     *pfAudio = FALSE;
  747.  
  748.     // check for proper driver signature
  749.     if (!(arc = doshDevIOCtl(hfDrive,
  750.                              IOCTL_CDROMDISK,
  751.                              CDROMDISK_GETDRIVER,
  752.                              &cds1, sizeof(cds1),
  753.                              &cds2, sizeof(cds2))))
  754.     {
  755.         if (memcmp(&cds1, &cds2, 4))
  756.             // this is not a CD-ROM then:
  757.             arc = NO_ERROR;
  758.         else
  759.         {
  760.             struct {
  761.                 UCHAR   ucFirstTrack,
  762.                         ucLastTrack;
  763.                 ULONG   ulLeadOut;
  764.             } cdat;
  765.  
  766.             // get track count
  767.             if (!(arc = doshDevIOCtl(hfDrive,
  768.                                      IOCTL_CDROMAUDIO,
  769.                                      CDROMAUDIO_GETAUDIODISK,
  770.                                      &cds1, sizeof(cds1),
  771.                                      &cdat, sizeof(cdat))))
  772.             {
  773.                 // still no error: build the audio TOC
  774.                 ULONG i;
  775.                 for (i = cdat.ucFirstTrack;
  776.                      i <= cdat.ucLastTrack;
  777.                      i++)
  778.                 {
  779.                     BYTE cdtp[5] =
  780.                       { 'C', 'D', '0', '1', (UCHAR)i };
  781.  
  782.                     struct {
  783.                         ULONG   ulTrackAddress;
  784.                         BYTE    bFlags;
  785.                     } trackdata;
  786.  
  787.                     if (!(arc = doshDevIOCtl(hfDrive,
  788.                                              IOCTL_CDROMAUDIO,
  789.                                              CDROMAUDIO_GETAUDIOTRACK,
  790.                                              &cdtp, sizeof(cdtp),
  791.                                              &trackdata, sizeof(trackdata))))
  792.                     {
  793.                         if (trackdata.bFlags & 64)
  794.                             ulDataTracks++;
  795.                         else
  796.                         {
  797.                             ulAudioTracks++;
  798.  
  799.                             if (!fMixedModeCD)
  800.                             {
  801.                                 // caller doesn't want mixed mode:
  802.                                 // stop here
  803.                                 ulDataTracks = 0;
  804.                                 break;
  805.                             }
  806.                         }
  807.                     }
  808.                 }
  809.  
  810.                 // _Pmpf(("   got %d audio, %d data tracks",
  811.                 //             ulAudioTracks, ulDataTracks));
  812.  
  813.                 if (!ulDataTracks)
  814.                     *pfAudio = TRUE;
  815.             }
  816.             else
  817.             {
  818.                 // not audio disk:
  819.                 // go on then
  820.                 // _Pmpf(("   CDROMAUDIO_GETAUDIODISK returned %d", arc));
  821.                 arc = NO_ERROR;
  822.             }
  823.         }
  824.     }
  825.     else
  826.     {
  827.         // not CD-ROM: go on then
  828.         // _Pmpf(("   CDROMDISK_GETDRIVER returned %d", arc));
  829.         arc = NO_ERROR;
  830.     }
  831.  
  832.     return arc;
  833. }
  834.  
  835. /*
  836.  *@@ doshEnumDrives:
  837.  *      this function enumerates all valid drive letters on
  838.  *      the system by composing a string of drive letters
  839.  *      in the buffer pointed to by pszBuffer, which should
  840.  *      be 27 characters in size to hold information for
  841.  *      all drives. The buffer will be null-terminated.
  842.  *
  843.  *      If (pcszFileSystem != NULL), only drives matching
  844.  *      the specified file system type (e.g. "HPFS") will
  845.  *      be enumerated. If (pcszFileSystem == NULL), all
  846.  *      drives will be enumerated.
  847.  *
  848.  *      If (fSkipRemovables == TRUE), removeable drives will
  849.  *      be skipped. This applies to floppy, CD-ROM, and
  850.  *      virtual floppy drives. This will start the search
  851.  *      at drive letter C: so that drives A: and B: will
  852.  *      never be checked (to avoid the hardware bumps).
  853.  *
  854.  *      Otherwise, the search starts at drive A:. Still,
  855.  *      removeable drives will only be added if valid media
  856.  *      is inserted.
  857.  *
  858.  *@@changed V0.9.4 (2000-07-03) [umoeller]: this stopped at the first invalid drive letter; fixed
  859.  *@@changed V0.9.4 (2000-07-03) [umoeller]: added fSkipRemoveables
  860.  */
  861.  
  862. VOID doshEnumDrives(PSZ pszBuffer,      // out: drive letters
  863.                     PCSZ pcszFileSystem,  // in: FS's to match or NULL
  864.                     BOOL fSkipRemoveables) // in: if TRUE, only non-removeable disks will be returned
  865. {
  866.     CHAR    szName[5] = "";
  867.     ULONG   ulLogicalDrive = 1, // start with drive A:
  868.             ulFound = 0;        // found drives count
  869.     APIRET  arc = NO_ERROR; // return code
  870.  
  871.     if (fSkipRemoveables)
  872.         // start with drive C:
  873.         ulLogicalDrive = 3;
  874.  
  875.     // go thru the drives, start with C: (== 3), stop after Z: (== 26)
  876.     while (ulLogicalDrive <= 26)
  877.     {
  878.         #pragma pack(1)
  879.         struct
  880.         {
  881.             UCHAR dummy,drive;
  882.         } parms;
  883.         #pragma pack()
  884.  
  885.         // data packet
  886.         UCHAR nonRemovable=0;
  887.  
  888.         parms.drive=(UCHAR)(ulLogicalDrive-1);
  889.         arc = doshDevIOCtl((HFILE)-1,
  890.                            IOCTL_DISK,
  891.                            DSK_BLOCKREMOVABLE,
  892.                            &parms, sizeof(parms),
  893.                            &nonRemovable, sizeof(nonRemovable));
  894.  
  895.         if (    // fixed disk and non-removeable
  896.                 ((arc == NO_ERROR) && (nonRemovable))
  897.                 // or network drive:
  898.              || (arc == ERROR_NOT_SUPPORTED)
  899.            )
  900.         {
  901.             ULONG  ulOrdinal       = 0;     // ordinal of entry in name list
  902.             BYTE   fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
  903.             ULONG  cbBuffer   = sizeof(fsqBuffer);        // Buffer length)
  904.             PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
  905.  
  906.             szName[0] = ulLogicalDrive + 'A' - 1;
  907.             szName[1] = ':';
  908.             szName[2] = '\0';
  909.  
  910.             arc = DosQueryFSAttach(szName,          // logical drive of attached FS
  911.                                    ulOrdinal,       // ignored for FSAIL_QUERYNAME
  912.                                    FSAIL_QUERYNAME, // return data for a Drive or Device
  913.                                    pfsqBuffer,      // returned data
  914.                                    &cbBuffer);      // returned data length
  915.  
  916.             if (arc == NO_ERROR)
  917.             {
  918.                 // The data for the last three fields in the FSQBUFFER2
  919.                 // structure are stored at the offset of fsqBuffer.szName.
  920.                 // Each data field following fsqBuffer.szName begins
  921.                 // immediately after the previous item.
  922.                 CHAR* pszFSDName = (PSZ)&(pfsqBuffer->szName) + (pfsqBuffer->cbName) + 1;
  923.                 if (pcszFileSystem == NULL)
  924.                 {
  925.                     // enum-all mode: always copy
  926.                     pszBuffer[ulFound] = szName[0]; // drive letter
  927.                     ulFound++;
  928.                 }
  929.                 else if (strcmp(pszFSDName, pcszFileSystem) == 0)
  930.                 {
  931.                     pszBuffer[ulFound] = szName[0]; // drive letter
  932.                     ulFound++;
  933.                 }
  934.             }
  935.         }
  936.  
  937.         ulLogicalDrive++;
  938.     } // end while (G_acDriveLetters[ulLogicalDrive] <= 'Z')
  939.  
  940.     pszBuffer[ulFound] = '\0';
  941. }
  942.  
  943. /*
  944.  *@@ doshQueryBootDrive:
  945.  *      returns the letter of the boot drive as a
  946.  *      single (capital) character, which is useful for
  947.  *      constructing file names using sprintf and such.
  948.  *
  949.  *@@changed V0.9.16 (2002-01-13) [umoeller]: optimized
  950.  */
  951.  
  952. CHAR doshQueryBootDrive(VOID)
  953. {
  954.     // this can never change, so query this only once
  955.     // V0.9.16 (2002-01-13) [umoeller]
  956.     static CHAR     cBootDrive = '\0';
  957.  
  958.     if (!cBootDrive)
  959.     {
  960.         ULONG ulBootDrive;
  961.         DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
  962.                         &ulBootDrive,
  963.                         sizeof(ulBootDrive));
  964.         cBootDrive = (CHAR)ulBootDrive + 'A' - 1;
  965.     }
  966.  
  967.     return (cBootDrive);
  968. }
  969.  
  970. /*
  971.  *@@ doshQueryMedia:
  972.  *      determines whether the given drive currently
  973.  *      has media inserted.
  974.  *
  975.  *      Call this only for non-fixed (removable) disks.
  976.  *      Use doshIsFixedDisk to find out.
  977.  *
  978.  *@@added V0.9.16 (2002-01-13) [umoeller]
  979.  */
  980.  
  981. APIRET doshQueryMedia(ULONG ulLogicalDrive,
  982.                       BOOL fCDROM,             // in: is drive CD-ROM?
  983.                       ULONG fl)                // in: DRVFL_* flags
  984. {
  985.     APIRET  arc;
  986.  
  987.     HFILE   hf = NULLHANDLE;
  988.     ULONG   dummy;
  989.  
  990.     CHAR    szDrive[3] = "C:";
  991.     szDrive[0] = 'A' + ulLogicalDrive - 1;
  992.  
  993.     arc = DosOpen(szDrive,   // "C:", "D:", ...
  994.                   &hf,
  995.                   &dummy,
  996.                   0,
  997.                   FILE_NORMAL,
  998.                   OPEN_ACTION_FAIL_IF_NEW
  999.                          | OPEN_ACTION_OPEN_IF_EXISTS,
  1000.                   OPEN_FLAGS_DASD
  1001.                          | OPEN_FLAGS_FAIL_ON_ERROR
  1002.                          | OPEN_FLAGS_NOINHERIT     // V0.9.6 (2000-11-25) [pr]
  1003.               //            | OPEN_ACCESS_READONLY  // V0.9.13 (2001-06-14) [umoeller]
  1004.                          | OPEN_SHARE_DENYNONE,
  1005.                   NULL);
  1006.  
  1007.     // this still returns NO_ERROR for audio CDs in a
  1008.     // CD-ROM drive...
  1009.     // however, the WPS then attempts to read in the
  1010.     // root directory for audio CDs, which produces
  1011.     // a "sector not found" error box...
  1012.  
  1013.     if (    (!arc)
  1014.          && (hf)
  1015.          && (fCDROM)
  1016.        )
  1017.     {
  1018.         BOOL fAudio;
  1019.         if (    (!(arc = doshHasAudioCD(ulLogicalDrive,
  1020.                                         hf,
  1021.                                         ((fl & DRVFL_MIXEDMODECD) != 0),
  1022.                                         &fAudio)))
  1023.              && (fAudio)
  1024.            )
  1025.             arc = ERROR_AUDIO_CD_ROM;       // special private error code (10000)
  1026.     }
  1027.  
  1028.     if (hf)
  1029.         DosClose(hf);
  1030.  
  1031.     return arc;
  1032. }
  1033.  
  1034. /*
  1035.  *@@ doshAssertDrive:
  1036.  *      this checks for whether the given drive
  1037.  *      is currently available without provoking
  1038.  *      those ugly white "Drive not ready" popups.
  1039.  *
  1040.  *      "fl" can specify additional flags for testing
  1041.  *      and can be any combination of:
  1042.  *
  1043.  *      --  DRVFL_MIXEDMODECD: whether to allow
  1044.  *          mixed-mode CD-ROMs. See error codes below.
  1045.  *
  1046.  *      This returns (from my testing):
  1047.  *
  1048.  *      -- NO_ERROR: drive is available.
  1049.  *
  1050.  *      -- ERROR_INVALID_DRIVE (15): drive letter does not exist.
  1051.  *
  1052.  *      -- ERROR_NOT_READY (21): drive exists, but is not ready.
  1053.  *                  This is produced by floppies and CD-ROM drives
  1054.  *                  which do not have valid media inserted.
  1055.  *
  1056.  *      -- ERROR_AUDIO_CD_ROM (10000): special error code returned
  1057.  *                  only by this function if a CD-ROM drive has audio
  1058.  *                  media inserted.
  1059.  *
  1060.  *                  If DRVFL_MIXEDMODECD was specified, ERROR_AUDIO_CD_ROM
  1061.  *                  is returned _only_ if _no_ data tracks are
  1062.  *                  present on a CD-ROM. Since OS/2 is not very
  1063.  *                  good at handling mixed-mode CDs, this might not
  1064.  *                  be desireable.
  1065.  *
  1066.  *                  If DRVFL_MIXEDMODECD was not set, ERROR_AUDIO_CD_ROM
  1067.  *                  will be returned already if _one_ audio track is present.
  1068.  *
  1069.  *@@changed V0.9.1 (99-12-13) [umoeller]: rewritten, prototype changed. Now using DosOpen on the drive instead of DosError.
  1070.  *@@changed V0.9.1 (2000-01-08) [umoeller]: DosClose was called even if DosOpen failed, which messed up OS/2 error handling.
  1071.  *@@changed V0.9.1 (2000-02-09) [umoeller]: this didn't work for network drives, including RAMFS; fixed.
  1072.  *@@changed V0.9.3 (2000-03-28) [umoeller]: added check for network drives, which weren't working
  1073.  *@@changed V0.9.4 (2000-08-03) [umoeller]: more network fixes
  1074.  *@@changed V0.9.9 (2001-03-19) [pr]: validate drive number
  1075.  *@@changed V0.9.11 (2001-04-23) [umoeller]: added an extra check for floppies
  1076.  *@@changed V0.9.13 (2001-06-14) [umoeller]: added "fl" parameter and lots of CD-ROM checks
  1077.  */
  1078.  
  1079. APIRET doshAssertDrive(ULONG ulLogicalDrive,    // in: 1 for A:, 2 for B:, 3 for C:, ...
  1080.                        ULONG fl)                // in: DRVFL_* flags
  1081. {
  1082.     APIRET  arc = NO_ERROR;
  1083.     BOOL    fFixed = FALSE,
  1084.             fCDROM = FALSE;
  1085.  
  1086.     if ((ulLogicalDrive < 1) || (ulLogicalDrive > 26))
  1087.         return(ERROR_PATH_NOT_FOUND);
  1088.  
  1089.     arc = doshIsFixedDisk(ulLogicalDrive,
  1090.                           &fFixed);    // V0.9.13 (2001-06-14) [umoeller]
  1091.  
  1092.     // _Pmpf((__FUNCTION__ ": doshIsFixedDisk returned %d for disk %d", arc, ulLogicalDrive));
  1093.     // _Pmpf(("   fFixed is %d", fFixed));
  1094.  
  1095.     if (!arc)
  1096.         if (!fFixed)
  1097.         {
  1098.             // removeable disk:
  1099.             // check if it's a CD-ROM
  1100.             BIOSPARAMETERBLOCK bpb;
  1101.             arc = doshQueryDiskParams(ulLogicalDrive,
  1102.                                       &bpb);
  1103.             // _Pmpf(("   doshQueryDiskParams returned %d", arc));
  1104.  
  1105.             if (    (!arc)
  1106.                  && (DRVTYPE_CDROM == doshQueryDriveType(ulLogicalDrive,
  1107.                                                          &bpb,
  1108.                                                          fFixed))
  1109.                )
  1110.             {
  1111.                 // _Pmpf(("   --> is CD-ROM"));
  1112.                 fCDROM = TRUE;
  1113.             }
  1114.         }
  1115.  
  1116.     if (!arc)
  1117.         arc = doshQueryMedia(ulLogicalDrive,
  1118.                              fCDROM,
  1119.                              fl);
  1120.  
  1121.     switch (arc)
  1122.     {
  1123.         case ERROR_NETWORK_ACCESS_DENIED: // 65
  1124.             // added V0.9.3 (2000-03-27) [umoeller];
  1125.             // according to user reports, this is returned
  1126.             // by all network drives, which apparently don't
  1127.             // support DASD DosOpen
  1128.         case ERROR_ACCESS_DENIED: // 5
  1129.             // added V0.9.4 (2000-07-10) [umoeller]
  1130.             // LAN drives still didn't work... apparently
  1131.             // the above only works for NFS drives
  1132.         case ERROR_PATH_NOT_FOUND: // 3
  1133.             // added V0.9.4 (2000-08-03) [umoeller]:
  1134.             // this is returned by some other network types...
  1135.             // sigh...
  1136.         case ERROR_NOT_SUPPORTED: // 50
  1137.             // this is returned by file systems which don't
  1138.             // support DASD DosOpen;
  1139.             // use some other method then, this isn't likely
  1140.             // to fail -- V0.9.1 (2000-02-09) [umoeller]
  1141.  
  1142.             // but don't do this for floppies
  1143.             // V0.9.11 (2001-04-23) [umoeller]
  1144.             if (ulLogicalDrive > 2)
  1145.             {
  1146.                 FSALLOCATE  fsa;
  1147.                 arc = DosQueryFSInfo(ulLogicalDrive,
  1148.                                      FSIL_ALLOC,
  1149.                                      &fsa,
  1150.                                      sizeof(fsa));
  1151.                 // _Pmpf(("   re-checked, DosQueryFSInfo returned %d", arc));
  1152.             }
  1153.         break;
  1154.     }
  1155.  
  1156.     return arc;
  1157. }
  1158.  
  1159. /*
  1160.  *@@ doshGetDriveInfo:
  1161.  *      fills the given XDISKINFO buffer with
  1162.  *      information about the given logical drive.
  1163.  *
  1164.  *      This function will not provoke "Drive not
  1165.  *      ready" popups, hopefully.
  1166.  *
  1167.  *      fl can be any combination of the following:
  1168.  *
  1169.  *      --  DRVFL_MIXEDMODECD: see doshAssertDrive.
  1170.  *
  1171.  *      --  DRVFL_TOUCHFLOPPIES: drive A: and B: should
  1172.  *          be touched for media checks (click, click);
  1173.  *          otherwise they will be left alone and
  1174.  *          default values will be returned.
  1175.  *
  1176.  *      --  DRVFL_CHECKEAS: drive should always be
  1177.  *          checked for EA support. If this is set,
  1178.  *          we will call DosFSCtl for the non-well-known
  1179.  *          file systems so we will always have a
  1180.  *          value for the DFL_SUPPORTS_EAS flags.
  1181.  *          Otherwise that flag might or might not
  1182.  *          be set correctly.
  1183.  *
  1184.  *          The EA support returned by DosFSCtl
  1185.  *          might not be correct for remote file
  1186.  *          systems since not all of them support
  1187.  *          that query.
  1188.  *
  1189.  *      --  DRVFL_CHECKLONGNAMES: drive should always be
  1190.  *          checked for longname support. If this is
  1191.  *          set, we will try a DosOpen("\\long.name.file")
  1192.  *          on the drive to see if it supports long
  1193.  *          filenames (unless it's a "well-known"
  1194.  *          file-system and we know it does). If enabled,
  1195.  *          the DFL_SUPPORTS_LONGNAMES flag is reliable.
  1196.  *          Note that this does not check for what special
  1197.  *          characters are supported in file names.
  1198.  *
  1199.  *      This should return only one of the following:
  1200.  *
  1201.  *      --  NO_ERROR: disk info was filled, but not
  1202.  *          necessarily all info was available (e.g.
  1203.  *          if no media was present in CD-ROM drive).
  1204.  *          See remarks below.
  1205.  *
  1206.  *      --  ERROR_INVALID_DRIVE 15): ulLogicalDrive
  1207.  *          is not used at all (invalid drive letter)
  1208.  *
  1209.  *      --  ERROR_BAD_UNIT (20): if drive was renamed for
  1210.  *          some reason (according to user reports
  1211.  *
  1212.  *      --  ERROR_NOT_READY (21): for ZIP disks where
  1213.  *          no media is inserted, depending on the
  1214.  *          driver apparently... normally ZIP drive
  1215.  *          letters should disappear when no media
  1216.  *          is present
  1217.  *
  1218.  *      --  ERROR_DRIVE_LOCKED (108)
  1219.  *
  1220.  *      So in order to check whether a drive is present
  1221.  *      and available, use this function as follows:
  1222.  *
  1223.  *      1)  Call this function and check whether it
  1224.  *          returns NO_ERROR for the given drive.
  1225.  *          This will rule out invalid drive letters
  1226.  *          and drives that are presently locked by
  1227.  *          CHKDSK or something.
  1228.  *
  1229.  *      2)  If so, check whether XDISKINFO.flDevice
  1230.  *          has the DFL_MEDIA_PRESENT flag set.
  1231.  *          This will rule out removeable drives without
  1232.  *          media and unformatted hard disks.
  1233.  *
  1234.  *      3)  If so, you can test the other fields if
  1235.  *          you need more information. For example,
  1236.  *          it would not be a good idea to create
  1237.  *          a new file if the bType field is
  1238.  *          DRVTYPE_CDROM.
  1239.  *
  1240.  *          If you want to exclude removeable disks,
  1241.  *          instead of checking bType, you should
  1242.  *          rather check flDevice for the DFL_FIXED
  1243.  *          flag, which will be set for ZIP drives also.
  1244.  *
  1245.  *      Remarks for special drive types:
  1246.  *
  1247.  *      --  Hard disks always have bType == DRVTYPE_HARDDISK.
  1248.  *          For them, we always check the file system.
  1249.  *          If this is reported as "UNKNOWN", this means
  1250.  *          that the drive is unformatted or formatted
  1251.  *          with a file system that OS/2 does not understand
  1252.  *          (e.g. NTFS). Only in that case, flDevice
  1253.  *          has the DFL_MEDIA_PRESENT bit clear.
  1254.  *
  1255.  *          DFL_FIXED is always set.
  1256.  *
  1257.  *      --  Remote (LAN) drives always have bType == DRVTYPE_LAN.
  1258.  *          flDevice will always have the DFL_REMOTE and
  1259.  *          DFL_MEDIA_PRESENT bits set.
  1260.  *
  1261.  *      --  ZIP disks will have bType == DRVTYPE_PARTITIONABLEREMOVEABLE.
  1262.  *          For them, flDevice will have both the
  1263.  *          and DFL_PARTITIONABLEREMOVEABLE and DFL_FIXED
  1264.  *          bits set.
  1265.  *
  1266.  *          ZIP disks are a bit special because they are
  1267.  *          dynamically mounted and unmounted when media
  1268.  *          is inserted and removed. In other words, if
  1269.  *          no media is present, the drive letter becomes
  1270.  *          invalid.
  1271.  *
  1272.  *      --  CD-ROM and DVD drives and CD writers will always
  1273.  *          be reported as DRVTYPE_CDROM. The DFL_FIXED bit
  1274.  *          will be clear always. For them, always check the
  1275.  *          DFL_MEDIA_PRESENT present bit to avoid "Drive not
  1276.  *          ready" popups.
  1277.  *
  1278.  *          As a special goody, we can also determine if the
  1279.  *          drive currently has audio media inserted (which
  1280.  *          would provoke errors also), by setting the
  1281.  *          DFL_AUDIO_CD bit.
  1282.  *
  1283.  *@@added V0.9.16 (2002-01-13) [umoeller]
  1284.  *@@changed V0.9.19 (2002-04-25) [umoeller]: added CDWFS (RSJ CD-Writer)
  1285.  */
  1286.  
  1287. APIRET doshGetDriveInfo(ULONG ulLogicalDrive,
  1288.                         ULONG fl,               // in: DRVFL_* flags
  1289.                         PXDISKINFO pdi)
  1290. {
  1291.     APIRET  arc = NO_ERROR;
  1292.  
  1293.     HFILE   hf;
  1294.     ULONG   dummy;
  1295.     BOOL    fCheck = TRUE,
  1296.             fCheckFS = FALSE,
  1297.             fCheckLongnames = FALSE,
  1298.             fCheckEAs = FALSE;
  1299.  
  1300.     memset(pdi, 0, sizeof(XDISKINFO));
  1301.  
  1302.     pdi->cDriveLetter = 'A' + ulLogicalDrive - 1;
  1303.     pdi->cLogicalDrive = ulLogicalDrive;
  1304.  
  1305.     pdi->bType = DRVTYPE_UNKNOWN;
  1306.     pdi->fPresent = TRUE;       // for now
  1307.  
  1308.     if (    (ulLogicalDrive == 1)
  1309.          || (ulLogicalDrive == 2)
  1310.        )
  1311.     {
  1312.         // drive A: and B: are special cases,
  1313.         // we don't even want to touch them (click, click)
  1314.         pdi->bType = DRVTYPE_FLOPPY;
  1315.  
  1316.         if (0 == (fl & DRVFL_TOUCHFLOPPIES))
  1317.         {
  1318.             fCheck = FALSE;
  1319.             // these support EAs too
  1320.             pdi->flDevice  = DFL_MEDIA_PRESENT | DFL_SUPPORTS_EAS;
  1321.             strcpy(pdi->szFileSystem, "FAT");
  1322.             pdi->lFileSystem = FSYS_FAT;
  1323.         }
  1324.     }
  1325.  
  1326.     if (fCheck)
  1327.     {
  1328.         // any other drive:
  1329.         // check if it's removeable first
  1330.         BOOL    fFixed = FALSE;
  1331.         arc = doshIsFixedDisk(ulLogicalDrive,
  1332.                               &fFixed);
  1333.  
  1334.         switch (arc)
  1335.         {
  1336.             case ERROR_INVALID_DRIVE:
  1337.                 // drive letter doesn't exist at all:
  1338.                 pdi->fPresent = FALSE;
  1339.                 // return this APIRET
  1340.             break;
  1341.  
  1342.             case ERROR_NOT_SUPPORTED:       // 50 for network drives
  1343.                 // we get this for remote drives added
  1344.                 // via "net use", so set these flags
  1345.                 pdi->bType = DRVTYPE_LAN;
  1346.                 pdi->lFileSystem = FSYS_REMOTE;
  1347.                 pdi->flDevice |= DFL_REMOTE | DFL_MEDIA_PRESENT;
  1348.                 // but still check what file-system we
  1349.                 // have and whether longnames are supported
  1350.                 fCheckFS = TRUE;
  1351.                 fCheckLongnames = TRUE;
  1352.                 fCheckEAs = TRUE;
  1353.             break;
  1354.  
  1355.             case NO_ERROR:
  1356.             {
  1357.                 if (fFixed)
  1358.                 {
  1359.                     // fixed drive:
  1360.                     pdi->flDevice |= DFL_FIXED | DFL_MEDIA_PRESENT;
  1361.  
  1362.                     fCheckFS = TRUE;
  1363.                     fCheckLongnames = TRUE;
  1364.                     fCheckEAs = TRUE;
  1365.                 }
  1366.  
  1367.                 if (!(arc = doshQueryDiskParams(ulLogicalDrive,
  1368.                                                 &pdi->bpb)))
  1369.                 {
  1370.                     BYTE bTemp = doshQueryDriveType(ulLogicalDrive,
  1371.                                                     &pdi->bpb,
  1372.                                                     fFixed);
  1373.                     if (bTemp != DRVTYPE_UNKNOWN)
  1374.                     {
  1375.                         // recognized: store it then
  1376.                         pdi->bType = bTemp;
  1377.  
  1378.                         if (bTemp == DRVTYPE_PARTITIONABLEREMOVEABLE)
  1379.                             pdi->flDevice |=    DFL_FIXED
  1380.                                               | DFL_PARTITIONABLEREMOVEABLE;
  1381.                     }
  1382.  
  1383.                     if (!fFixed)
  1384.                     {
  1385.                         // removeable:
  1386.  
  1387.                         // before checking the drive, try if we have media
  1388.                         if (!(arc = doshQueryMedia(ulLogicalDrive,
  1389.                                                    (pdi->bType == DRVTYPE_CDROM),
  1390.                                                    fl)))
  1391.                         {
  1392.                             pdi->flDevice |= DFL_MEDIA_PRESENT;
  1393.                             fCheckFS = TRUE;
  1394.                             fCheckLongnames = TRUE;
  1395.                                     // but never EAs
  1396.                         }
  1397.                         else if (arc == ERROR_AUDIO_CD_ROM)
  1398.                         {
  1399.                             pdi->flDevice |= DFL_AUDIO_CD;
  1400.                             // do not check longnames and file-system
  1401.                         }
  1402.                         else
  1403.                             pdi->arcQueryMedia = arc;
  1404.  
  1405.                         arc = NO_ERROR;
  1406.                     }
  1407.                 }
  1408.                 else
  1409.                     pdi->arcQueryDiskParams = arc;
  1410.             }
  1411.             break;
  1412.  
  1413.             default:
  1414.                 pdi->arcIsFixedDisk = arc;
  1415.                 // and return this
  1416.             break;
  1417.  
  1418.         } // end swich arc = doshIsFixedDisk(ulLogicalDrive, &fFixed);
  1419.     }
  1420.  
  1421.     if (fCheckFS)
  1422.     {
  1423.         // TRUE only for local fixed disks or
  1424.         // remote drives or if media was present above
  1425.         if (!(arc = doshQueryDiskFSType(ulLogicalDrive,
  1426.                                         pdi->szFileSystem,
  1427.                                         sizeof(pdi->szFileSystem))))
  1428.         {
  1429.             if (!stricmp(pdi->szFileSystem, "UNKNOWN"))
  1430.             {
  1431.                 // this is returned by the stupid DosQueryFSAttach
  1432.                 // if the file system is not recognized by OS/2,
  1433.                 // or if the drive is unformatted
  1434.                 pdi->lFileSystem = FSYS_UNKNOWN;
  1435.                 pdi->flDevice &= ~DFL_MEDIA_PRESENT;
  1436.                 fCheckLongnames = FALSE;
  1437.                 fCheckEAs = FALSE;
  1438.                         // should we return ERROR_NOT_DOS_DISK (26)
  1439.                         // in this case?
  1440.             }
  1441.             else if (!stricmp(pdi->szFileSystem, "FAT"))
  1442.             {
  1443.                 pdi->lFileSystem = FSYS_FAT;
  1444.                 pdi->flDevice |= DFL_SUPPORTS_EAS;
  1445.                 fCheckLongnames = FALSE;
  1446.                 fCheckEAs = FALSE;
  1447.             }
  1448.             else if (    (!stricmp(pdi->szFileSystem, "HPFS"))
  1449.                       || (!stricmp(pdi->szFileSystem, "JFS"))
  1450.                     )
  1451.             {
  1452.                 pdi->lFileSystem = FSYS_HPFS_JFS;
  1453.                 pdi->flDevice |= DFL_SUPPORTS_EAS | DFL_SUPPORTS_LONGNAMES;
  1454.                 fCheckLongnames = FALSE;
  1455.                 fCheckEAs = FALSE;
  1456.             }
  1457.             else if (!stricmp(pdi->szFileSystem, "CDFS"))
  1458.                 pdi->lFileSystem = FSYS_CDFS;
  1459.             else if (    (!stricmp(pdi->szFileSystem, "FAT32"))
  1460.                       || (!stricmp(pdi->szFileSystem, "ext2"))
  1461.                     )
  1462.             {
  1463.                 pdi->lFileSystem = FSYS_FAT32_EXT2;
  1464.                 fCheckLongnames = TRUE;
  1465.                 fCheckEAs = TRUE;
  1466.             }
  1467.             else if (!stricmp(pdi->szFileSystem, "RAMFS"))
  1468.             {
  1469.                 pdi->lFileSystem = FSYS_RAMFS;
  1470.                 pdi->flDevice |= DFL_SUPPORTS_EAS | DFL_SUPPORTS_LONGNAMES;
  1471.                 fCheckLongnames = FALSE;
  1472.                 fCheckEAs = FALSE;
  1473.             }
  1474.             else if (!stricmp(pdi->szFileSystem, "TVFS"))
  1475.             {
  1476.                 pdi->lFileSystem = FSYS_TVFS;
  1477.                 fCheckLongnames = TRUE;
  1478.                 fCheckEAs = TRUE;
  1479.             }
  1480.             else if (!stricmp(pdi->szFileSystem, "CDWFS"))
  1481.                     // V0.9.19 (2002-04-25) [umoeller]
  1482.             {
  1483.                 pdi->lFileSystem = FSYS_CDWFS;
  1484.                 pdi->flDevice |= DFL_SUPPORTS_LONGNAMES;
  1485.                 fCheckLongnames = FALSE;
  1486.                 fCheckEAs = FALSE;
  1487.             }
  1488.         }
  1489.         else
  1490.             // store negative error code
  1491.             pdi->lFileSystem = -(LONG)arc;
  1492.     }
  1493.  
  1494.     if (    (!arc)
  1495.          && (fCheckLongnames)
  1496.          && (fl & DRVFL_CHECKLONGNAMES)
  1497.        )
  1498.     {
  1499.         CHAR szTemp[30] = "?:\\long.name.file";
  1500.         szTemp[0]  = ulLogicalDrive + 'A' - 1;
  1501.         if (!(arc = DosOpen(szTemp,
  1502.                             &hf,
  1503.                             &dummy,
  1504.                             0,
  1505.                             0,
  1506.                             FILE_READONLY,
  1507.                             OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT,
  1508.                             0)))
  1509.         {
  1510.             DosClose(hf);
  1511.         }
  1512.  
  1513.         switch (arc)
  1514.         {
  1515.             case NO_ERROR:
  1516.             case ERROR_OPEN_FAILED:
  1517.             case ERROR_FILE_NOT_FOUND:      // returned by TVFS
  1518.                 pdi->flDevice |= DFL_SUPPORTS_LONGNAMES;
  1519.             break;
  1520.  
  1521.             // if longnames are not supported,
  1522.             // we get ERROR_INVALID_NAME
  1523.             default:
  1524.                 pdi->arcOpenLongnames = arc;
  1525.             break;
  1526.  
  1527.             // default:
  1528.                //  printf("      drive %d returned %d\n", ulLogicalDrive, arc);
  1529.         }
  1530.  
  1531.         arc = NO_ERROR;
  1532.     }
  1533.  
  1534.     if (    (!arc)
  1535.          && (fCheckEAs)
  1536.          && (fl & DRVFL_CHECKEAS)
  1537.        )
  1538.     {
  1539.         EASIZEBUF easb = {0};
  1540.         ULONG   cbData = sizeof(easb),
  1541.                 cbParams = 0;
  1542.         CHAR    szDrive[] = "?:\\";
  1543.         szDrive[0] = pdi->cDriveLetter;
  1544.         if (!(arc = DosFSCtl(&easb,
  1545.                              cbData,
  1546.                              &cbData,
  1547.                              NULL, // params,
  1548.                              cbParams,
  1549.                              &cbParams,
  1550.                              FSCTL_MAX_EASIZE,
  1551.                              szDrive,
  1552.                              -1,        // HFILE
  1553.                              FSCTL_PATHNAME)))
  1554.             if (easb.cbMaxEASize != 0)
  1555.                         // the other field (cbMaxEAListSize) is 0 always, I think
  1556.                 pdi->flDevice |= DFL_SUPPORTS_EAS;
  1557.     }
  1558.  
  1559.     if (doshQueryBootDrive() == pdi->cDriveLetter)
  1560.         pdi->flDevice |= DFL_BOOTDRIVE;
  1561.  
  1562.     return arc;
  1563. }
  1564.  
  1565. /*
  1566.  *@@ doshSetLogicalMap:
  1567.  *      sets the mapping of logical floppy drives onto a single
  1568.  *      physical floppy drive.
  1569.  *      This means selecting either drive A: or drive B: to refer
  1570.  *      to the physical drive.
  1571.  *
  1572.  *      Paul explained this to me as follows:
  1573.  *
  1574.  *      "It is really very simple - in a single physical floppy
  1575.  *      drive system,  you still have 2 logical floppy drives
  1576.  *      A: and B:. This was primarily to  support disk copying
  1577.  *      e.g. diskcopy a: b: on a single floppy system without
  1578.  *      having to rewrite applications. Whenever the application
  1579.  *      accessed the other logical drive, the user would get a
  1580.  *      prompt from the OS to swap disks.
  1581.  *
  1582.  *      "These calls allow applications to bypass the prompt by
  1583.  *      doing the mapping themselves. They just get/set which
  1584.  *      logical drive is currently mapped to the physical drive.
  1585.  *      This concept existed in DOS as well although the specifics
  1586.  *      of how it was done escape me now.... actually I just
  1587.  *      looked it up - the byte at 0:504h in low memory on
  1588.  *      DOS controlled this (it doesn't work in VDMs)."
  1589.  *
  1590.  *@@added V0.9.6 (2000-11-24) [pr]
  1591.  */
  1592.  
  1593. APIRET doshSetLogicalMap(ULONG ulLogicalDrive)
  1594. {
  1595.     CHAR    name[3] = "?:";
  1596.     ULONG   fd = 0,
  1597.             action = 0;
  1598. //             paramsize = 0;
  1599. //             datasize = 0;
  1600.     APIRET  rc = NO_ERROR;
  1601.     USHORT  data,
  1602.             param;
  1603.  
  1604.     name[0] = doshQueryBootDrive();
  1605.     rc = DosOpen(name,
  1606.                  &fd,
  1607.                  &action,
  1608.                  0,
  1609.                  0,
  1610.                  OPEN_ACTION_FAIL_IF_NEW
  1611.                           | OPEN_ACTION_OPEN_IF_EXISTS,
  1612.                  OPEN_FLAGS_DASD
  1613.                        | OPEN_FLAGS_FAIL_ON_ERROR
  1614.                        | OPEN_FLAGS_NOINHERIT
  1615.                        | OPEN_ACCESS_READONLY
  1616.                        | OPEN_SHARE_DENYNONE,
  1617.                  0);
  1618.  
  1619.     if (rc == NO_ERROR)
  1620.     {
  1621.         param = 0;
  1622.         data = (USHORT)ulLogicalDrive;
  1623.         // paramsize = sizeof(param);
  1624.         // datasize = sizeof(data);
  1625.         rc = doshDevIOCtl(fd,
  1626.                           IOCTL_DISK, DSK_SETLOGICALMAP,
  1627.                           ¶m, sizeof(param),
  1628.                           &data, sizeof(data));
  1629.         DosClose(fd);
  1630.     }
  1631.  
  1632.     return(rc);
  1633. }
  1634.  
  1635. /*
  1636.  *@@ doshQueryDiskSize:
  1637.  *      returns the size of the specified disk in bytes.
  1638.  *
  1639.  *      Note: This returns a "double" value, because a ULONG
  1640.  *      can only hold values of some 4 billion, which would
  1641.  *      lead to funny results for drives > 4 GB.
  1642.  *
  1643.  *@@added V0.9.11 (2001-04-18) [umoeller]
  1644.  */
  1645.  
  1646. APIRET doshQueryDiskSize(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
  1647.                          double *pdSize)
  1648. {
  1649.     APIRET      arc = NO_ERROR;
  1650.     FSALLOCATE  fsa;
  1651.     // double      dbl = -1;
  1652.  
  1653.     if (!(arc = DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa))))
  1654.         *pdSize = ((double)fsa.cSectorUnit * fsa.cbSector * fsa.cUnit);
  1655.  
  1656.     return arc;
  1657. }
  1658.  
  1659. /*
  1660.  *@@ doshQueryDiskFree:
  1661.  *      returns the number of bytes remaining on the disk
  1662.  *      specified by the given logical drive.
  1663.  *
  1664.  *      Note: This returns a "double" value, because a ULONG
  1665.  *      can only hold values of some 4 billion, which would
  1666.  *      lead to funny results for drives > 4 GB.
  1667.  *
  1668.  *@@changed V0.9.0 [umoeller]: fixed another > 4 GB bug (thanks to Rüdiger Ihle)
  1669.  *@@changed V0.9.7 (2000-12-01) [umoeller]: changed prototype
  1670.  */
  1671.  
  1672. APIRET doshQueryDiskFree(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
  1673.                          double *pdFree)
  1674. {
  1675.     APIRET      arc = NO_ERROR;
  1676.     FSALLOCATE  fsa;
  1677.     // double      dbl = -1;
  1678.  
  1679.     if (!(arc = DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa))))
  1680.         *pdFree = ((double)fsa.cSectorUnit * fsa.cbSector * fsa.cUnitAvail);
  1681.                    // ^ fixed V0.9.0
  1682.  
  1683.     return arc;
  1684. }
  1685.  
  1686. /*
  1687.  *@@ doshQueryDiskFSType:
  1688.  *       copies the file-system type of the given disk object
  1689.  *       (HPFS, FAT, CDFS etc.) to pszBuf.
  1690.  *       Returns the DOS error code.
  1691.  *
  1692.  *@@changed V0.9.1 (99-12-12) [umoeller]: added cbBuf to prototype
  1693.  *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed, this never respected cbBuf
  1694.  *@@changed V0.9.16 (2001-10-02) [umoeller]: added check for valid logical disk no
  1695.  */
  1696.  
  1697. APIRET doshQueryDiskFSType(ULONG ulLogicalDrive, // in:  1 for A:, 2 for B:, 3 for C:, ...
  1698.                            PSZ pszBuf,           // out: buffer for FS type
  1699.                            ULONG cbBuf)          // in: size of that buffer
  1700. {
  1701.     APIRET arc = NO_ERROR;
  1702.     CHAR szName[5];
  1703.  
  1704.     BYTE   fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
  1705.     ULONG  cbBuffer   = sizeof(fsqBuffer);        // Buffer length)
  1706.     PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
  1707.  
  1708.     // compose "D:"-type string from logical drive letter
  1709.     if (ulLogicalDrive > 0 && ulLogicalDrive < 27)
  1710.     {
  1711.         szName[0] = ulLogicalDrive + 'A' - 1;
  1712.         szName[1] = ':';
  1713.         szName[2] = '\0';
  1714.  
  1715.         arc = DosQueryFSAttach(szName,          // logical drive of attached FS ("D:"-style)
  1716.                                0,               // ulOrdinal, ignored for FSAIL_QUERYNAME
  1717.                                FSAIL_QUERYNAME, // return name for a drive or device
  1718.                                pfsqBuffer,      // buffer for returned data
  1719.                                &cbBuffer);      // sizeof(*pfsqBuffer)
  1720.  
  1721.         if (arc == NO_ERROR)
  1722.         {
  1723.             if (pszBuf)
  1724.             {
  1725.                 // The data for the last three fields in the FSQBUFFER2
  1726.                 // structure are stored at the offset of fsqBuffer.szName.
  1727.                 // Each data field following fsqBuffer.szName begins
  1728.                 // immediately after the previous item.
  1729.                 strncpy(pszBuf,
  1730.                         (CHAR*)(&pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
  1731.                         cbBuf);         // V0.9.14 (2001-08-01) [umoeller]
  1732.                 *(pszBuf + cbBuf) = '\0';
  1733.             }
  1734.         }
  1735.     }
  1736.     else
  1737.         arc = ERROR_INVALID_PARAMETER; // V0.9.16 (2001-10-02) [umoeller]
  1738.  
  1739.     return arc;
  1740. }
  1741.  
  1742. /*
  1743.  *@@ doshQueryDiskLabel:
  1744.  *      this returns the label of a disk into
  1745.  *      *pszVolumeLabel, which must be 12 bytes
  1746.  *      in size.
  1747.  *
  1748.  *      This function was added because the Toolkit
  1749.  *      information for DosQueryFSInfo is only partly
  1750.  *      correct. On OS/2 2.x, that function does not
  1751.  *      take an FSINFO structure as input, but a VOLUMELABEL.
  1752.  *      On Warp, this does take an FSINFO.
  1753.  *
  1754.  *      DosSetFSInfo is even worse. See doshSetDiskLabel.
  1755.  *
  1756.  *      See http://zebra.asta.fh-weingarten.de/os2/Snippets/Bugi6787.HTML
  1757.  *      for details.
  1758.  *
  1759.  *@@added V0.9.0 [umoeller]
  1760.  *@@changed V0.9.11 (2001-04-22) [umoeller]: this copied even with errors, fixed
  1761.  */
  1762.  
  1763. APIRET doshQueryDiskLabel(ULONG ulLogicalDrive,         // in:  1 for A:, 2 for B:, 3 for C:, ...
  1764.                           PSZ pszVolumeLabel)           // out: volume label (must be 12 chars in size)
  1765. {
  1766.     APIRET      arc;
  1767.  
  1768.     #ifdef __OS2V2X__
  1769.         VOLUMELABEL FSInfoBuf;
  1770.     #else
  1771.         FSINFO      FSInfoBuf;
  1772.     #endif
  1773.  
  1774.     arc = DosQueryFSInfo(ulLogicalDrive,
  1775.                          FSIL_VOLSER,
  1776.                          &FSInfoBuf,
  1777.                          sizeof(FSInfoBuf)); // depends
  1778.  
  1779.     if (!arc)       // V0.9.11 (2001-04-22) [umoeller]
  1780.     {
  1781.         #ifdef __OS2V2X__
  1782.             strcpy(pszVolumeLabel, FSInfoBuf.szVolLabel);
  1783.         #else
  1784.             strcpy(pszVolumeLabel, FSInfoBuf.vol.szVolLabel);
  1785.         #endif
  1786.     }
  1787.  
  1788.     return arc;
  1789. }
  1790.  
  1791. /*
  1792.  *@@ doshSetDiskLabel:
  1793.  *      this sets the label of a disk.
  1794.  *
  1795.  *      This function was added because the Toolkit
  1796.  *      information for DosSetFSInfo is flat out wrong.
  1797.  *      That function does not take an FSINFO structure
  1798.  *      as input, but a VOLUMELABEL. As a result, using
  1799.  *      that function with the Toolkit's calling specs
  1800.  *      results in ERROR_LABEL_TOO_LONG always.
  1801.  *
  1802.  *      See http://zebra.asta.fh-weingarten.de/os2/Snippets/Bugi6787.HTML
  1803.  *      for details.
  1804.  *
  1805.  *@@added V0.9.0 [umoeller]
  1806.  */
  1807.  
  1808. APIRET doshSetDiskLabel(ULONG ulLogicalDrive,        // in:  1 for A:, 2 for B:, 3 for C:, ...
  1809.                         PSZ pszNewLabel)
  1810. {
  1811.     VOLUMELABEL FSInfoBuf;
  1812.  
  1813.     // check length; 11 chars plus null byte allowed
  1814.     FSInfoBuf.cch = (BYTE)strlen(pszNewLabel);
  1815.     if (FSInfoBuf.cch < sizeof(FSInfoBuf.szVolLabel))
  1816.     {
  1817.         strcpy(FSInfoBuf.szVolLabel, pszNewLabel);
  1818.  
  1819.         return (DosSetFSInfo(ulLogicalDrive,
  1820.                              FSIL_VOLSER,
  1821.                              &FSInfoBuf,
  1822.                              sizeof(FSInfoBuf)));
  1823.     }
  1824.     else
  1825.         return (ERROR_LABEL_TOO_LONG);
  1826. }
  1827.  
  1828. /*
  1829.  *@@category: Helpers\Control program helpers\File name parsing
  1830.  */
  1831.  
  1832. /* ******************************************************************
  1833.  *
  1834.  *   File name parsing
  1835.  *
  1836.  ********************************************************************/
  1837.  
  1838. /*
  1839.  *@@ doshGetDriveSpec:
  1840.  *      returns the drive specification in pcszFullFile,
  1841.  *      if any is present. This is useful for UNC support.
  1842.  *
  1843.  *      This returns:
  1844.  *
  1845.  *      --  NO_ERROR: drive spec was given, and the output
  1846.  *          fields have been set.
  1847.  *
  1848.  *      --  ERROR_INVALID_NAME: incorrect UNC syntax.
  1849.  *
  1850.  *      --  ERROR_INVALID_DRIVE: second char is ':', but
  1851.  *          drive letter is not in the range [A-Z].
  1852.  *
  1853.  *      --  ERROR_INVALID_PARAMETER: no drive spec given
  1854.  *          at all; apparently pcszFullFile is not fully
  1855.  *          qualified in the first place, or it is NULL,
  1856.  *          or its length is <= 2.
  1857.  *
  1858.  *@@added V0.9.16 (2001-10-25) [umoeller]
  1859.  */
  1860.  
  1861. APIRET doshGetDriveSpec(PCSZ pcszFullFile,      // in: fully q'fied file spec
  1862.                         PSZ pszDrive,           // out: drive spec ("C:" or "\\SERVER\RESOURCE"; ptr can be NULL)
  1863.                         PULONG pulDriveLen,     // out: length of drive spec (2 if local drive; ptr can be NULL)
  1864.                         PBOOL pfIsUNC)          // out: set to TRUE if UNC name, FALSE otherwise (ptr can be NULL)
  1865. {
  1866.     APIRET  arc = NO_ERROR;
  1867.     ULONG   ulFileSpecLength;
  1868.  
  1869.     if (    (pcszFullFile)
  1870.          && (ulFileSpecLength = strlen(pcszFullFile))
  1871.          && (ulFileSpecLength >= 2)
  1872.        )
  1873.     {
  1874.         // upper-case the drive letter
  1875.         if (pcszFullFile[1] == ':')
  1876.         {
  1877.             CHAR cDrive = toupper(*pcszFullFile);
  1878.             // local drive specified:
  1879.             if (    (cDrive >= 'A')
  1880.                  && (cDrive <= 'Z')
  1881.                )
  1882.             {
  1883.                 if (pszDrive)
  1884.                 {
  1885.                     pszDrive[0] = cDrive;
  1886.                     pszDrive[1] = ':';
  1887.                     pszDrive[2] = '\0';
  1888.                 }
  1889.  
  1890.                 if (pulDriveLen)
  1891.                     *pulDriveLen = 2;
  1892.                 if (pfIsUNC)
  1893.                     *pfIsUNC = FALSE;
  1894.             }
  1895.             else
  1896.                 // this is not a valid drive:
  1897.                 arc = ERROR_INVALID_DRIVE;
  1898.         }
  1899.         else if (    (pcszFullFile[0] == '\\')
  1900.                   && (pcszFullFile[1] == '\\')
  1901.                 )
  1902.         {
  1903.             // UNC drive specified:
  1904.             // this better be a full \\SERVER\RESOURCE string
  1905.             PCSZ pResource;
  1906.             if (pResource = strchr(pcszFullFile + 3, '\\'))
  1907.             {
  1908.                 // we got at least \\SERVER\:
  1909.                 ULONG ulLength;
  1910.                 PCSZ p;
  1911.  
  1912.                 // check if more stuff is coming
  1913.                 if (p = strchr(pResource + 1, '\\'))
  1914.                 {
  1915.                     // yes: copy server and resource excluding that backslash
  1916.                     if (p == pResource + 1)
  1917.                         // "\\SERVER\\" is invalid
  1918.                         arc = ERROR_INVALID_NAME;
  1919.                     else
  1920.                         // we got "\\SERVER\something\":
  1921.                         // drop the last backslash
  1922.                         ulLength = p - pcszFullFile;
  1923.                 }
  1924.                 else
  1925.                     // "\\SERVER\something" only:
  1926.                     ulLength = ulFileSpecLength;
  1927.  
  1928.                 if (!arc)
  1929.                 {
  1930.                     if (pszDrive)
  1931.                     {
  1932.                         memcpy(pszDrive,
  1933.                                pcszFullFile,
  1934.                                ulLength);
  1935.                         pszDrive[ulLength] = '\0';
  1936.                     }
  1937.  
  1938.                     if (pulDriveLen)
  1939.                         *pulDriveLen = ulLength;
  1940.                     if (pfIsUNC)
  1941.                         *pfIsUNC = TRUE;
  1942.                 }
  1943.             }
  1944.             else
  1945.                 // invalid UNC name:
  1946.                 arc = ERROR_INVALID_NAME;
  1947.         }
  1948.         else
  1949.             // neither local, nor UNC:
  1950.             arc = ERROR_INVALID_PARAMETER;
  1951.     }
  1952.     else
  1953.         arc = ERROR_INVALID_PARAMETER;
  1954.  
  1955.     return arc;
  1956. }
  1957.  
  1958. /*
  1959.  *@@ doshGetExtension:
  1960.  *      finds the file name extension of pszFilename,
  1961.  *      which can be a file name only or a fully
  1962.  *      qualified filename.
  1963.  *
  1964.  *      This returns a pointer into pszFilename to
  1965.  *      the character after the last dot.
  1966.  *
  1967.  *      Returns NULL if not found (e.g. if the filename
  1968.  *      has no dot in it).
  1969.  *
  1970.  *      In the pathological case of a dot in the path
  1971.  *      but not in the filename itself (e.g.
  1972.  *      "C:\files.new\readme"), this correctly returns
  1973.  *      NULL.
  1974.  *
  1975.  *@@added V0.9.6 (2000-10-16) [umoeller]
  1976.  *@@changed V0.9.7 (2000-12-10) [umoeller]: fixed "F:filename.ext" case
  1977.  */
  1978.  
  1979. PSZ doshGetExtension(PCSZ pcszFilename)
  1980. {
  1981.     PSZ pReturn = NULL;
  1982.  
  1983.     if (pcszFilename)
  1984.     {
  1985.         // find filename
  1986.         PCSZ    p2,
  1987.                 pStartOfName = NULL,
  1988.                 pExtension = NULL;
  1989.  
  1990.         if (p2 = strrchr(pcszFilename + 2, '\\'))
  1991.                             // works on "C:\blah" or "\\unc\blah"
  1992.             pStartOfName = p2 + 1;
  1993.         else
  1994.         {
  1995.             // no backslash found:
  1996.             // maybe only a drive letter was specified:
  1997.             if (pcszFilename[1] == ':')
  1998.                 // yes:
  1999.                 pStartOfName = pcszFilename + 2;
  2000.             else
  2001.                 // then this is not qualified at all...
  2002.                 // use start of filename
  2003.                 pStartOfName = (PSZ)pcszFilename;
  2004.         }
  2005.  
  2006.         // find last dot in filename
  2007.         if (pExtension = strrchr(pStartOfName, '.'))
  2008.             pReturn = (PSZ)pExtension + 1;
  2009.     }
  2010.  
  2011.     return (pReturn);
  2012. }
  2013.  
  2014. /*
  2015.  *@@category: Helpers\Control program helpers\File management
  2016.  */
  2017.  
  2018. /* ******************************************************************
  2019.  *
  2020.  *   File helpers
  2021.  *
  2022.  ********************************************************************/
  2023.  
  2024. /*
  2025.  *@@ doshIsFileOnFAT:
  2026.  *      returns TRUE if pszFileName resides on
  2027.  *      a FAT drive. Note that pszFileName must
  2028.  *      be fully qualified (i.e. the drive letter
  2029.  *      must be the first character), or this will
  2030.  *      return garbage.
  2031.  */
  2032.  
  2033. BOOL doshIsFileOnFAT(const char* pcszFileName)
  2034. {
  2035.     BOOL brc = FALSE;
  2036.     CHAR szName[5];
  2037.  
  2038.     APIRET arc;
  2039.     BYTE   fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
  2040.     ULONG  cbBuffer   = sizeof(fsqBuffer);        // Buffer length)
  2041.     PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
  2042.  
  2043.     szName[0] = pcszFileName[0];    // copy drive letter
  2044.     szName[1] = ':';
  2045.     szName[2] = '\0';
  2046.  
  2047.     if (!(arc = DosQueryFSAttach(szName,          // logical drive of attached FS
  2048.                                  0,               // ulOrdinal, ignored for FSAIL_QUERYNAME
  2049.                                  FSAIL_QUERYNAME, // return data for a Drive or Device
  2050.                                  pfsqBuffer,      // returned data
  2051.                                  &cbBuffer)))     // returned data length
  2052.     {
  2053.         // The data for the last three fields in the FSQBUFFER2
  2054.         // structure are stored at the offset of fsqBuffer.szName.
  2055.         // Each data field following fsqBuffer.szName begins
  2056.         // immediately after the previous item.
  2057.         if (!strncmp((PSZ)&(pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
  2058.                      "FAT",
  2059.                      3))
  2060.             brc = TRUE;
  2061.     }
  2062.  
  2063.     return brc;
  2064. }
  2065.  
  2066. /*
  2067.  *@@ doshQueryFileSize:
  2068.  *      returns the size of an already opened file
  2069.  *      or 0 upon errors.
  2070.  *      Use doshQueryPathSize to query the size of
  2071.  *      any file.
  2072.  *
  2073.  *@@changed V0.9.16 (2001-10-19) [umoeller]: now returning APIRET
  2074.  */
  2075.  
  2076. APIRET doshQueryFileSize(HFILE hFile,       // in: file handle
  2077.                          PULONG pulSize)    // out: file size (ptr can be NULL)
  2078. {
  2079.     APIRET arc;
  2080.     FILESTATUS3 fs3;
  2081.     if (!(arc = DosQueryFileInfo(hFile, FIL_STANDARD, &fs3, sizeof(fs3))))
  2082.         if (pulSize)
  2083.             *pulSize = fs3.cbFile;
  2084.     return arc;
  2085. }
  2086.  
  2087. /*
  2088.  *@@ doshQueryPathSize:
  2089.  *      returns the size of any file,
  2090.  *      or 0 if the file could not be
  2091.  *      found.
  2092.  *
  2093.  *      Use doshQueryFileSize instead to query the
  2094.  *      size if you have a HFILE.
  2095.  *
  2096.  *      Otherwise this returns:
  2097.  *
  2098.  *      --  ERROR_FILE_NOT_FOUND
  2099.  *      --  ERROR_PATH_NOT_FOUND
  2100.  *      --  ERROR_SHARING_VIOLATION
  2101.  *      --  ERROR_FILENAME_EXCED_RANGE
  2102.  *      --  ERROR_INVALID_PARAMETER
  2103.  *
  2104.  *@@changed V0.9.16 (2001-10-19) [umoeller]: now returning APIRET
  2105.  */
  2106.  
  2107. APIRET doshQueryPathSize(PCSZ pcszFile,         // in: filename
  2108.                          PULONG pulSize)        // out: file size (ptr can be NULL)
  2109. {
  2110.     APIRET arc;
  2111.  
  2112.     if (pcszFile)      // V0.9.16 (2001-12-08) [umoeller]
  2113.     {
  2114.         FILESTATUS3 fs3;
  2115.         if (!(arc = DosQueryPathInfo((PSZ)pcszFile, FIL_STANDARD, &fs3, sizeof(fs3))))
  2116.             if (pulSize)
  2117.                 *pulSize = fs3.cbFile;
  2118.     }
  2119.     else
  2120.         arc = ERROR_INVALID_PARAMETER;
  2121.  
  2122.     return arc;
  2123. }
  2124.  
  2125. /*
  2126.  *@@ doshQueryPathAttr:
  2127.  *      returns the file attributes of pszFile,
  2128.  *      which can be fully qualified. The
  2129.  *      attributes will be stored in *pulAttr.
  2130.  *      pszFile can also specify a directory,
  2131.  *      although not all attributes make sense
  2132.  *      for directories.
  2133.  *
  2134.  *      fAttr can be:
  2135.  *      --  FILE_ARCHIVED
  2136.  *      --  FILE_READONLY
  2137.  *      --  FILE_SYSTEM
  2138.  *      --  FILE_HIDDEN
  2139.  *
  2140.  *      This returns the APIRET of DosQueryPathInfo.
  2141.  *      *pulAttr is only valid if NO_ERROR is
  2142.  *      returned.
  2143.  *
  2144.  *      Otherwise this returns:
  2145.  *
  2146.  *      --  ERROR_FILE_NOT_FOUND
  2147.  *      --  ERROR_PATH_NOT_FOUND
  2148.  *      --  ERROR_SHARING_VIOLATION
  2149.  *      --  ERROR_FILENAME_EXCED_RANGE
  2150.  *
  2151.  *@@added V0.9.0 [umoeller]
  2152.  */
  2153.  
  2154. APIRET doshQueryPathAttr(const char* pcszFile,      // in: file or directory name
  2155.                          PULONG pulAttr)            // out: attributes (ptr can be NULL)
  2156. {
  2157.     FILESTATUS3 fs3;
  2158.     APIRET arc;
  2159.  
  2160.     if (!(arc = DosQueryPathInfo((PSZ)pcszFile,
  2161.                                   FIL_STANDARD,
  2162.                                   &fs3,
  2163.                                   sizeof(fs3))))
  2164.     {
  2165.         if (pulAttr)
  2166.             *pulAttr = fs3.attrFile;
  2167.     }
  2168.  
  2169.     return arc;
  2170. }
  2171.  
  2172. /*
  2173.  *@@ doshSetPathAttr:
  2174.  *      sets the file attributes of pszFile,
  2175.  *      which can be fully qualified.
  2176.  *      pszFile can also specify a directory,
  2177.  *      although not all attributes make sense
  2178.  *      for directories.
  2179.  *
  2180.  *      fAttr can be:
  2181.  *      --  FILE_ARCHIVED
  2182.  *      --  FILE_READONLY
  2183.  *      --  FILE_SYSTEM
  2184.  *      --  FILE_HIDDEN
  2185.  *
  2186.  *      Note that this simply sets all the given
  2187.  *      attributes; the existing attributes
  2188.  *      are lost.
  2189.  *
  2190.  *      This returns the APIRET of DosQueryPathInfo.
  2191.  */
  2192.  
  2193. APIRET doshSetPathAttr(const char* pcszFile,    // in: file or directory name
  2194.                        ULONG ulAttr)            // in: new attributes
  2195. {
  2196.     APIRET arc;
  2197.  
  2198.     if (pcszFile)
  2199.     {
  2200.         FILESTATUS3 fs3;
  2201.         if (!(arc = DosQueryPathInfo((PSZ)pcszFile,
  2202.                                       FIL_STANDARD,
  2203.                                       &fs3,
  2204.                                       sizeof(fs3))))
  2205.         {
  2206.             fs3.attrFile = ulAttr;
  2207.             arc = DosSetPathInfo((PSZ)pcszFile,
  2208.                                  FIL_STANDARD,
  2209.                                  &fs3,
  2210.                                  sizeof(fs3),
  2211.                                  DSPI_WRTTHRU);
  2212.         }
  2213.     }
  2214.     else
  2215.         arc = ERROR_INVALID_PARAMETER;
  2216.  
  2217.     return arc;
  2218. }
  2219.  
  2220. /*
  2221.  *@@category: Helpers\Control program helpers\File management\XFILEs
  2222.  */
  2223.  
  2224. /* ******************************************************************
  2225.  *
  2226.  *   XFILEs
  2227.  *
  2228.  ********************************************************************/
  2229.  
  2230. /*
  2231.  * doshOpen:
  2232.  *      wrapper around DosOpen for simpler opening
  2233.  *      of files.
  2234.  *
  2235.  *      ulOpenMode determines the mode to open the
  2236.  *      file in (fptr specifies the position after
  2237.  *      the open):
  2238.  *
  2239.  +      +-------------------------+------+------------+-----------+
  2240.  +      |  ulOpenMode             | mode | if exists  | if new    |
  2241.  +      +-------------------------+------+------------+-----------+
  2242.  +      |  XOPEN_READ_EXISTING    | read | opens      | fails     |
  2243.  +      |                         |      | fptr = 0   |           |
  2244.  +      +-------------------------+------+------------+-----------+
  2245.  +      |  XOPEN_READWRITE_EXISTING r/w  | opens      | fails     |
  2246.  +      |                         |      | fptr = 0   |           |
  2247.  +      +-------------------------+------+------------+-----------+
  2248.  +      |  XOPEN_READWRITE_APPEND | r/w  | opens,     | creates   |
  2249.  +      |                         |      | appends    |           |
  2250.  +      |                         |      | fptr = end | fptr = 0  |
  2251.  +      +-------------------------+------+------------+-----------+
  2252.  +      |  XOPEN_READWRITE_NEW    | r/w  | replaces   | creates   |
  2253.  +      |                         |      | fptr = 0   | fptr = 0  |
  2254.  +      +-------------------------+------+------------+-----------+
  2255.  *
  2256.  *      In addition, you can OR one of the above values with
  2257.  *      the XOPEN_BINARY flag:
  2258.  *
  2259.  *      --  If XOPEN_BINARY is set, no conversion is performed
  2260.  *          on read and write.
  2261.  *
  2262.  *      --  If XOPEN_BINARY is _not_ set, all \n chars are
  2263.  *          converted to \r\n on write.
  2264.  *
  2265.  *      *ppFile receives a new XFILE structure describing
  2266.  *      the open file, if NO_ERROR is returned.
  2267.  *
  2268.  *      The file pointer is then set to the beginning of the
  2269.  *      file _unless_ XOPEN_READWRITE_APPEND was specified;
  2270.  *      in that case only, the file pointer is set to the
  2271.  *      end of the file so data can be appended (see above).
  2272.  *
  2273.  *      Otherwise this returns:
  2274.  *
  2275.  *      --  ERROR_FILE_NOT_FOUND
  2276.  *      --  ERROR_PATH_NOT_FOUND
  2277.  *      --  ERROR_SHARING_VIOLATION
  2278.  *      --  ERROR_FILENAME_EXCED_RANGE
  2279.  *
  2280.  *      --  ERROR_NOT_ENOUGH_MEMORY
  2281.  *      --  ERROR_INVALID_PARAMETER
  2282.  *
  2283.  *@@added V0.9.16 (2001-10-19) [umoeller]
  2284.  *@@changed V0.9.16 (2001-12-18) [umoeller]: fixed error codes
  2285.  */
  2286.  
  2287. APIRET doshOpen(PCSZ pcszFilename,   // in: filename to open
  2288.                 ULONG flOpenMode,       // in: XOPEN_* mode
  2289.                 PULONG pcbFile,         // in: new file size (if new file is created)
  2290.                                         // out: file size
  2291.                 PXFILE *ppFile)
  2292. {
  2293.     APIRET  arc = NO_ERROR;
  2294.  
  2295.     ULONG   fsOpenFlags = 0,
  2296.             fsOpenMode =    OPEN_FLAGS_FAIL_ON_ERROR
  2297.                           | OPEN_FLAGS_NO_LOCALITY
  2298.                           | OPEN_FLAGS_NOINHERIT;
  2299.  
  2300.     switch (flOpenMode & XOPEN_ACCESS_MASK)
  2301.     {
  2302.         case XOPEN_READ_EXISTING:
  2303.             fsOpenFlags =   OPEN_ACTION_FAIL_IF_NEW
  2304.                           | OPEN_ACTION_OPEN_IF_EXISTS;
  2305.             fsOpenMode |=   OPEN_SHARE_DENYWRITE
  2306.                           | OPEN_ACCESS_READONLY;
  2307.  
  2308.             // run this first, because if the file doesn't
  2309.             // exists, DosOpen only returns ERROR_OPEN_FAILED,
  2310.             // which isn't that meaningful
  2311.             // V0.9.16 (2001-12-08) [umoeller]
  2312.             arc = doshQueryPathSize(pcszFilename,
  2313.                                     pcbFile);
  2314.         break;
  2315.  
  2316.         case XOPEN_READWRITE_EXISTING:
  2317.             fsOpenFlags =   OPEN_ACTION_FAIL_IF_NEW
  2318.                           | OPEN_ACTION_OPEN_IF_EXISTS;
  2319.             fsOpenMode |=   OPEN_SHARE_DENYWRITE
  2320.                           | OPEN_ACCESS_READWRITE;
  2321.  
  2322.             arc = doshQueryPathSize(pcszFilename,
  2323.                                     pcbFile);
  2324.         break;
  2325.  
  2326.         case XOPEN_READWRITE_APPEND:
  2327.             fsOpenFlags =   OPEN_ACTION_CREATE_IF_NEW
  2328.                           | OPEN_ACTION_OPEN_IF_EXISTS;
  2329.             fsOpenMode |=   OPEN_SHARE_DENYREADWRITE
  2330.                           | OPEN_ACCESS_READWRITE;
  2331.             // _Pmpf((__FUNCTION__ ": opening XOPEN_READWRITE_APPEND"));
  2332.         break;
  2333.  
  2334.         case XOPEN_READWRITE_NEW:
  2335.             fsOpenFlags =   OPEN_ACTION_CREATE_IF_NEW
  2336.                           | OPEN_ACTION_REPLACE_IF_EXISTS;
  2337.             fsOpenMode |=   OPEN_SHARE_DENYREADWRITE
  2338.                           | OPEN_ACCESS_READWRITE;
  2339.             // _Pmpf((__FUNCTION__ ": opening XOPEN_READWRITE_NEW"));
  2340.         break;
  2341.     }
  2342.  
  2343.     if ((!arc) && fsOpenFlags && pcbFile && ppFile)
  2344.     {
  2345.         PXFILE pFile;
  2346.         if (pFile = NEW(XFILE))
  2347.         {
  2348.             ULONG ulAction;
  2349.  
  2350.             ZERO(pFile);
  2351.  
  2352.             // copy open flags
  2353.             pFile->flOpenMode = flOpenMode;
  2354.  
  2355.             if (!(arc = DosOpen((PSZ)pcszFilename,
  2356.                                 &pFile->hf,
  2357.                                 &ulAction,
  2358.                                 *pcbFile,
  2359.                                 FILE_ARCHIVED,
  2360.                                 fsOpenFlags,
  2361.                                 fsOpenMode,
  2362.                                 NULL)))       // EAs
  2363.             {
  2364.                 // alright, got the file:
  2365.  
  2366.                 if (    (ulAction == FILE_EXISTED)
  2367.                      && ((flOpenMode & XOPEN_ACCESS_MASK) == XOPEN_READWRITE_APPEND)
  2368.                    )
  2369.                     // get its size and set ptr to end for append
  2370.                     arc = DosSetFilePtr(pFile->hf,
  2371.                                         0,
  2372.                                         FILE_END,
  2373.                                         pcbFile);
  2374.                 else
  2375.                     arc = doshQueryFileSize(pFile->hf,
  2376.                                             pcbFile);
  2377.                     // file ptr is at beginning
  2378.  
  2379.                 #ifdef DEBUG_DOSOPEN
  2380.                  if (arc)
  2381.                     _Pmpf((__FUNCTION__ ": DosSetFilePtr/queryfilesize returned %d for %s",
  2382.                                 arc, pcszFilename));
  2383.                 #endif
  2384.  
  2385.                 // store file size
  2386.                 pFile->cbInitial
  2387.                 = pFile->cbCurrent
  2388.                 = *pcbFile;
  2389.  
  2390.                 pFile->pszFilename = strdup(pcszFilename);
  2391.             }
  2392.             #ifdef DEBUG_DOSOPEN
  2393.             else
  2394.                  _Pmpf((__FUNCTION__ ": DosOpen returned %d for %s",
  2395.                              arc, pcszFilename));
  2396.             #endif
  2397.  
  2398.             if (arc)
  2399.                 doshClose(&pFile);
  2400.             else
  2401.                 *ppFile = pFile;
  2402.         }
  2403.         else
  2404.             arc = ERROR_NOT_ENOUGH_MEMORY;
  2405.     }
  2406.     else
  2407.         if (!arc)       // V0.9.19 (2002-04-02) [umoeller]
  2408.             arc = ERROR_INVALID_PARAMETER;
  2409.  
  2410.     return arc;
  2411. }
  2412.  
  2413. /*
  2414.  *@@ doshReadAt:
  2415.  *      reads cb bytes from the position specified by
  2416.  *      lOffset into the buffer pointed to by pbData,
  2417.  *      which should be cb bytes in size.
  2418.  *
  2419.  *      Note that lOffset is always considered to
  2420.  *      be from the beginning of the file (FILE_BEGIN
  2421.  *      method).
  2422.  *
  2423.  *      This implements a small cache so that several
  2424.  *      calls with a near offset will not touch the
  2425.  *      disk always. The cache has been optimized for
  2426.  *      the exeh* functions and works quite nicely
  2427.  *      there.
  2428.  *
  2429.  *      Note that the position of the file pointer is
  2430.  *      undefined after calling this function because
  2431.  *      the data might have been returned from the
  2432.  *      cache.
  2433.  *
  2434.  *      fl may be any combination of the following:
  2435.  *
  2436.  *      --  DRFL_NOCACHE: do not fill the cache with
  2437.  *          new data if the data is not in the cache
  2438.  *          currently.
  2439.  *
  2440.  *      --  DRFL_FAILIFLESS: return ERROR_NO_DATA
  2441.  *          if the data returned by DosRead is less
  2442.  *          than what was specified. This might
  2443.  *          simplify error handling.
  2444.  *
  2445.  *@@added V0.9.13 (2001-06-14) [umoeller]
  2446.  *@@changed V0.9.16 (2001-12-18) [umoeller]: now with XFILE, and always using FILE_BEGIN
  2447.  *@@chaanged V0.9.19 (2002-04-02) [umoeller]: added params checking
  2448.  */
  2449.  
  2450. APIRET doshReadAt(PXFILE pFile,
  2451.                   ULONG ulOffset,   // in: offset to read from (from beginning of file)
  2452.                   PULONG pcb,       // in: bytes to read, out: bytes read (req.)
  2453.                   PBYTE pbData,     // out: read buffer (must be cb bytes)
  2454.                   ULONG fl)         // in: DRFL_* flags
  2455. {
  2456.     APIRET arc = NO_ERROR;
  2457.     ULONG cb;
  2458.     ULONG ulDummy;
  2459.  
  2460.     if (!pFile || !pcb)
  2461.         // V0.9.19 (2002-04-02) [umoeller]
  2462.         return ERROR_INVALID_PARAMETER;
  2463.  
  2464.     cb = *pcb;
  2465.     *pcb = 0;
  2466.  
  2467.     // check if we have the data in the cache already;
  2468.  
  2469.     if (    (pFile->pbCache)
  2470.             // first byte must be in cache
  2471.          && (ulOffset >= pFile->ulReadFrom)
  2472.             // last byte must be in cache
  2473.          && (    ulOffset + cb
  2474.               <= pFile->ulReadFrom + pFile->cbCache
  2475.             )
  2476.        )
  2477.     {
  2478.         // alright, return data from cache simply
  2479.         ULONG ulOfsInCache = ulOffset - pFile->ulReadFrom;
  2480.  
  2481.         memcpy(pbData,
  2482.                pFile->pbCache + ulOfsInCache,
  2483.                cb);
  2484.         *pcb = cb;
  2485.  
  2486.         #ifdef DEBUG_DOSOPEN
  2487.         _Pmpf((__FUNCTION__ " %s: data is fully in cache",
  2488.                     pFile->pszFilename));
  2489.         _Pmpf(("  caller wants %d bytes from %d",
  2490.                     cb, ulOffset));
  2491.         _Pmpf(("  we got %d bytes from %d",
  2492.                     pFile->cbCache, pFile->ulReadFrom));
  2493.         _Pmpf(("  so copied %d bytes from cache ofs %d",
  2494.                     cb, ulOfsInCache));
  2495.         #endif
  2496.     }
  2497.     else
  2498.     {
  2499.         // data is not in cache:
  2500.         // check how much it is... for small amounts,
  2501.         // we load the cache first
  2502.         if (    (cb <= 4096 - 512)
  2503.              && (!(fl & DRFL_NOCACHE))
  2504.            )
  2505.         {
  2506.             #ifdef DEBUG_DOSOPEN
  2507.             _Pmpf((__FUNCTION__ " %s: filling cache anew",
  2508.                     pFile->pszFilename));
  2509.             _Pmpf(("  caller wants %d bytes from %d",
  2510.                         cb, ulOffset));
  2511.             #endif
  2512.  
  2513.             // OK, then fix the offset to read from
  2514.             // to a multiple of 512 to get a full sector
  2515.             pFile->ulReadFrom = ulOffset / 512L * 512L;
  2516.             // and read 4096 bytes always plus the
  2517.             // value we cut off above
  2518.             pFile->cbCache = 4096;
  2519.  
  2520.             #ifdef DEBUG_DOSOPEN
  2521.             _Pmpf(("  getting %d bytes from %d",
  2522.                         pFile->cbCache, pFile->ulReadFrom));
  2523.             #endif
  2524.  
  2525.             // free old cache
  2526.             if (pFile->pbCache)
  2527.                 free(pFile->pbCache);
  2528.  
  2529.             // allocate new cache
  2530.             if (!(pFile->pbCache = (PBYTE)malloc(pFile->cbCache)))
  2531.                 arc = ERROR_NOT_ENOUGH_MEMORY;
  2532.             else
  2533.             {
  2534.                 ULONG ulOfsInCache = 0;
  2535.  
  2536.                 if (!(arc = DosSetFilePtr(pFile->hf,
  2537.                                           (LONG)pFile->ulReadFrom,
  2538.                                           FILE_BEGIN,
  2539.                                           &ulDummy)))
  2540.                 {
  2541.                     if (!(arc = DosRead(pFile->hf,
  2542.                                         pFile->pbCache,
  2543.                                         pFile->cbCache,
  2544.                                         &ulDummy)))
  2545.                     {
  2546.                         // got data:
  2547.                         #ifdef DEBUG_DOSOPEN
  2548.                         _Pmpf(("        %d bytes read", ulDummy));
  2549.                         #endif
  2550.  
  2551.                         pFile->cbCache = ulDummy;
  2552.  
  2553.                         // check bounds
  2554.                         ulOfsInCache = ulOffset - pFile->ulReadFrom;
  2555.  
  2556.                         /*
  2557.                         if (ulOfsInCache + cb > pFile->cbCache)
  2558.                         {
  2559.                             cb = pFile->cbCache - ulOfsInCache;
  2560.                             if (fl & DRFL_FAILIFLESS)
  2561.                                 arc = ERROR_NO_DATA;
  2562.                         }
  2563.                         */
  2564.                     }
  2565.                 }
  2566.  
  2567.                 if (!arc)
  2568.                 {
  2569.                     // copy to caller
  2570.                     memcpy(pbData,
  2571.                            pFile->pbCache + ulOfsInCache,
  2572.                            cb);
  2573.                     *pcb = cb;
  2574.  
  2575.                     #ifdef DEBUG_DOSOPEN
  2576.                     _Pmpf(("  so copied %d bytes from cache ofs %d",
  2577.                                 cb, ulOfsInCache));
  2578.                     #endif
  2579.                 }
  2580.                 else
  2581.                 {
  2582.                     free(pFile->pbCache);
  2583.                     pFile->pbCache = NULL;
  2584.                 }
  2585.             } // end else if (!(pFile->pbCache = (PBYTE)malloc(pFile->cbCache)))
  2586.         }
  2587.         else
  2588.         {
  2589.             // read uncached:
  2590.             #ifdef DEBUG_DOSOPEN
  2591.             _Pmpf(("  " __FUNCTION__ " %s: reading uncached",
  2592.                         pFile->pszFilename));
  2593.             _Pmpf(("      caller wants %d bytes from %d",
  2594.                         cb, ulOffset));
  2595.             #endif
  2596.  
  2597.             if (!(arc = DosSetFilePtr(pFile->hf,
  2598.                                       (LONG)ulOffset,
  2599.                                       FILE_BEGIN,
  2600.                                       &ulDummy)))
  2601.             {
  2602.                 if (!(arc = DosRead(pFile->hf,
  2603.                                     pbData,
  2604.                                     cb,
  2605.                                     &ulDummy)))
  2606.                 {
  2607.                     if (    (fl & DRFL_FAILIFLESS)
  2608.                          && (ulDummy != cb)
  2609.                        )
  2610.                         arc = ERROR_NO_DATA;
  2611.                     else
  2612.                         *pcb = ulDummy;     // bytes read
  2613.                 }
  2614.             }
  2615.         }
  2616.     }
  2617.  
  2618.     return arc;
  2619. }
  2620.  
  2621. /*
  2622.  *@@ doshWrite:
  2623.  *      writes the specified data to the file.
  2624.  *      If (cb == 0), this runs strlen on pcsz
  2625.  *      to find out the length.
  2626.  *
  2627.  *      If the file is not in binary mode, all
  2628.  *      \n chars are converted to \r\n before
  2629.  *      writing.
  2630.  *
  2631.  *      Note that this expects that the file
  2632.  *      pointer is at the end of the file, or
  2633.  *      you will get garbage.
  2634.  *
  2635.  *@@added V0.9.16 (2001-10-19) [umoeller]
  2636.  *@@changed V0.9.16 (2001-12-02) [umoeller]: added XOPEN_BINARY \r\n support
  2637.  *@@changed V0.9.16 (2001-12-06) [umoeller]: added check for pFile != NULL
  2638.  */
  2639.  
  2640. APIRET doshWrite(PXFILE pFile,
  2641.                  ULONG cb,
  2642.                  PCSZ pbData)
  2643. {
  2644.     APIRET arc = NO_ERROR;
  2645.     if ((!pFile) || (!pbData))
  2646.         arc = ERROR_INVALID_PARAMETER;
  2647.     else
  2648.     {
  2649.         if (!cb)
  2650.             cb = strlen(pbData);
  2651.  
  2652.         if (!cb)
  2653.             arc = ERROR_INVALID_PARAMETER;
  2654.         else
  2655.         {
  2656.             PSZ pszNew = NULL;
  2657.  
  2658.             if (!(pFile->flOpenMode & XOPEN_BINARY))
  2659.             {
  2660.                 // convert all \n to \r\n:
  2661.                 // V0.9.16 (2001-12-02) [umoeller]
  2662.  
  2663.                 // count all \n first
  2664.                 ULONG cNewLines = 0;
  2665.                 PCSZ pSource = pbData;
  2666.                 ULONG ul;
  2667.                 for (ul = 0;
  2668.                      ul < cb;
  2669.                      ul++)
  2670.                 {
  2671.                     if (*pSource++ == '\n')
  2672.                         cNewLines++;
  2673.                 }
  2674.  
  2675.                 if (cNewLines)
  2676.                 {
  2677.                     // we have '\n' chars:
  2678.                     // then we need just as many \r chars inserted
  2679.                     ULONG cbNew = cb + cNewLines;
  2680.                     if (!(pszNew = (PSZ)malloc(cbNew)))
  2681.                         arc = ERROR_NOT_ENOUGH_MEMORY;
  2682.                     else
  2683.                     {
  2684.                         PSZ pTarget = pszNew;
  2685.                         pSource = pbData;
  2686.                         for (ul = 0;
  2687.                              ul < cb;
  2688.                              ul++)
  2689.                         {
  2690.                             CHAR c = *pSource++;
  2691.                             if (c == '\n')
  2692.                                 *pTarget++ = '\r';
  2693.                             *pTarget++ = c;
  2694.                         }
  2695.  
  2696.                         cb = cbNew;
  2697.                     }
  2698.                 }
  2699.             }
  2700.  
  2701.             if (!arc)
  2702.             {
  2703.                 ULONG cbWritten;
  2704.                 if (!(arc = DosWrite(pFile->hf,
  2705.                                      (pszNew)
  2706.                                             ? pszNew
  2707.                                             : (PSZ)pbData,
  2708.                                      cb,
  2709.                                      &cbWritten)))
  2710.                 {
  2711.                     pFile->cbCurrent += cbWritten;
  2712.                     // invalidate the cache
  2713.                     FREE(pFile->pbCache);
  2714.                 }
  2715.             }
  2716.  
  2717.             if (pszNew)
  2718.                 free(pszNew);
  2719.         }
  2720.     }
  2721.  
  2722.     return arc;
  2723. }
  2724.  
  2725. /*
  2726.  *@@ doshWriteAt:
  2727.  *      writes cb bytes (pointed to by pbData) to the
  2728.  *      specified file at the position lOffset (from
  2729.  *      the beginning of the file).
  2730.  *
  2731.  *@@added V0.9.13 (2001-06-14) [umoeller]
  2732.  */
  2733.  
  2734. APIRET doshWriteAt(PXFILE pFile,
  2735.                    ULONG ulOffset,    // in: offset to write at
  2736.                    ULONG cb,        // in: bytes to write
  2737.                    PCSZ pbData)     // in: ptr to bytes to write (must be cb bytes)
  2738. {
  2739.     APIRET arc = NO_ERROR;
  2740.     ULONG cbWritten;
  2741.     if (!(arc = DosSetFilePtr(pFile->hf,
  2742.                               (LONG)ulOffset,
  2743.                               FILE_BEGIN,
  2744.                               &cbWritten)))
  2745.     {
  2746.         if (!(arc = DosWrite(pFile->hf,
  2747.                              (PSZ)pbData,
  2748.                              cb,
  2749.                              &cbWritten)))
  2750.         {
  2751.             if (ulOffset + cbWritten > pFile->cbCurrent)
  2752.                 pFile->cbCurrent = ulOffset + cbWritten;
  2753.             // invalidate the cache V0.9.19 (2002-04-02) [umoeller]
  2754.             FREE(pFile->pbCache);
  2755.         }
  2756.     }
  2757.  
  2758.     return arc;
  2759. }
  2760.  
  2761. /*
  2762.  *@@ doshWriteLogEntry
  2763.  *      writes a log string to an XFILE, adding a
  2764.  *      leading timestamp before the line.
  2765.  *
  2766.  *      The internal string buffer is limited to 2000
  2767.  *      characters. Length checking is _not_ performed.
  2768.  *
  2769.  *@@added V0.9.16 (2001-10-19) [umoeller]
  2770.  *@@changed V0.9.16 (2001-12-06) [umoeller]: added check for pFile != NULL
  2771.  */
  2772.  
  2773. APIRET doshWriteLogEntry(PXFILE pFile,
  2774.                          const char* pcszFormat,
  2775.                          ...)
  2776. {
  2777.     APIRET arc = NO_ERROR;
  2778.  
  2779.     if ((!pFile) || (!pcszFormat))
  2780.         arc = ERROR_INVALID_PARAMETER;
  2781.     else
  2782.     {
  2783.         DATETIME dt;
  2784.         CHAR szTemp[2000];
  2785.         ULONG   ulLength;
  2786.  
  2787.         DosGetDateTime(&dt);
  2788.         if (ulLength = sprintf(szTemp,
  2789.                                "%04d-%02d-%02d %02d:%02d:%02d:%02d ",
  2790.                                dt.year, dt.month, dt.day,
  2791.                                dt.hours, dt.minutes, dt.seconds, dt.hundredths))
  2792.         {
  2793.             if (!(arc = doshWrite(pFile,
  2794.                                   ulLength,
  2795.                                   szTemp)))
  2796.             {
  2797.                 va_list arg_ptr;
  2798.                 va_start(arg_ptr, pcszFormat);
  2799.                 ulLength = vsprintf(szTemp, pcszFormat, arg_ptr);
  2800.                 va_end(arg_ptr);
  2801.  
  2802.                 if (pFile->flOpenMode & XOPEN_BINARY)
  2803.                     // if we're in binary mode, we need to add \r too
  2804.                     szTemp[ulLength++] = '\r';
  2805.                 szTemp[ulLength++] = '\n';
  2806.  
  2807.                 arc = doshWrite(pFile,
  2808.                                 ulLength,
  2809.                                 szTemp);
  2810.             }
  2811.         }
  2812.     }
  2813.  
  2814.     return arc;
  2815. }
  2816.  
  2817. /*
  2818.  * doshClose:
  2819.  *      closes an XFILE opened by doshOpen and
  2820.  *      sets *ppFile to NULL.
  2821.  *
  2822.  *@@added V0.9.16 (2001-10-19) [umoeller]
  2823.  */
  2824.  
  2825. APIRET doshClose(PXFILE *ppFile)
  2826. {
  2827.     APIRET arc = NO_ERROR;
  2828.     PXFILE pFile;
  2829.  
  2830.     if (    (ppFile)
  2831.          && (pFile = *ppFile)
  2832.        )
  2833.     {
  2834.         // set the ptr to NULL
  2835.         *ppFile = NULL;
  2836.  
  2837.         FREE(pFile->pbCache);
  2838.         FREE(pFile->pszFilename);
  2839.  
  2840.         if (pFile->hf)
  2841.         {
  2842.             DosSetFileSize(pFile->hf, pFile->cbCurrent);
  2843.             DosClose(pFile->hf);
  2844.             pFile->hf = NULLHANDLE;
  2845.         }
  2846.  
  2847.         free(pFile);
  2848.     }
  2849.     else
  2850.         arc = ERROR_INVALID_PARAMETER;
  2851.  
  2852.     return arc;
  2853. }
  2854.  
  2855. /*
  2856.  *@@ doshReadText:
  2857.  *      reads all the contents of the given XFILE into
  2858.  *      a newly allocated buffer. Handles Ctrl-Z properly.
  2859.  *
  2860.  *      Implementation for doshLoadTextFile, but can
  2861.  *      be called separately now.
  2862.  *
  2863.  *@@added V0.9.20 (2002-07-19) [umoeller]
  2864.  */
  2865.  
  2866. APIRET doshReadText(PXFILE pFile,
  2867.                     PSZ* ppszContent,   // out: newly allocated buffer with file's content
  2868.                     PULONG pcbRead)     // out: size of that buffer including null byte (ptr can be NULL)
  2869. {
  2870.     APIRET  arc;
  2871.     PSZ     pszContent;
  2872.  
  2873.     if (!(pszContent = (PSZ)malloc(pFile->cbCurrent + 1)))
  2874.         arc = ERROR_NOT_ENOUGH_MEMORY;
  2875.     else
  2876.     {
  2877.         ULONG cbRead = 0;
  2878.         if (!(arc = DosRead(pFile->hf,
  2879.                             pszContent,
  2880.                             pFile->cbCurrent,
  2881.                             &cbRead)))
  2882.         {
  2883.             if (cbRead != pFile->cbCurrent)
  2884.                 arc = ERROR_NO_DATA;
  2885.             else
  2886.             {
  2887.                 PSZ p;
  2888.                 pszContent[cbRead] = '\0';
  2889.  
  2890.                 // check if we have a ctrl-z (EOF) marker
  2891.                 // this is present, for example, in config.sys
  2892.                 // after install, and stupid E.EXE writes this
  2893.                 // all the time when saving a file
  2894.                 // V0.9.18 (2002-03-08) [umoeller]
  2895.                 if (p = strchr(pszContent, '\26'))
  2896.                 {
  2897.                     *p = '\0';
  2898.                     cbRead = p - pszContent;
  2899.                 }
  2900.  
  2901.                 *ppszContent = pszContent;
  2902.                 if (pcbRead)
  2903.                     *pcbRead = cbRead + 1;
  2904.             }
  2905.         }
  2906.  
  2907.         if (arc)
  2908.             free(pszContent);
  2909.     }
  2910.  
  2911.     return arc;
  2912. }
  2913.  
  2914. /*
  2915.  *@@ doshLoadTextFile:
  2916.  *      reads a text file from disk, allocates memory
  2917.  *      via malloc() and sets a pointer to this
  2918.  *      buffer (or NULL upon errors).
  2919.  *
  2920.  *      This allocates one extra byte to make the
  2921.  *      buffer null-terminated always. The buffer
  2922.  *      is _not_ converted WRT the line format.
  2923.  *
  2924.  *      If CTRL-Z (ASCII 26) is encountered in the
  2925.  *      content, it is set to the null character
  2926.  *      instead (V0.9.18).
  2927.  *
  2928.  *      This returns the APIRET of DosOpen and DosRead.
  2929.  *      If any error occured, no buffer was allocated.
  2930.  *      Otherwise, you should free() the buffer when
  2931.  *      no longer needed.
  2932.  *
  2933.  *@@changed V0.9.7 (2001-01-15) [umoeller]: renamed from doshReadTextFile
  2934.  *@@changed V0.9.16 (2002-01-05) [umoeller]: added pcbRead
  2935.  *@@changed V0.9.16 (2002-01-05) [umoeller]: rewritten using doshOpen
  2936.  *@@changed V0.9.18 (2002-03-08) [umoeller]: fixed ctrl-z (EOF) bug
  2937.  */
  2938.  
  2939. APIRET doshLoadTextFile(PCSZ pcszFile,      // in: file name to read
  2940.                         PSZ* ppszContent,   // out: newly allocated buffer with file's content
  2941.                         PULONG pcbRead)     // out: size of that buffer including null byte (ptr can be NULL)
  2942. {
  2943.     APIRET  arc;
  2944.  
  2945.     ULONG   cbFile = 0;
  2946.     PXFILE  pFile = NULL;
  2947.  
  2948.     if (!(arc = doshOpen(pcszFile,
  2949.                          XOPEN_READ_EXISTING,
  2950.                          &cbFile,
  2951.                          &pFile)))
  2952.     {
  2953.         doshReadText(pFile,
  2954.                      ppszContent,
  2955.                      pcbRead);
  2956.         doshClose(&pFile);
  2957.     }
  2958.  
  2959.     return arc;
  2960. }
  2961.  
  2962. /*
  2963.  *@@ doshCreateBackupFileName:
  2964.  *      creates a valid backup filename of pszExisting
  2965.  *      with a numerical file name extension which does
  2966.  *      not exist in the directory where pszExisting
  2967.  *      resides.
  2968.  *      Returns a PSZ to a new buffer which was allocated
  2969.  *      using malloc().
  2970.  *
  2971.  *      <B>Example:</B> returns "C:\CONFIG.002" for input
  2972.  *      "C:\CONFIG.SYS" if "C:\CONFIG.001" already exists.
  2973.  *
  2974.  *@@changed V0.9.1 (99-12-13) [umoeller]: this crashed if pszExisting had no file extension
  2975.  */
  2976.  
  2977. PSZ doshCreateBackupFileName(const char* pszExisting)
  2978. {
  2979.     CHAR    szFilename[CCHMAXPATH];
  2980.     PSZ     pszLastDot;
  2981.     ULONG   ulCount = 1;
  2982.     CHAR    szCount[5];
  2983.     ULONG   ulDummy;
  2984.  
  2985.     strcpy(szFilename, pszExisting);
  2986.  
  2987.     if (!(pszLastDot = strrchr(szFilename, '.')))
  2988.         // no dot in filename:
  2989.         pszLastDot = szFilename + strlen(szFilename);
  2990.  
  2991.     do
  2992.     {
  2993.         sprintf(szCount, ".%03lu", ulCount);
  2994.         strcpy(pszLastDot, szCount);
  2995.         ulCount++;
  2996.     } while (!doshQueryPathSize(szFilename, &ulDummy));
  2997.  
  2998.     return (strdup(szFilename));
  2999. }
  3000.  
  3001. /*
  3002.  *@@ doshCreateTempFileName:
  3003.  *      produces a file name in the the specified directory
  3004.  *      or $(TEMP) which presently doesn't exist. This
  3005.  *      checks the directory for existing files, but does
  3006.  *      not open the temp file.
  3007.  *
  3008.  *      If (pcszDir != NULL), we look into that directory.
  3009.  *      Otherwise we look into the directory specified
  3010.  *      by the $(TEMP) environment variable.
  3011.  *      If $(TEMP) is not set, $(TMP) is tried next.
  3012.  *
  3013.  *      If the directory thus specified does not exist, the
  3014.  *      root directory of the boot drive is used instead.
  3015.  *      As a result, this function should be fairly bomb-proof.
  3016.  *
  3017.  *      If (pcszExt != NULL), the temp file receives
  3018.  *      that extension, or no extension otherwise.
  3019.  *      Do not specify the dot in pcszExt.
  3020.  *
  3021.  *      pszTempFileName receives the fully qualified
  3022.  *      file name of the temp file in that directory
  3023.  *      and must point to a buffer CCHMAXPATH in size.
  3024.  *      The file name is 8+3 compliant if pcszExt does
  3025.  *      not exceed three characters.
  3026.  *
  3027.  *      If (pcszPrefix != NULL), the temp file name
  3028.  *      is prefixed with pcszPrefix. Since the temp
  3029.  *      file name must not exceed 8+3 letters, we
  3030.  *      can only use ( 8 - strlen(pcszPrefix ) digits
  3031.  *      for a random number to make the temp file name
  3032.  *      unique. You must therefore use a maximum of
  3033.  *      four characters for the prefix. Otherwise
  3034.  *      ERROR_INVALID_PARAMETER is returned.
  3035.  *
  3036.  *      Example: Assuming TEMP is set to C:\TEMP,
  3037.  +
  3038.  +          doshCreateTempFileName(szBuffer,
  3039.  +                                 NULL,             // use $(TEMP)
  3040.  +                                 "pre",            // prefix
  3041.  +                                 "tmp")            // extension
  3042.  +
  3043.  *      would produce something like "C:\TEMP\pre07FG2.tmp".
  3044.  *
  3045.  *@@added V0.9.9 (2001-04-04) [umoeller]
  3046.  */
  3047.  
  3048. APIRET doshCreateTempFileName(PSZ pszTempFileName,        // out: fully q'fied temp file name
  3049.                               PCSZ pcszDir,        // in: dir or NULL for %TEMP%
  3050.                               PCSZ pcszPrefix,     // in: prefix for temp file or NULL
  3051.                               PCSZ pcszExt)        // in: extension (without dot) or NULL
  3052. {
  3053.     APIRET      arc = NO_ERROR;
  3054.  
  3055.     ULONG       ulPrefixLen = (pcszPrefix)
  3056.                                     ? strlen(pcszPrefix)
  3057.                                     : 0;
  3058.  
  3059.     if (    (!pszTempFileName)
  3060.          || (ulPrefixLen > 4)
  3061.        )
  3062.         arc = ERROR_INVALID_PARAMETER;
  3063.     else
  3064.     {
  3065.         CHAR        szDir[CCHMAXPATH] = "";
  3066.         FILESTATUS3 fs3;
  3067.  
  3068.         const char  *pcszTemp = pcszDir;
  3069.  
  3070.         if (!pcszTemp)
  3071.         {
  3072.             if (!(pcszTemp = getenv("TEMP")))
  3073.                 pcszTemp = getenv("TMP");
  3074.         }
  3075.  
  3076.         if (pcszTemp)       // either pcszDir or $(TEMP) or $(TMP) now
  3077.             if (DosQueryPathInfo((PSZ)pcszTemp,
  3078.                                  FIL_STANDARD,
  3079.                                  &fs3,
  3080.                                  sizeof(fs3)))
  3081.                 // TEMP doesn't exist:
  3082.                 pcszTemp = NULL;
  3083.  
  3084.         if (!pcszTemp)
  3085.             // not set, or doesn't exist:
  3086.             // use root directory on boot drive
  3087.             sprintf(szDir,
  3088.                     "%c:\\",
  3089.                     doshQueryBootDrive());
  3090.         else
  3091.         {
  3092.             strcpy(szDir, pcszTemp);
  3093.             if (szDir[strlen(szDir) - 1] != '\\')
  3094.                 strcat(szDir, "\\");
  3095.         }
  3096.  
  3097.         if (!szDir[0])
  3098.             arc = ERROR_PATH_NOT_FOUND;     // shouldn't happen
  3099.         else
  3100.         {
  3101.             ULONG       ulRandom = 0;
  3102.             ULONG       cAttempts = 0;
  3103.  
  3104.             // produce random number
  3105.             DosQuerySysInfo(QSV_MS_COUNT,
  3106.                             QSV_MS_COUNT,
  3107.                             &ulRandom,
  3108.                             sizeof(ulRandom));
  3109.  
  3110.             do
  3111.             {
  3112.                 CHAR szFile[20] = "",
  3113.                      szFullTryThis[CCHMAXPATH];
  3114.  
  3115.                 // use the lower eight hex digits of the
  3116.                 // system uptime as the temp dir name
  3117.                 sprintf(szFile,
  3118.                         "%08lX",
  3119.                         ulRandom & 0xFFFFFFFF);
  3120.  
  3121.                 // if prefix is specified, overwrite the
  3122.                 // first characters in the random number
  3123.                 if (pcszPrefix)
  3124.                     memcpy(szFile, pcszPrefix, ulPrefixLen);
  3125.  
  3126.                 if (pcszExt)
  3127.                 {
  3128.                     szFile[8] = '.';
  3129.                     strcpy(szFile + 9, pcszExt);
  3130.                 }
  3131.  
  3132.                 // now compose full temp file name
  3133.                 strcpy(szFullTryThis, szDir);
  3134.                 strcat(szFullTryThis, szFile);
  3135.                 // now we have: "C:\temp\wpiXXXXX"
  3136.                 if (DosQueryPathInfo(szFullTryThis,
  3137.                                      FIL_STANDARD,
  3138.                                      &fs3,
  3139.                                      sizeof(fs3))
  3140.                         == ERROR_FILE_NOT_FOUND)
  3141.                 {
  3142.                     // file or dir doesn't exist:
  3143.                     // cool, we're done
  3144.                     strcpy(pszTempFileName, szFullTryThis);
  3145.                     return NO_ERROR;
  3146.                 }
  3147.  
  3148.                 // if this didn't work, raise ulRandom and try again
  3149.                 ulRandom += 123;
  3150.  
  3151.                 // try only 100 times, just to be sure
  3152.                 cAttempts++;
  3153.             } while (cAttempts < 100);
  3154.  
  3155.             // 100 loops elapsed:
  3156.             arc = ERROR_BAD_FORMAT;
  3157.         }
  3158.     }
  3159.  
  3160.     return arc;
  3161. }
  3162.  
  3163. /*
  3164.  *@@ doshWriteTextFile:
  3165.  *      writes a text file to disk; pszFile must contain the
  3166.  *      whole path and filename.
  3167.  *
  3168.  *      An existing file will be backed up if (pszBackup != NULL),
  3169.  *      using doshCreateBackupFileName. In that case, pszBackup
  3170.  *      receives the name of the backup created, so that buffer
  3171.  *      should be CCHMAXPATH in size.
  3172.  *
  3173.  *      If (pszbackup == NULL), an existing file will be overwritten.
  3174.  *
  3175.  *      On success (NO_ERROR returned), *pulWritten receives
  3176.  *      the no. of bytes written.
  3177.  *
  3178.  *@@changed V0.9.3 (2000-05-01) [umoeller]: optimized DosOpen; added error checking; changed prototype
  3179.  *@@changed V0.9.3 (2000-05-12) [umoeller]: added pszBackup
  3180.  */
  3181.  
  3182. APIRET doshWriteTextFile(const char* pszFile,        // in: file name
  3183.                          const char* pszContent,     // in: text to write
  3184.                          PULONG pulWritten,          // out: bytes written (ptr can be NULL)
  3185.                          PSZ pszBackup)              // in/out: create-backup?
  3186. {
  3187.     APIRET  arc = NO_ERROR;
  3188.     ULONG   ulWritten = 0;
  3189.  
  3190.     if ((!pszFile) || (!pszContent))
  3191.         arc = ERROR_INVALID_PARAMETER;
  3192.     else
  3193.     {
  3194.         ULONG   ulAction = 0,
  3195.                 ulLocal = 0;
  3196.         HFILE   hFile = 0;
  3197.  
  3198.         ULONG ulSize = strlen(pszContent);      // exclude 0 byte
  3199.  
  3200.         if (pszBackup)
  3201.         {
  3202.             PSZ     pszBackup2 = doshCreateBackupFileName(pszFile);
  3203.             DosCopy((PSZ)pszFile,
  3204.                     pszBackup2,
  3205.                     DCPY_EXISTING); // delete existing
  3206.             strcpy(pszBackup, pszBackup2);
  3207.             free(pszBackup2);
  3208.         }
  3209.  
  3210.         if (!(arc = DosOpen((PSZ)pszFile,
  3211.                             &hFile,
  3212.                             &ulAction,                      // action taken
  3213.                             ulSize,                         // primary allocation size
  3214.                             FILE_ARCHIVED | FILE_NORMAL,    // file attribute
  3215.                             OPEN_ACTION_CREATE_IF_NEW
  3216.                                | OPEN_ACTION_REPLACE_IF_EXISTS,  // open flags
  3217.                             OPEN_FLAGS_NOINHERIT
  3218.                                | OPEN_FLAGS_SEQUENTIAL         // sequential, not random access
  3219.                                | OPEN_SHARE_DENYWRITE          // deny write mode
  3220.                                | OPEN_ACCESS_WRITEONLY,        // write mode
  3221.                             NULL)))                         // no EAs
  3222.         {
  3223.             if (!(arc = DosSetFilePtr(hFile,
  3224.                                       0L,
  3225.                                       FILE_BEGIN,
  3226.                                       &ulLocal)))
  3227.                 if (!(arc = DosWrite(hFile,
  3228.                                      (PVOID)pszContent,
  3229.                                      ulSize,
  3230.                                      &ulWritten)))
  3231.                     arc = DosSetFileSize(hFile, ulSize);
  3232.  
  3233.             DosClose(hFile);
  3234.         }
  3235.     } // end if ((pszFile) && (pszContent))
  3236.  
  3237.     if (pulWritten)
  3238.         *pulWritten = ulWritten;
  3239.  
  3240.     return arc;
  3241. }
  3242.  
  3243. /*
  3244.  *@@category: Helpers\Control program helpers\Directory management
  3245.  *      directory helpers (querying, creating, deleting etc.).
  3246.  */
  3247.  
  3248. /* ******************************************************************
  3249.  *
  3250.  *   Directory helpers
  3251.  *
  3252.  ********************************************************************/
  3253.  
  3254. /*
  3255.  *@@ doshQueryDirExist:
  3256.  *      returns TRUE if the given directory
  3257.  *      exists and really is a directory.
  3258.  */
  3259.  
  3260. BOOL doshQueryDirExist(PCSZ pcszDir)
  3261. {
  3262.     FILESTATUS3 fs3;
  3263.     APIRET arc;
  3264.  
  3265.     if (!(arc = DosQueryPathInfo((PSZ)pcszDir,
  3266.                                  FIL_STANDARD,
  3267.                                  &fs3,
  3268.                                  sizeof(fs3))))
  3269.         // file found:
  3270.         return ((fs3.attrFile & FILE_DIRECTORY) != 0);
  3271.     else
  3272.         return FALSE;
  3273. }
  3274.  
  3275. /*
  3276.  *@@ doshCreatePath:
  3277.  *      this creates the specified directory.
  3278.  *      As opposed to DosCreateDir, this
  3279.  *      function can create several directories
  3280.  *      at the same time, if the parent
  3281.  *      directories do not exist yet.
  3282.  */
  3283.  
  3284. APIRET doshCreatePath(PCSZ pcszPath,
  3285.                       BOOL fHidden) // in: if TRUE, the new directories will get FILE_HIDDEN
  3286. {
  3287.     APIRET  arc0 = NO_ERROR;
  3288.     CHAR    path[CCHMAXPATH];
  3289.     CHAR    *cp, c;
  3290.     ULONG   cbPath;
  3291.  
  3292.     strcpy(path, pcszPath);
  3293.     cbPath = strlen(path);
  3294.  
  3295.     if (path[cbPath] != '\\')
  3296.     {
  3297.         path[cbPath] = '\\';
  3298.         path[cbPath + 1] = 0;
  3299.     }
  3300.  
  3301.     cp = path;
  3302.     // advance past the drive letter only if we have one
  3303.     if (*(cp+1) == ':')
  3304.         cp += 3;
  3305.  
  3306.     // go!
  3307.     while (*cp != 0)
  3308.     {
  3309.         if (*cp == '\\')
  3310.         {
  3311.             c = *cp;
  3312.             *cp = 0;
  3313.             if (!doshQueryDirExist(path))
  3314.             {
  3315.                 APIRET arc = DosCreateDir(path,
  3316.                                           0);   // no EAs
  3317.                 if (arc != NO_ERROR)
  3318.                 {
  3319.                     arc0 = arc;
  3320.                     break;
  3321.                 }
  3322.                 else
  3323.                     if (fHidden)
  3324.                     {
  3325.                         // hide the directory we just created
  3326.                         doshSetPathAttr(path, FILE_HIDDEN);
  3327.                     }
  3328.             }
  3329.             *cp = c;
  3330.         }
  3331.         cp++;
  3332.     }
  3333.  
  3334.     return (arc0);
  3335. }
  3336.  
  3337. /*
  3338.  *@@ doshQueryCurrentDir:
  3339.  *      writes the current directory into
  3340.  *      the specified buffer, which should be
  3341.  *      CCHMAXPATH in size.
  3342.  *
  3343.  *      As opposed to DosQueryCurrentDir, this
  3344.  *      includes the drive letter.
  3345.  *
  3346.  *@@added V0.9.4 (2000-07-22) [umoeller]
  3347.  */
  3348.  
  3349. APIRET doshQueryCurrentDir(PSZ pszBuf)
  3350. {
  3351.     APIRET arc = NO_ERROR;
  3352.     ULONG   ulCurDisk = 0;
  3353.     ULONG   ulMap = 0;
  3354.     if (!(arc = DosQueryCurrentDisk(&ulCurDisk, &ulMap)))
  3355.     {
  3356.         ULONG   cbBuf = CCHMAXPATH - 3;
  3357.         pszBuf[0] = ulCurDisk + 'A' - 1;
  3358.         pszBuf[1] = ':';
  3359.         pszBuf[2] = '\\';
  3360.         pszBuf[3] = '\0';
  3361.         arc = DosQueryCurrentDir(0, pszBuf + 3, &cbBuf);
  3362.     }
  3363.  
  3364.     return arc;
  3365. }
  3366.  
  3367. /*
  3368.  *@@ doshDeleteDir:
  3369.  *      deletes a directory. As opposed to DosDeleteDir,
  3370.  *      this removes empty subdirectories and/or files
  3371.  *      as well.
  3372.  *
  3373.  *      flFlags can be any combination of the following:
  3374.  *
  3375.  *      -- DOSHDELDIR_RECURSE: recurse into subdirectories.
  3376.  *
  3377.  *      -- DOSHDELDIR_DELETEFILES: delete all regular files
  3378.  *              which are found on the way.
  3379.  *
  3380.  *              THIS IS NOT IMPLEMENTED YET.
  3381.  *
  3382.  *      If 0 is specified, this effectively behaves just as
  3383.  *      DosDeleteDir.
  3384.  *
  3385.  *      If you specify DOSHDELDIR_RECURSE only, only empty
  3386.  *      subdirectories are deleted as well.
  3387.  *
  3388.  *      If you specify DOSHDELDIR_RECURSE | DOSHDELDIR_DELETEFILES,
  3389.  *      this removes an entire directory tree, including all
  3390.  *      subdirectories and files.
  3391.  *
  3392.  *@@added V0.9.4 (2000-07-01) [umoeller]
  3393.  */
  3394.  
  3395. APIRET doshDeleteDir(PCSZ pcszDir,
  3396.                      ULONG flFlags,
  3397.                      PULONG pulDirs,        // out: directories found
  3398.                      PULONG pulFiles)       // out: files found
  3399. {
  3400.     APIRET          arc = NO_ERROR,
  3401.                     arcReturn = NO_ERROR;
  3402.  
  3403.     HDIR            hdirFindHandle = HDIR_CREATE;
  3404.     FILEFINDBUF3    ffb3     = {0};      // returned from FindFirst/Next
  3405.     ULONG           ulResultBufLen = sizeof(FILEFINDBUF3);
  3406.     ULONG           ulFindCount    = 1;        // look for 1 file at a time
  3407.  
  3408.     CHAR            szFileMask[2*CCHMAXPATH];
  3409.     sprintf(szFileMask, "%s\\*", pcszDir);
  3410.  
  3411.     // find files
  3412.     arc = DosFindFirst(szFileMask,
  3413.                        &hdirFindHandle,     // directory search handle
  3414.                        FILE_ARCHIVED | FILE_DIRECTORY | FILE_SYSTEM
  3415.                          | FILE_HIDDEN | FILE_READONLY,
  3416.                                      // search attributes; include all, even dirs
  3417.                        &ffb3,               // result buffer
  3418.                        ulResultBufLen,      // result buffer length
  3419.                        &ulFindCount,        // number of entries to find
  3420.                        FIL_STANDARD);       // return level 1 file info
  3421.  
  3422.     if (arc == NO_ERROR)
  3423.     {
  3424.         // keep finding the next file until there are no more files
  3425.         while (arc == NO_ERROR)     // != ERROR_NO_MORE_FILES
  3426.         {
  3427.             if (ffb3.attrFile & FILE_DIRECTORY)
  3428.             {
  3429.                 // we found a directory:
  3430.  
  3431.                 // ignore the pseudo-directories
  3432.                 if (    (strcmp(ffb3.achName, ".") != 0)
  3433.                      && (strcmp(ffb3.achName, "..") != 0)
  3434.                    )
  3435.                 {
  3436.                     // real directory:
  3437.                     if (flFlags & DOSHDELDIR_RECURSE)
  3438.                     {
  3439.                         // recurse!
  3440.                         CHAR szSubDir[2*CCHMAXPATH];
  3441.                         sprintf(szSubDir, "%s\\%s", pcszDir, ffb3.achName);
  3442.                         arcReturn = doshDeleteDir(szSubDir,
  3443.                                                   flFlags,
  3444.                                                   pulDirs,
  3445.                                                   pulFiles);
  3446.                         // this removes ffb3.achName as well
  3447.                     }
  3448.                     else
  3449.                     {
  3450.                         // directory, but no recursion:
  3451.                         // report "access denied"
  3452.                         // (this is what OS/2 reports with DosDeleteDir as well)
  3453.                         arcReturn = ERROR_ACCESS_DENIED;  // 5
  3454.                         (*pulDirs)++;
  3455.                     }
  3456.                 }
  3457.             }
  3458.             else
  3459.             {
  3460.                 // it's a file:
  3461.                 arcReturn = ERROR_ACCESS_DENIED;
  3462.                 (*pulFiles)++;
  3463.             }
  3464.  
  3465.             if (arc == NO_ERROR)
  3466.             {
  3467.                 ulFindCount = 1;                      // reset find count
  3468.                 arc = DosFindNext(hdirFindHandle,      // directory handle
  3469.                                   &ffb3,         // result buffer
  3470.                                   ulResultBufLen,      // result buffer length
  3471.                                   &ulFindCount);       // number of entries to find
  3472.             }
  3473.         } // endwhile
  3474.  
  3475.         DosFindClose(hdirFindHandle);    // close our find handle
  3476.     }
  3477.  
  3478.     if (arcReturn == NO_ERROR)
  3479.         // success so far:
  3480.         // delete our directory now
  3481.         arcReturn = DosDeleteDir((PSZ)pcszDir);
  3482.  
  3483.     return (arcReturn);
  3484. }
  3485.  
  3486. /*
  3487.  *@@ doshCanonicalize:
  3488.  *      simplifies path specifications to remove '.'
  3489.  *      and '..' entries and generates a fully
  3490.  *      qualified path name where possible.
  3491.  *      File specifications are left unchanged.
  3492.  *
  3493.  *      This returns:
  3494.  *
  3495.  *      --  NO_ERROR: the buffers were valid.
  3496.  *
  3497.  *      --  ERROR_INVALID_PARAMETER: the buffers
  3498.  *          were invalid.
  3499.  *
  3500.  *@@added V0.9.19 (2002-04-22) [pr]
  3501.  */
  3502.  
  3503. APIRET doshCanonicalize(PCSZ pcszFileIn,        // in: path to canonicalize
  3504.                         PSZ pszFileOut,         // out: canonicalized path if NO_ERROR
  3505.                         ULONG cbFileOut)        // in: size of pszFileOut buffer
  3506. {
  3507.     APIRET ulrc = NO_ERROR;
  3508.     CHAR szFileTemp[CCHMAXPATH];
  3509.  
  3510.     if (pcszFileIn && pszFileOut && cbFileOut)
  3511.     {
  3512.         strncpy(szFileTemp, pcszFileIn, sizeof(szFileTemp) - 1);
  3513.         szFileTemp[sizeof(szFileTemp) - 1] = 0;
  3514.         if (    strchr(szFileTemp, '\\')
  3515.              || strchr(szFileTemp, ':')
  3516.            )
  3517.         {
  3518.             ULONG cbFileTemp = strlen(szFileTemp);
  3519.  
  3520.             if (    (cbFileTemp > 3)
  3521.                  && (szFileTemp[cbFileTemp - 1] == '\\')
  3522.                )
  3523.             {
  3524.                 szFileTemp[cbFileTemp - 1] = 0;
  3525.             }
  3526.  
  3527.             if (DosQueryPathInfo(szFileTemp,
  3528.                                  FIL_QUERYFULLNAME,
  3529.                                  pszFileOut,
  3530.                                  cbFileOut))
  3531.             {
  3532.                 pszFileOut[0] = 0;
  3533.             }
  3534.         }
  3535.         else
  3536.         {
  3537.             strncpy(pszFileOut, pcszFileIn, cbFileOut - 1);
  3538.             pszFileOut[cbFileOut - 1] = 0;
  3539.         }
  3540.     }
  3541.     else
  3542.         ulrc = ERROR_INVALID_PARAMETER;
  3543.  
  3544.     return(ulrc);
  3545. }
  3546.  
  3547. /*
  3548.  *@@category: Helpers\Control program helpers\Module handling
  3549.  *      helpers for importing functions from a module (DLL).
  3550.  */
  3551.  
  3552. /* ******************************************************************
  3553.  *
  3554.  *   Module handling helpers
  3555.  *
  3556.  ********************************************************************/
  3557.  
  3558. /*
  3559.  *@@ doshQueryProcAddr:
  3560.  *      attempts to resolve the given procedure from
  3561.  *      the given module name. Saves you from querying
  3562.  *      the module handle and all that.
  3563.  *
  3564.  *      This is intended for resolving undocumented
  3565.  *      APIs from OS/2 system DLls such as PMMERGE
  3566.  *      and DOSCALLS. It is assumed that the specified
  3567.  *      module is already loaded.
  3568.  *
  3569.  *      Returns:
  3570.  *
  3571.  *      --  NO_ERROR
  3572.  *
  3573.  *      --  ERROR_INVALID_NAME
  3574.  *
  3575.  *      --  ERROR_INVALID_ORDINAL
  3576.  *
  3577.  *      plus the error codes of DosLoadModule.
  3578.  *
  3579.  *@@added V0.9.16 (2002-01-13) [umoeller]
  3580.  */
  3581.  
  3582. APIRET doshQueryProcAddr(PCSZ pcszModuleName,       // in: module name (e.g. "PMMERGE")
  3583.                          ULONG ulOrdinal,           // in: proc ordinal
  3584.                          PFN *ppfn)                 // out: proc address
  3585. {
  3586.     HMODULE hmod;
  3587.     APIRET  arc;
  3588.     if (!(arc = DosQueryModuleHandle((PSZ)pcszModuleName,
  3589.                                      &hmod)))
  3590.     {
  3591.         if ((arc = DosQueryProcAddr(hmod,
  3592.                                     ulOrdinal,
  3593.                                     NULL,
  3594.                                     ppfn)))
  3595.         {
  3596.             // the CP programming guide and reference says use
  3597.             // DosLoadModule if DosQueryProcAddr fails with this error
  3598.             if (arc == ERROR_INVALID_HANDLE)
  3599.             {
  3600.                 if (!(arc = DosLoadModule(NULL,
  3601.                                           0,
  3602.                                           (PSZ)pcszModuleName,
  3603.                                           &hmod)))
  3604.                 {
  3605.                     arc = DosQueryProcAddr(hmod,
  3606.                                            ulOrdinal,
  3607.                                            NULL,
  3608.                                            ppfn);
  3609.                 }
  3610.             }
  3611.         }
  3612.     }
  3613.  
  3614.     return arc;
  3615. }
  3616.  
  3617. /*
  3618.  *@@ doshResolveImports:
  3619.  *      this function loads the module called pszModuleName
  3620.  *      and resolves imports dynamically using DosQueryProcAddress.
  3621.  *
  3622.  *      To specify the functions to be imported, a RESOLVEFUNCTION
  3623.  *      array is used. In each of the array items, specify the
  3624.  *      name of the function and a pointer to a function pointer
  3625.  *      where to store the resolved address.
  3626.  *
  3627.  *@@added V0.9.3 (2000-04-29) [umoeller]
  3628.  */
  3629.  
  3630. APIRET doshResolveImports(PCSZ pcszModuleName,    // in: DLL to load
  3631.                           HMODULE *phmod,       // out: module handle
  3632.                           PCRESOLVEFUNCTION paResolves, // in/out: function resolves
  3633.                           ULONG cResolves)      // in: array item count (not array size!)
  3634. {
  3635.     CHAR    szName[CCHMAXPATH];
  3636.     APIRET  arc;
  3637.  
  3638.     if (!(arc = DosLoadModule(szName,
  3639.                               sizeof(szName),
  3640.                               (PSZ)pcszModuleName,
  3641.                               phmod)))
  3642.     {
  3643.         ULONG  ul;
  3644.         for (ul = 0;
  3645.              ul < cResolves;
  3646.              ul++)
  3647.         {
  3648.             if (arc = DosQueryProcAddr(*phmod,
  3649.                                        0,               // ordinal, ignored
  3650.                                        (PSZ)paResolves[ul].pcszFunctionName,
  3651.                                        paResolves[ul].ppFuncAddress))
  3652.                 // error:
  3653.                 break;
  3654.         }
  3655.  
  3656.         if (arc)
  3657.             // V0.9.16 (2001-12-08) [umoeller]
  3658.             DosFreeModule(*phmod);
  3659.     }
  3660.  
  3661.     return arc;
  3662. }
  3663.  
  3664. /*
  3665.  *@@category: Helpers\Control program helpers\Performance (CPU load) helpers
  3666.  *      helpers around DosPerfSysCall.
  3667.  */
  3668.  
  3669. /* ******************************************************************
  3670.  *
  3671.  *   Performance Counters (CPU Load)
  3672.  *
  3673.  ********************************************************************/
  3674.  
  3675. /*
  3676.  *@@ doshPerfOpen:
  3677.  *      initializes the OS/2 DosPerfSysCall API for
  3678.  *      the calling thread.
  3679.  *
  3680.  *      Note: This API is not supported on all OS/2
  3681.  *      versions. I believe it came up with some Warp 4
  3682.  *      fixpak. The API is resolved dynamically by
  3683.  *      this function (using DosQueryProcAddr).
  3684.  *
  3685.  *      This properly initializes the internal counters
  3686.  *      which the OS/2 kernel uses for this API. Apparently,
  3687.  *      with newer kernels (FP13/14), IBM has chosen to no
  3688.  *      longer do this automatically, which is the reason
  3689.  *      why many "pulse" utilities display garbage with these
  3690.  *      fixpaks.
  3691.  *
  3692.  *      After NO_ERROR is returned, DOSHPERFSYS.cProcessors
  3693.  *      contains the no. of processors found on the system.
  3694.  *      All pointers in DOSHPERFSYS then point to arrays
  3695.  *      which have exactly cProcessors array items.
  3696.  *
  3697.  *      So after NO_ERROR was returned here, you can keep
  3698.  *      calling doshPerfGet to get a current snapshot of the
  3699.  *      IRQ and user loads for each CPU. Note that interrupts
  3700.  *      are only processed on CPU 0 on SMP systems.
  3701.  *
  3702.  *      Call doshPerfClose to clean up resources allocated
  3703.  *      by this function.
  3704.  *
  3705.  *      For more sample code, take a look at the "Pulse" widget
  3706.  *      in the XWorkplace sources (src\xcenter\w_pulse.c).
  3707.  *
  3708.  *      Example code:
  3709.  *
  3710.  +      PDOSHPERFSYS pPerf = NULL;
  3711.  +      APIRET arc;
  3712.  +      if (!(arc = arc = doshPerfOpen(&pPerf)))
  3713.  +      {
  3714.  +          // this should really be in a timer,
  3715.  +          // e.g. once per second
  3716.  +          ULONG   ulCPU;
  3717.  +          if (!(arc = doshPerfGet(&pPerf)))
  3718.  +          {
  3719.  +              // go thru all CPUs
  3720.  +              for (ulCPU = 0; ulCPU < pPerf->cProcessors; ulCPU++)
  3721.  +              {
  3722.  +                  LONG lUserLoadThis = pPerf->palLoads[ulCPU];
  3723.  +                  ...
  3724.  +              }
  3725.  +          }
  3726.  +
  3727.  +          // clean up
  3728.  +          doshPerfClose(&pPerf);
  3729.  +      }
  3730.  +
  3731.  *
  3732.  *@@added V0.9.7 (2000-12-02) [umoeller]
  3733.  *@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
  3734.  */
  3735.  
  3736. APIRET doshPerfOpen(PDOSHPERFSYS *ppPerfSys)  // out: new DOSHPERFSYS structure
  3737. {
  3738.     APIRET  arc = NO_ERROR;
  3739.  
  3740.     // allocate DOSHPERFSYS structure
  3741.     *ppPerfSys = (PDOSHPERFSYS)malloc(sizeof(DOSHPERFSYS));
  3742.     if (!*ppPerfSys)
  3743.         arc = ERROR_NOT_ENOUGH_MEMORY;
  3744.     else
  3745.     {
  3746.         // initialize structure
  3747.         PDOSHPERFSYS pPerfSys = *ppPerfSys;
  3748.         memset(pPerfSys, 0, sizeof(*pPerfSys));
  3749.  
  3750.         // resolve DosPerfSysCall API entry
  3751.         if (!(arc = DosLoadModule(NULL, 0, "DOSCALLS", &pPerfSys->hmod)))
  3752.         {
  3753.             if (!(arc = DosQueryProcAddr(pPerfSys->hmod,
  3754.                                          976,
  3755.                                          "DosPerfSysCall",
  3756.                                          (PFN*)(&pPerfSys->pDosPerfSysCall))))
  3757.             {
  3758.                 // OK, we got the API: initialize!
  3759.                 if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_ENABLE, 0, 0, 0)))
  3760.                 {
  3761.                     pPerfSys->fInitialized = TRUE;
  3762.                             // call CMD_KI_DISABLE later
  3763.  
  3764.                     if (!(arc = pPerfSys->pDosPerfSysCall(CMD_PERF_INFO,
  3765.                                                           0,
  3766.                                                           (ULONG)(&pPerfSys->cProcessors),
  3767.                                                           0)))
  3768.                     {
  3769.                         ULONG   ul = 0,
  3770.                                 cProcs = pPerfSys->cProcessors,
  3771.                                 cbDouble = cProcs * sizeof(double),
  3772.                                 cbLong = cProcs * sizeof(LONG);
  3773.  
  3774.                         // allocate arrays
  3775.                         if (    (!(pPerfSys->paCPUUtils = (PCPUUTIL)calloc(cProcs,
  3776.                                                                 sizeof(CPUUTIL))))
  3777.                              || (!(pPerfSys->padBusyPrev
  3778.                                     = (double*)malloc(cbDouble)))
  3779.                              || (!(pPerfSys->padTimePrev
  3780.                                     = (double*)malloc(cbDouble)))
  3781.                              || (!(pPerfSys->padIntrPrev
  3782.                                     = (double*)malloc(cbDouble)))
  3783.                              || (!(pPerfSys->palLoads
  3784.                                     = (PLONG)malloc(cbLong)))
  3785.                              || (!(pPerfSys->palIntrs
  3786.                                     = (PLONG)malloc(cbLong)))
  3787.                            )
  3788.                             arc = ERROR_NOT_ENOUGH_MEMORY;
  3789.                         else
  3790.                         {
  3791.                             for (ul = 0; ul < cProcs; ul++)
  3792.                             {
  3793.                                 pPerfSys->padBusyPrev[ul] = 0.0;
  3794.                                 pPerfSys->padTimePrev[ul] = 0.0;
  3795.                                 pPerfSys->padIntrPrev[ul] = 0.0;
  3796.                                 pPerfSys->palLoads[ul] = 0;
  3797.                                 pPerfSys->palIntrs[ul] = 0;
  3798.                             }
  3799.                         }
  3800.                     }
  3801.                 }
  3802.             } // end if (arc == NO_ERROR)
  3803.         } // end if (arc == NO_ERROR)
  3804.  
  3805.         if (arc)
  3806.             // error: clean up
  3807.             doshPerfClose(ppPerfSys);
  3808.  
  3809.     } // end else if (!*ppPerfSys)
  3810.  
  3811.     return arc;
  3812. }
  3813.  
  3814. /*
  3815.  *@@ doshPerfGet:
  3816.  *      calculates a current snapshot of the system load,
  3817.  *      compared with the load which was calculated on
  3818.  *      the previous call.
  3819.  *
  3820.  *      If you want to continually measure the system CPU
  3821.  *      load, this is the function you will want to call
  3822.  *      regularly -- e.g. with a timer once per second.
  3823.  *
  3824.  *      Call this ONLY if doshPerfOpen returned NO_ERROR,
  3825.  *      or you'll get crashes.
  3826.  *
  3827.  *      If this call returns NO_ERROR, you get LONG load
  3828.  *      values for each CPU in the system in the arrays
  3829.  *      in DOSHPERFSYS (in per-mille, 0-1000).
  3830.  *
  3831.  *      There are two arrays:
  3832.  *
  3833.  *      -- DOSHPERFSYS.palLoads contains the "user" load
  3834.  *         for each CPU.
  3835.  *
  3836.  *      -- DOSHPERFSYS.palIntrs contains the "IRQ" load
  3837.  *         for each CPU.
  3838.  *
  3839.  *      Sum up the two values to get the total load for
  3840.  *      each CPU.
  3841.  *
  3842.  *      For example, if there are two CPUs, after this call,
  3843.  *
  3844.  *      -- DOSHPERFSYS.palLoads[0] contains the "user" load
  3845.  *         of the first CPU,
  3846.  *
  3847.  *      -- DOSHPERFSYS.palLoads[0] contains the "user" load
  3848.  *         of the second CPU.
  3849.  *
  3850.  *      See doshPerfOpen for example code.
  3851.  *
  3852.  *@@added V0.9.7 (2000-12-02) [umoeller]
  3853.  *@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
  3854.  */
  3855.  
  3856. APIRET doshPerfGet(PDOSHPERFSYS pPerfSys)
  3857. {
  3858.     APIRET arc = NO_ERROR;
  3859.     if (!pPerfSys->pDosPerfSysCall)
  3860.         arc = ERROR_INVALID_PARAMETER;
  3861.     else
  3862.     {
  3863.         if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_RDCNT,
  3864.                                               (ULONG)pPerfSys->paCPUUtils,
  3865.                                               0, 0)))
  3866.         {
  3867.             // go thru all processors
  3868.             ULONG ul = 0;
  3869.             for (; ul < pPerfSys->cProcessors; ul++)
  3870.             {
  3871.                 PCPUUTIL    pCPUUtilThis = &pPerfSys->paCPUUtils[ul];
  3872.  
  3873.                 double      dTime = LL2F(pCPUUtilThis->ulTimeHigh,
  3874.                                          pCPUUtilThis->ulTimeLow);
  3875.                 double      dBusy = LL2F(pCPUUtilThis->ulBusyHigh,
  3876.                                          pCPUUtilThis->ulBusyLow);
  3877.                 double      dIntr = LL2F(pCPUUtilThis->ulIntrHigh,
  3878.                                          pCPUUtilThis->ulIntrLow);
  3879.  
  3880.                 double      *pdBusyPrevThis = &pPerfSys->padBusyPrev[ul];
  3881.                 double      *pdTimePrevThis = &pPerfSys->padTimePrev[ul];
  3882.                 double      *pdIntrPrevThis = &pPerfSys->padIntrPrev[ul];
  3883.  
  3884.                 // avoid division by zero
  3885.                 double      dTimeDelta;
  3886.                 if (dTimeDelta = (dTime - *pdTimePrevThis))
  3887.                 {
  3888.                     pPerfSys->palLoads[ul]
  3889.                         = (LONG)( (double)(   (dBusy - *pdBusyPrevThis)
  3890.                                             / dTimeDelta
  3891.                                             * 1000.0
  3892.                                           )
  3893.                                 );
  3894.                     pPerfSys->palIntrs[ul]
  3895.                         = (LONG)( (double)(   (dIntr - *pdIntrPrevThis)
  3896.                                             / dTimeDelta
  3897.                                             * 1000.0
  3898.                                           )
  3899.                                 );
  3900.                 }
  3901.                 else
  3902.                 {
  3903.                     // no clear readings are available
  3904.                     pPerfSys->palLoads[ul] = 0;
  3905.                     pPerfSys->palIntrs[ul] = 0;
  3906.                 }
  3907.  
  3908.                 *pdTimePrevThis = dTime;
  3909.                 *pdBusyPrevThis = dBusy;
  3910.                 *pdIntrPrevThis = dIntr;
  3911.             }
  3912.         }
  3913.     }
  3914.  
  3915.     return arc;
  3916. }
  3917.  
  3918. /*
  3919.  *@@ doshPerfClose:
  3920.  *      frees all resources allocated by doshPerfOpen.
  3921.  *
  3922.  *@@added V0.9.7 (2000-12-02) [umoeller]
  3923.  *@@changed V0.9.9 (2001-02-06) [umoeller]: removed disable; this broke the WarpCenter
  3924.  *@@changed V0.9.9 (2001-03-14) [umoeller]: fixed memory leak
  3925.  *@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
  3926.  */
  3927.  
  3928. APIRET doshPerfClose(PDOSHPERFSYS *ppPerfSys)
  3929. {
  3930.     APIRET arc = NO_ERROR;
  3931.     PDOSHPERFSYS pPerfSys;
  3932.     if (    (!ppPerfSys)
  3933.          || (!(pPerfSys = *ppPerfSys))
  3934.        )
  3935.         arc = ERROR_INVALID_PARAMETER;
  3936.     else
  3937.     {
  3938.         // do not call this, this messes up the WarpCenter V0.9.9 (2001-02-06) [umoeller]
  3939.         // if (pPerfSys->fInitialized) pPerfSys->pDosPerfSysCall(CMD_KI_DISABLE, 0, 0, 0);
  3940.  
  3941.         if (pPerfSys->paCPUUtils)
  3942.             free(pPerfSys->paCPUUtils);
  3943.         if (pPerfSys->padBusyPrev)
  3944.             free(pPerfSys->padBusyPrev);
  3945.         if (pPerfSys->padTimePrev)
  3946.             free(pPerfSys->padTimePrev);
  3947.         if (pPerfSys->padIntrPrev)
  3948.             free(pPerfSys->padIntrPrev);
  3949.         if (pPerfSys->palLoads)             // was missing V0.9.9 (2001-03-14) [umoeller]
  3950.             free(pPerfSys->palLoads);
  3951.         if (pPerfSys->palIntrs)
  3952.             free(pPerfSys->palIntrs);
  3953.  
  3954.         if (pPerfSys->hmod)
  3955.             DosFreeModule(pPerfSys->hmod);
  3956.         free(pPerfSys);
  3957.         *ppPerfSys = NULL;
  3958.     }
  3959.  
  3960.     return arc;
  3961. }
  3962.  
  3963. /*
  3964.  *@@category: Helpers\Control program helpers\Process management
  3965.  *      helpers for starting subprocesses.
  3966.  */
  3967.  
  3968. /* ******************************************************************
  3969.  *
  3970.  *   Process helpers
  3971.  *
  3972.  ********************************************************************/
  3973.  
  3974. static PVOID    // G_pvGlobalInfoSeg = NULL,
  3975.                 G_pvLocalInfoSeg = NULL;
  3976.  
  3977. USHORT _Far16 _Pascal Dos16GetInfoSeg(PSEL pselGlobal,
  3978.                                       PSEL pselLocal);
  3979.  
  3980. /*
  3981.  * GetInfoSegs:
  3982.  *
  3983.  */
  3984.  
  3985. static VOID GetInfoSegs(VOID)
  3986. {
  3987.     SEL     GlobalInfoSegSelector,
  3988.             LocalInfoSegSelector;
  3989.  
  3990.     // get selectors (old 16-bit API; this gets called only once)
  3991.     Dos16GetInfoSeg(&GlobalInfoSegSelector,
  3992.                     &LocalInfoSegSelector);
  3993.     // thunk
  3994.     /* G_pvGlobalInfoSeg = (PVOID)(   (GlobalInfoSegSelector << 0x000D)
  3995.                                  & 0x01fff0000); */
  3996.     G_pvLocalInfoSeg  = (PVOID)(   (LocalInfoSegSelector << 0x000D)
  3997.                                  & 0x01fff0000);
  3998. }
  3999.  
  4000. /*
  4001.  *@@ doshMyPID:
  4002.  *      returns the PID of the current process.
  4003.  *
  4004.  *      This uses an interesting hack which is way
  4005.  *      faster than DosGetInfoBlocks.
  4006.  *
  4007.  *@@added V0.9.9 (2001-04-04) [umoeller]
  4008.  */
  4009.  
  4010. ULONG doshMyPID(VOID)
  4011. {
  4012.     if (!G_pvLocalInfoSeg)
  4013.         // first call:
  4014.         GetInfoSegs();
  4015.  
  4016.     // PID is at offset 0 in the local info seg
  4017.     return (*(PUSHORT)G_pvLocalInfoSeg);
  4018. }
  4019.  
  4020. /*
  4021.  *@@ doshMyTID:
  4022.  *      returns the TID of the current thread.
  4023.  *
  4024.  *      This uses an interesting hack which is way
  4025.  *      faster than DosGetInfoBlocks.
  4026.  *
  4027.  *@@added V0.9.9 (2001-04-04) [umoeller]
  4028.  */
  4029.  
  4030. ULONG doshMyTID(VOID)
  4031. {
  4032.     if (!G_pvLocalInfoSeg)
  4033.         // first call:
  4034.         GetInfoSegs();
  4035.  
  4036.     // TID is at offset 6 in the local info seg
  4037.     return (*(PUSHORT)((PBYTE)G_pvLocalInfoSeg + 6));
  4038. }
  4039.  
  4040. /*
  4041.  *@@ doshExecVIO:
  4042.  *      executes cmd.exe with the /c parameter
  4043.  *      and pcszExecWithArgs. This is equivalent
  4044.  *      to the C library system() function,
  4045.  *      except that an OS/2 error code is returned
  4046.  *      and that this works with the VAC subsystem
  4047.  *      library.
  4048.  *
  4049.  *      This uses DosExecPgm and handles the sick
  4050.  *      argument passing.
  4051.  *
  4052.  *      If NO_ERROR is returned, *plExitCode receives
  4053.  *      the exit code of the process. If the process
  4054.  *      was terminated abnormally, *plExitCode receives:
  4055.  *
  4056.  *      -- -1: hard error halt
  4057.  *      -- -2: 16-bit trap
  4058.  *      -- -3: DosKillProcess
  4059.  *      -- -4: 32-bit exception
  4060.  *
  4061.  *@@added V0.9.4 (2000-07-27) [umoeller]
  4062.  */
  4063.  
  4064. APIRET doshExecVIO(PCSZ pcszExecWithArgs,
  4065.                    PLONG plExitCode)            // out: exit code (ptr can be NULL)
  4066. {
  4067.     APIRET arc = NO_ERROR;
  4068.  
  4069.     if (strlen(pcszExecWithArgs) > 255)
  4070.         arc = ERROR_INSUFFICIENT_BUFFER;
  4071.     else
  4072.     {
  4073.         CHAR szObj[CCHMAXPATH];
  4074.         RESULTCODES res = {0};
  4075.         CHAR szBuffer[300];
  4076.  
  4077.         // DosExecPgm expects two args in szBuffer:
  4078.         // --  cmd.exe\0
  4079.         // --  then the actual argument, terminated by two 0 bytes.
  4080.         memset(szBuffer, 0, sizeof(szBuffer));
  4081.         strcpy(szBuffer, "cmd.exe\0");
  4082.         sprintf(szBuffer + 8, "/c %s",
  4083.                 pcszExecWithArgs);
  4084.  
  4085.         arc = DosExecPgm(szObj, sizeof(szObj),
  4086.                          EXEC_SYNC,
  4087.                          szBuffer,
  4088.                          0,
  4089.                          &res,
  4090.                          "cmd.exe");
  4091.         if ((arc == NO_ERROR) && (plExitCode))
  4092.         {
  4093.             if (res.codeTerminate == 0)
  4094.                 // normal exit:
  4095.                 *plExitCode = res.codeResult;
  4096.             else
  4097.                 *plExitCode = -((LONG)res.codeTerminate);
  4098.         }
  4099.     }
  4100.  
  4101.     return arc;
  4102. }
  4103.  
  4104. /*
  4105.  *@@ doshQuickStartSession:
  4106.  *      this is a shortcut to DosStartSession w/out having to
  4107.  *      deal with all the messy parameters.
  4108.  *
  4109.  *      This one starts pszPath as a child session and passes
  4110.  *      pszParams to it.
  4111.  *
  4112.  *      In usPgmCtl, OR any or none of the following (V0.9.0):
  4113.  *      --  SSF_CONTROL_NOAUTOCLOSE (0x0008): do not close automatically.
  4114.  *              This bit is used only for VIO Windowable apps and ignored
  4115.  *              for all other applications.
  4116.  *      --  SSF_CONTROL_MINIMIZE (0x0004)
  4117.  *      --  SSF_CONTROL_MAXIMIZE (0x0002)
  4118.  *      --  SSF_CONTROL_INVISIBLE (0x0001)
  4119.  *      --  SSF_CONTROL_VISIBLE (0x0000)
  4120.  *
  4121.  *      Specifying 0 will therefore auto-close the session and make it
  4122.  *      visible.
  4123.  *
  4124.  *      If (fWait), this function will create a termination queue
  4125.  *      and not return until the child session has ended. Be warned,
  4126.  *      this blocks the calling thread.
  4127.  *
  4128.  *      Otherwise the function will return immediately.
  4129.  *
  4130.  *      The session and process IDs of the child session will be
  4131.  *      written to *pulSID and *ppid. Of course, in "wait" mode,
  4132.  *      these are no longer valid after this function returns.
  4133.  *
  4134.  *      Returns the error code of DosStartSession.
  4135.  *
  4136.  *      Note: According to CPREF, calling DosStartSession calls
  4137.  *      DosExecPgm in turn for all full-screen, VIO, and PM sessions.
  4138.  *
  4139.  *@@changed V0.9.0 [umoeller]: prototype changed to include usPgmCtl
  4140.  *@@changed V0.9.1 (99-12-30) [umoeller]: queue was sometimes not closed. Fixed.
  4141.  *@@changed V0.9.3 (2000-05-03) [umoeller]: added fForeground
  4142.  *@@changed V0.9.14 (2001-08-03) [umoeller]: fixed potential queue leak
  4143.  *@@changed V0.9.14 (2001-08-03) [umoeller]: fixed memory leak in wait mode; added pusReturn to prototype
  4144.  */
  4145.  
  4146. APIRET doshQuickStartSession(PCSZ pcszPath,       // in: program to start
  4147.                              PCSZ pcszParams,     // in: parameters for program
  4148.                              BOOL fForeground,  // in: if TRUE, session will be in foreground
  4149.                              USHORT usPgmCtl,   // in: STARTDATA.PgmControl
  4150.                              BOOL fWait,        // in: wait for termination?
  4151.                              PULONG pulSID,     // out: session ID (req.)
  4152.                              PPID ppid,         // out: process ID (req.)
  4153.                              PUSHORT pusReturn) // out: in wait mode, session's return code (ptr can be NULL)
  4154. {
  4155.     APIRET      arc = NO_ERROR;
  4156.     // queue stuff
  4157.     const char  *pcszQueueName = "\\queues\\xwphlpsw.que";
  4158.     HQUEUE      hq = 0;
  4159.     PID         qpid = 0;
  4160.  
  4161.     if (fWait)
  4162.     {
  4163.         if (!(arc = DosCreateQueue(&hq,
  4164.                                    QUE_FIFO | QUE_CONVERT_ADDRESS,
  4165.                                    (PSZ)pcszQueueName)))
  4166.             arc = DosOpenQueue(&qpid, &hq, (PSZ)pcszQueueName);
  4167.     }
  4168.  
  4169.     if (!arc)    // V0.9.14 (2001-08-03) [umoeller]
  4170.     {
  4171.         STARTDATA   SData;
  4172.         CHAR        szObjBuf[CCHMAXPATH];
  4173.  
  4174.         SData.Length  = sizeof(STARTDATA);
  4175.         SData.Related = SSF_RELATED_CHILD; //INDEPENDENT;
  4176.         SData.FgBg    = (fForeground) ? SSF_FGBG_FORE : SSF_FGBG_BACK;
  4177.                 // V0.9.3 (2000-05-03) [umoeller]
  4178.         SData.TraceOpt = SSF_TRACEOPT_NONE;
  4179.  
  4180.         SData.PgmTitle = (PSZ)pcszPath;       // title for window
  4181.         SData.PgmName = (PSZ)pcszPath;
  4182.         SData.PgmInputs = (PSZ)pcszParams;
  4183.  
  4184.         SData.TermQ = (fWait) ? (PSZ)pcszQueueName : NULL;
  4185.         SData.Environment = 0;
  4186.         SData.InheritOpt = SSF_INHERTOPT_PARENT;
  4187.         SData.SessionType = SSF_TYPE_DEFAULT;
  4188.         SData.IconFile = 0;
  4189.         SData.PgmHandle = 0;
  4190.  
  4191.         SData.PgmControl = usPgmCtl;
  4192.  
  4193.         SData.InitXPos  = 30;
  4194.         SData.InitYPos  = 40;
  4195.         SData.InitXSize = 200;
  4196.         SData.InitYSize = 140;
  4197.         SData.Reserved = 0;
  4198.         SData.ObjectBuffer  = szObjBuf;
  4199.         SData.ObjectBuffLen = (ULONG)sizeof(szObjBuf);
  4200.  
  4201.         if (    (!(arc = DosStartSession(&SData, pulSID, ppid)))
  4202.              && (fWait)
  4203.            )
  4204.         {
  4205.             // block on the termination queue, which is written
  4206.             // to when the subprocess ends
  4207.             REQUESTDATA rqdata;
  4208.             ULONG       cbData = 0;
  4209.             PULONG      pulData = NULL;
  4210.             BYTE        elpri;
  4211.  
  4212.             rqdata.pid = qpid;
  4213.             if (!(arc = DosReadQueue(hq,                // in: queue handle
  4214.                                      &rqdata,           // out: pid and ulData
  4215.                                      &cbData,           // out: size of data returned
  4216.                                      (PVOID*)&pulData,  // out: data returned
  4217.                                      0,                 // in: remove first element in queue
  4218.                                      0,                 // in: wait for queue data (block thread)
  4219.                                      &elpri,            // out: element's priority
  4220.                                      0)))               // in: event semaphore to be posted
  4221.             {
  4222.                 if (!rqdata.ulData)
  4223.                 {
  4224.                     // child session ended:
  4225.                     // V0.9.14 (2001-08-03) [umoeller]
  4226.  
  4227.                     // *pulSID = (*pulData) & 0xffff;
  4228.                     if (pusReturn)
  4229.                         *pusReturn = ((*pulData) >> 16) & 0xffff;
  4230.  
  4231.                 }
  4232.                 // else: continue looping
  4233.  
  4234.                 if (pulData)
  4235.                     DosFreeMem(pulData);
  4236.             }
  4237.         }
  4238.     }
  4239.  
  4240.     if (hq)
  4241.         DosCloseQueue(hq);
  4242.  
  4243.     return arc;
  4244. }
  4245.  
  4246.  
  4247.