source: trunk/src/helpers/dosh.c@ 263

Last change on this file since 263 was 257, checked in by umoeller, 22 years ago

Fixes that have piled up in the last three months.

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