xref: /reactos/sdk/tools/cabman/cabinet.cxx (revision 177ae91b)
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS cabinet manager
4  * FILE:        tools/cabman/cabinet.cxx
5  * PURPOSE:     Cabinet routines
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  *              Colin Finck <mail@colinfinck.de>
8  * NOTES:       Define CAB_READ_ONLY for read only version
9  * REVISIONS:
10  *   CSH 21/03-2001 Created
11  *   CSH 15/08-2003 Made it portable
12  *   CF  04/05-2007 Made it compatible with 64-bit operating systems
13  * TODO:
14  *   - Checksum of datablocks should be calculated
15  *   - EXTRACT.EXE complains if a disk is created manually
16  *   - Folders that are created manually and span disks will result in a damaged cabinet
17  */
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #if !defined(_WIN32)
22 # include <dirent.h>
23 # include <sys/stat.h>
24 # include <sys/types.h>
25 #endif
26 #include "cabinet.h"
27 #include "raw.h"
28 #include "mszip.h"
29 
30 #ifndef CAB_READ_ONLY
31 
32 #if 0
33 #if DBG
34 
35 void DumpBuffer(void* Buffer, ULONG Size)
36 {
37     HANDLE FileHandle;
38     ULONG BytesWritten;
39 
40     /* Create file, overwrite if it already exists */
41     FileHandle = CreateFile("dump.bin", // Create this file
42         GENERIC_WRITE,                  // Open for writing
43         0,                              // No sharing
44         NULL,                           // No security
45         CREATE_ALWAYS,                  // Create or overwrite
46         FILE_ATTRIBUTE_NORMAL,          // Normal file
47         NULL);                          // No attribute template
48     if (FileHandle == INVALID_HANDLE_VALUE)
49     {
50         DPRINT(MID_TRACE, ("ERROR OPENING '%u'.\n", (UINT)GetLastError()));
51         return;
52     }
53 
54     if (!WriteFile(FileHandle, Buffer, Size, &BytesWritten, NULL))
55     {
56         DPRINT(MID_TRACE, ("ERROR WRITING '%u'.\n", (UINT)GetLastError()));
57     }
58 
59     CloseFile(FileHandle);
60 }
61 
62 #endif /* DBG */
63 #endif
64 
65 #endif /* CAB_READ_ONLY */
66 
67 
68 /* CCabinet */
69 
70 CCabinet::CCabinet()
71 /*
72  * FUNCTION: Default constructor
73  */
74 {
75     *CabinetName = '\0';
76     *CabinetPrev = '\0';
77     *DiskPrev = '\0';
78     *CabinetNext = '\0';
79     *DiskNext = '\0';
80     *DestPath = '\0';
81     *CabinetReservedFile = '\0';
82 
83     FileOpen = false;
84     CabinetReservedFileBuffer = NULL;
85     CabinetReservedFileSize = 0;
86 
87     FolderListHead   = NULL;
88     FolderListTail   = NULL;
89     FileListHead     = NULL;
90     FileListTail     = NULL;
91     CriteriaListHead = NULL;
92     CriteriaListTail = NULL;
93 
94     Codec          = NULL;
95     CodecId        = -1;
96     CodecSelected  = false;
97 
98     OutputBuffer = NULL;
99     InputBuffer  = NULL;
100     MaxDiskSize  = 0;
101     BlockIsSplit = false;
102     ScratchFile  = NULL;
103 
104     FolderUncompSize = 0;
105     BytesLeftInBlock = 0;
106     ReuseBlock       = false;
107     CurrentDataNode  = NULL;
108 }
109 
110 
111 CCabinet::~CCabinet()
112 /*
113  * FUNCTION: Default destructor
114  */
115 {
116     if (CabinetReservedFileBuffer != NULL)
117     {
118         free(CabinetReservedFileBuffer);
119         CabinetReservedFileBuffer = NULL;
120         CabinetReservedFileSize = 0;
121     }
122 
123     if (CodecSelected)
124         delete Codec;
125 }
126 
127 bool CCabinet::IsSeparator(char Char)
128 /*
129  * FUNCTION: Determines if a character is a separator
130  * ARGUMENTS:
131  *     Char = Character to check
132  * RETURNS:
133  *     Whether it is a separator
134  */
135 {
136     if ((Char == '\\') || (Char == '/'))
137         return true;
138     else
139         return false;
140 }
141 
142 char* CCabinet::ConvertPath(char* Path, bool Allocate)
143 /*
144  * FUNCTION: Replaces \ or / with the one used by the host environment
145  * ARGUMENTS:
146  *     Path     = Pointer to string with pathname
147  *     Allocate = Specifies whether to allocate memory for the new
148  *                string or to change the existing buffer
149  * RETURNS:
150  *     Pointer to new path
151  */
152 {
153     char *newpath;
154     int i;
155 
156     if (Allocate)
157         newpath = strdup(Path);
158     else
159         newpath = Path;
160 
161     i = 0;
162     while (Path[i] != 0)
163     {
164 #if defined(_WIN32)
165         if (Path[i] == '/')
166             newpath[i] = '\\';
167         else
168 #else
169         if (Path[i] == '\\')
170             newpath[i] = '/';
171         else
172 #endif
173             newpath[i] = Path[i];
174 
175         i++;
176     }
177     newpath[i] = 0;
178 
179     return(newpath);
180 }
181 
182 
183 char* CCabinet::GetFileName(char* Path)
184 /*
185  * FUNCTION: Returns a pointer to file name
186  * ARGUMENTS:
187  *     Path = Pointer to string with pathname
188  * RETURNS:
189  *     Pointer to filename
190  */
191 {
192     ULONG i, j;
193 
194     j = i = (Path[0] ? (Path[1] == ':' ? 2 : 0) : 0);
195 
196     while (Path [i++])
197         if (IsSeparator(Path [i - 1]))
198             j = i;
199 
200     return Path + j;
201 }
202 
203 
204 void CCabinet::RemoveFileName(char* Path)
205 /*
206  * FUNCTION: Removes a file name from a path
207  * ARGUMENTS:
208  *     Path = Pointer to string with path
209  */
210 {
211     char* FileName;
212     ULONG i;
213 
214     i = (Path [0] ? (Path[1] == ':' ? 2 : 0) : 0);
215     FileName = GetFileName(Path + i);
216 
217     if ((FileName != (Path + i)) && (IsSeparator(FileName [-1])))
218         FileName--;
219     if ((FileName == (Path + i)) && (IsSeparator(FileName [0])))
220         FileName++;
221     FileName[0] = 0;
222 }
223 
224 
225 bool CCabinet::NormalizePath(char* Path,
226                              ULONG Length)
227 /*
228  * FUNCTION: Normalizes a path
229  * ARGUMENTS:
230  *     Path   = Pointer to string with pathname
231  *     Length = Number of bytes in Path
232  * RETURNS:
233  *     true if there was enough room in Path, or false
234  */
235 {
236     ULONG n;
237     bool OK = true;
238 
239     if ((n = (ULONG)strlen(Path)) &&
240         (!IsSeparator(Path[n - 1])) &&
241         (OK = ((n + 1) < Length)))
242     {
243         Path[n]     = DIR_SEPARATOR_CHAR;
244         Path[n + 1] = 0;
245     }
246     return OK;
247 }
248 
249 
250 char* CCabinet::GetCabinetName()
251 /*
252  * FUNCTION: Returns pointer to cabinet file name
253  * RETURNS:
254  *     Pointer to string with name of cabinet
255  */
256 {
257     return CabinetName;
258 }
259 
260 
261 void CCabinet::SetCabinetName(char* FileName)
262 /*
263  * FUNCTION: Sets cabinet file name
264  * ARGUMENTS:
265  *     FileName = Pointer to string with name of cabinet
266  */
267 {
268     strcpy(CabinetName, FileName);
269 }
270 
271 
272 void CCabinet::SetDestinationPath(char* DestinationPath)
273 /*
274  * FUNCTION: Sets destination path
275  * ARGUMENTS:
276  *    DestinationPath = Pointer to string with name of destination path
277  */
278 {
279     strcpy(DestPath, DestinationPath);
280     ConvertPath(DestPath, false);
281     if (strlen(DestPath) > 0)
282         NormalizePath(DestPath, PATH_MAX);
283 }
284 
285 ULONG CCabinet::AddSearchCriteria(char* SearchCriteria)
286 /*
287  * FUNCTION: Adds a criteria to the search criteria list
288  * ARGUMENTS:
289  *     SearchCriteria = String with the search criteria to add
290  * RETURNS:
291  *     Status of operation
292  */
293 {
294     PSEARCH_CRITERIA Criteria;
295 
296     // Add the criteria to the list of search criteria
297     Criteria = (PSEARCH_CRITERIA)malloc(sizeof(SEARCH_CRITERIA));
298     if(!Criteria)
299     {
300         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
301         return CAB_STATUS_NOMEMORY;
302     }
303 
304     Criteria->Prev = CriteriaListTail;
305     Criteria->Next = NULL;
306 
307     if(CriteriaListTail)
308         CriteriaListTail->Next = Criteria;
309     else
310         CriteriaListHead = Criteria;
311 
312     CriteriaListTail = Criteria;
313 
314     // Set the actual criteria string
315     Criteria->Search = (char*)malloc(strlen(SearchCriteria) + 1);
316     if (!Criteria->Search)
317     {
318         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
319         return CAB_STATUS_NOMEMORY;
320     }
321 
322     strcpy(Criteria->Search, SearchCriteria);
323 
324     return CAB_STATUS_SUCCESS;
325 }
326 
327 void CCabinet::DestroySearchCriteria()
328 /*
329  * FUNCTION: Destroys the list with the search criteria
330  */
331 {
332     PSEARCH_CRITERIA Criteria;
333     PSEARCH_CRITERIA NextCriteria;
334 
335     Criteria = CriteriaListHead;
336 
337     while(Criteria)
338     {
339         NextCriteria = Criteria->Next;
340 
341         free(Criteria->Search);
342         free(Criteria);
343 
344         Criteria = NextCriteria;
345     }
346 
347     CriteriaListHead = NULL;
348     CriteriaListTail = NULL;
349 }
350 
351 bool CCabinet::HasSearchCriteria()
352 /*
353  * FUNCTION: Returns whether we have search criteria
354  * RETURNS:
355  *    Whether we have search criteria or not.
356  */
357 {
358     return (CriteriaListHead != NULL);
359 }
360 
361 bool CCabinet::SetCompressionCodec(char* CodecName)
362 /*
363  * FUNCTION: Selects the codec to use for compression
364  * ARGUMENTS:
365  *    CodecName = Pointer to a string with the name of the codec
366  */
367 {
368     if( !strcasecmp(CodecName, "raw") )
369         SelectCodec(CAB_CODEC_RAW);
370     else if( !strcasecmp(CodecName, "mszip") )
371         SelectCodec(CAB_CODEC_MSZIP);
372     else
373     {
374         printf("ERROR: Invalid codec specified!\n");
375         return false;
376     }
377 
378     return true;
379 }
380 
381 char* CCabinet::GetDestinationPath()
382 /*
383  * FUNCTION: Returns destination path
384  * RETURNS:
385  *    Pointer to string with name of destination path
386  */
387 {
388     return DestPath;
389 }
390 
391 
392 bool CCabinet::SetCabinetReservedFile(char* FileName)
393 /*
394  * FUNCTION: Sets cabinet reserved file
395  * ARGUMENTS:
396  *    FileName = Pointer to string with name of cabinet reserved file
397  */
398 {
399     FILE* FileHandle;
400     ULONG BytesRead;
401     char* ConvertedFileName;
402 
403     ConvertedFileName = ConvertPath(FileName, true);
404 
405     FileHandle = fopen(ConvertedFileName, "rb");
406     free(ConvertedFileName);
407     if (FileHandle == NULL)
408     {
409         DPRINT(MID_TRACE, ("Cannot open cabinet reserved file.\n"));
410         return false;
411     }
412 
413     CabinetReservedFileSize = GetSizeOfFile(FileHandle);
414     if (CabinetReservedFileSize == (ULONG)-1)
415     {
416         DPRINT(MIN_TRACE, ("Cannot read from cabinet reserved file.\n"));
417         fclose(FileHandle);
418         return false;
419     }
420 
421     if (CabinetReservedFileSize == 0)
422     {
423         fclose(FileHandle);
424         return false;
425     }
426 
427     CabinetReservedFileBuffer = malloc(CabinetReservedFileSize);
428     if (!CabinetReservedFileBuffer)
429     {
430         fclose(FileHandle);
431         return false;
432     }
433 
434     BytesRead = fread(CabinetReservedFileBuffer, 1, CabinetReservedFileSize, FileHandle);
435     if( BytesRead != CabinetReservedFileSize )
436     {
437         fclose(FileHandle);
438         return false;
439     }
440 
441     fclose(FileHandle);
442 
443     strcpy(CabinetReservedFile, FileName);
444 
445     return true;
446 }
447 
448 
449 char* CCabinet::GetCabinetReservedFile()
450 /*
451  * FUNCTION: Returns cabionet reserved file
452  * RETURNS:
453  *    Pointer to string with name of cabinet reserved file
454  */
455 {
456     return CabinetReservedFile;
457 }
458 
459 
460 ULONG CCabinet::GetCurrentDiskNumber()
461 /*
462  * FUNCTION: Returns current disk number
463  * RETURNS:
464  *     Current disk number
465  */
466 {
467     return CurrentDiskNumber;
468 }
469 
470 
471 ULONG CCabinet::Open()
472 /*
473  * FUNCTION: Opens a cabinet file
474  * RETURNS:
475  *     Status of operation
476  */
477 {
478     PCFFOLDER_NODE FolderNode;
479     ULONG Status;
480     ULONG Index;
481 
482     if (!FileOpen)
483     {
484         ULONG BytesRead;
485         ULONG Size;
486 
487         OutputBuffer = malloc(CAB_BLOCKSIZE + 12);    // This should be enough
488         if (!OutputBuffer)
489             return CAB_STATUS_NOMEMORY;
490 
491         FileHandle = fopen(CabinetName, "rb");
492         if (FileHandle == NULL)
493         {
494             DPRINT(MID_TRACE, ("Cannot open file.\n"));
495             return CAB_STATUS_CANNOT_OPEN;
496         }
497 
498         FileOpen = true;
499 
500         /* Load CAB header */
501         if ((Status = ReadBlock(&CABHeader, sizeof(CFHEADER), &BytesRead))
502             != CAB_STATUS_SUCCESS)
503         {
504             DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
505             return CAB_STATUS_INVALID_CAB;
506         }
507 
508         /* Check header */
509         if ((BytesRead                 != sizeof(CFHEADER)) ||
510             (CABHeader.Signature       != CAB_SIGNATURE   ) ||
511             (CABHeader.Version         != CAB_VERSION     ) ||
512             (CABHeader.FolderCount     == 0               ) ||
513             (CABHeader.FileCount       == 0               ) ||
514             (CABHeader.FileTableOffset < sizeof(CFHEADER)))
515         {
516             CloseCabinet();
517             DPRINT(MID_TRACE, ("File has invalid header.\n"));
518             return CAB_STATUS_INVALID_CAB;
519         }
520 
521         Size = 0;
522 
523         /* Read/skip any reserved bytes */
524         if (CABHeader.Flags & CAB_FLAG_RESERVE)
525         {
526             if ((Status = ReadBlock(&Size, sizeof(ULONG), &BytesRead))
527                 != CAB_STATUS_SUCCESS)
528             {
529                 DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
530                 return CAB_STATUS_INVALID_CAB;
531             }
532             CabinetReserved = Size & 0xFFFF;
533             FolderReserved  = (Size >> 16) & 0xFF;
534             DataReserved    = (Size >> 24) & 0xFF;
535 
536             if (fseek(FileHandle, CabinetReserved, SEEK_CUR) != 0)
537             {
538                 DPRINT(MIN_TRACE, ("fseek() failed.\n"));
539                 return CAB_STATUS_FAILURE;
540             }
541         }
542 
543         if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
544         {
545             /* Read name of previous cabinet */
546             Status = ReadString(CabinetPrev, 256);
547             if (Status != CAB_STATUS_SUCCESS)
548                 return Status;
549             /* Read label of previous disk */
550             Status = ReadString(DiskPrev, 256);
551             if (Status != CAB_STATUS_SUCCESS)
552                 return Status;
553         }
554         else
555         {
556             strcpy(CabinetPrev, "");
557             strcpy(DiskPrev,    "");
558         }
559 
560         if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
561         {
562             /* Read name of next cabinet */
563             Status = ReadString(CabinetNext, 256);
564             if (Status != CAB_STATUS_SUCCESS)
565                 return Status;
566             /* Read label of next disk */
567             Status = ReadString(DiskNext, 256);
568             if (Status != CAB_STATUS_SUCCESS)
569                 return Status;
570         }
571         else
572         {
573             strcpy(CabinetNext, "");
574             strcpy(DiskNext,    "");
575         }
576 
577         /* Read all folders */
578         for (Index = 0; Index < CABHeader.FolderCount; Index++)
579         {
580             FolderNode = NewFolderNode();
581             if (!FolderNode)
582             {
583                 DPRINT(MIN_TRACE, ("Insufficient resources.\n"));
584                 return CAB_STATUS_NOMEMORY;
585             }
586 
587             if (Index == 0)
588                 FolderNode->UncompOffset = FolderUncompSize;
589 
590             FolderNode->Index = Index;
591 
592             if ((Status = ReadBlock(&FolderNode->Folder,
593                 sizeof(CFFOLDER), &BytesRead)) != CAB_STATUS_SUCCESS)
594             {
595                 DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
596                 return CAB_STATUS_INVALID_CAB;
597             }
598         }
599 
600         /* Read file entries */
601         Status = ReadFileTable();
602         if (Status != CAB_STATUS_SUCCESS)
603         {
604             DPRINT(MIN_TRACE, ("ReadFileTable() failed (%u).\n", (UINT)Status));
605             return Status;
606         }
607 
608         /* Read data blocks for all folders */
609         FolderNode = FolderListHead;
610         while (FolderNode != NULL)
611         {
612             Status = ReadDataBlocks(FolderNode);
613             if (Status != CAB_STATUS_SUCCESS)
614             {
615                 DPRINT(MIN_TRACE, ("ReadDataBlocks() failed (%u).\n", (UINT)Status));
616                 return Status;
617             }
618             FolderNode = FolderNode->Next;
619         }
620     }
621     return CAB_STATUS_SUCCESS;
622 }
623 
624 
625 void CCabinet::Close()
626 /*
627  * FUNCTION: Closes the cabinet file
628  */
629 {
630     if (FileOpen)
631     {
632         fclose(FileHandle);
633         FileOpen = false;
634     }
635 }
636 
637 
638 ULONG CCabinet::FindFirst(PCAB_SEARCH Search)
639 /*
640  * FUNCTION: Finds the first file in the cabinet that matches a search criteria
641  * ARGUMENTS:
642  *     Search   = Pointer to search structure
643  * RETURNS:
644  *     Status of operation
645  */
646 {
647     RestartSearch = false;
648     Search->Next = FileListHead;
649     return FindNext(Search);
650 }
651 
652 
653 ULONG CCabinet::FindNext(PCAB_SEARCH Search)
654 /*
655  * FUNCTION: Finds next file in the cabinet that matches a search criteria
656  * ARGUMENTS:
657  *     Search = Pointer to search structure
658  * RETURNS:
659  *     Status of operation
660  */
661 {
662     bool bFound = false;
663     PSEARCH_CRITERIA Criteria;
664     ULONG Status;
665 
666     if (RestartSearch)
667     {
668         Search->Next  = FileListHead;
669 
670         /* Skip split files already extracted */
671         while ((Search->Next) &&
672             (Search->Next->File.FileControlID > CAB_FILE_MAX_FOLDER) &&
673             (Search->Next->File.FileOffset <= LastFileOffset))
674         {
675             DPRINT(MAX_TRACE, ("Skipping file (%s)  FileOffset (0x%X)  LastFileOffset (0x%X).\n",
676                 Search->Next->FileName, (UINT)Search->Next->File.FileOffset, (UINT)LastFileOffset));
677             Search->Next = Search->Next->Next;
678         }
679 
680         RestartSearch = false;
681     }
682 
683     /* Check each search criteria against each file */
684     while(Search->Next)
685     {
686         // Some features (like displaying cabinets) don't require search criteria, so we can just break here.
687         // If a feature requires it, handle this in the ParseCmdline() function in "main.cxx".
688         if(!CriteriaListHead)
689             break;
690 
691         Criteria = CriteriaListHead;
692 
693         while(Criteria)
694         {
695             if(MatchFileNamePattern(Search->Next->FileName, Criteria->Search))
696             {
697                 bFound = true;
698                 break;
699             }
700 
701             Criteria = Criteria->Next;
702         }
703 
704         if(bFound)
705             break;
706 
707         Search->Next = Search->Next->Next;
708     }
709 
710     if (!Search->Next)
711     {
712         if (strlen(DiskNext) > 0)
713         {
714             CloseCabinet();
715 
716             SetCabinetName(CabinetNext);
717 
718             OnDiskChange(CabinetNext, DiskNext);
719 
720             Status = Open();
721             if (Status != CAB_STATUS_SUCCESS)
722                 return Status;
723 
724             Search->Next = FileListHead;
725             if (!Search->Next)
726                 return CAB_STATUS_NOFILE;
727         }
728         else
729             return CAB_STATUS_NOFILE;
730     }
731 
732     Search->File     = &Search->Next->File;
733     Search->FileName = Search->Next->FileName;
734     Search->Next     = Search->Next->Next;
735     return CAB_STATUS_SUCCESS;
736 }
737 
738 
739 ULONG CCabinet::ExtractFile(char* FileName)
740 /*
741  * FUNCTION: Extracts a file from the cabinet
742  * ARGUMENTS:
743  *     FileName = Pointer to buffer with name of file
744  * RETURNS
745  *     Status of operation
746  */
747 {
748     ULONG Size;
749     ULONG Offset;
750     ULONG BytesRead;
751     ULONG BytesToRead;
752     ULONG BytesWritten;
753     ULONG BytesSkipped;
754     ULONG BytesToWrite;
755     ULONG TotalBytesRead;
756     ULONG CurrentOffset;
757     PUCHAR Buffer;
758     PUCHAR CurrentBuffer;
759     FILE* DestFile;
760     PCFFILE_NODE File;
761     CFDATA CFData;
762     ULONG Status;
763     bool Skip;
764 #if defined(_WIN32)
765     FILETIME FileTime;
766 #endif
767     CHAR DestName[PATH_MAX];
768     CHAR TempName[PATH_MAX];
769 
770     Status = LocateFile(FileName, &File);
771     if (Status != CAB_STATUS_SUCCESS)
772     {
773         DPRINT(MID_TRACE, ("Cannot locate file (%u).\n", (UINT)Status));
774         return Status;
775     }
776 
777     LastFileOffset = File->File.FileOffset;
778 
779     switch (CurrentFolderNode->Folder.CompressionType & CAB_COMP_MASK)
780     {
781         case CAB_COMP_NONE:
782             SelectCodec(CAB_CODEC_RAW);
783             break;
784 
785         case CAB_COMP_MSZIP:
786             SelectCodec(CAB_CODEC_MSZIP);
787             break;
788 
789         default:
790             return CAB_STATUS_UNSUPPCOMP;
791     }
792 
793     DPRINT(MAX_TRACE, ("Extracting file at uncompressed offset (0x%X)  Size (%u bytes)  AO (0x%X)  UO (0x%X).\n",
794         (UINT)File->File.FileOffset,
795         (UINT)File->File.FileSize,
796         (UINT)File->DataBlock->AbsoluteOffset,
797         (UINT)File->DataBlock->UncompOffset));
798 
799     strcpy(DestName, DestPath);
800     strcat(DestName, FileName);
801 
802     /* Create destination file, fail if it already exists */
803     DestFile = fopen(DestName, "rb");
804     if (DestFile != NULL)
805     {
806         fclose(DestFile);
807         /* If file exists, ask to overwrite file */
808         if (OnOverwrite(&File->File, FileName))
809         {
810             DestFile = fopen(DestName, "w+b");
811             if (DestFile == NULL)
812                 return CAB_STATUS_CANNOT_CREATE;
813         }
814         else
815             return CAB_STATUS_FILE_EXISTS;
816     }
817     else
818     {
819         DestFile = fopen(DestName, "w+b");
820         if (DestFile == NULL)
821             return CAB_STATUS_CANNOT_CREATE;
822     }
823 
824 #if defined(_WIN32)
825     if (!DosDateTimeToFileTime(File->File.FileDate, File->File.FileTime, &FileTime))
826     {
827         fclose(DestFile);
828         DPRINT(MIN_TRACE, ("DosDateTimeToFileTime() failed (%u).\n", (UINT)GetLastError()));
829         return CAB_STATUS_CANNOT_WRITE;
830     }
831 
832     SetFileTime(DestFile, NULL, &FileTime, NULL);
833 #else
834     //DPRINT(MIN_TRACE, ("FIXME: DosDateTimeToFileTime\n"));
835 #endif
836 
837     SetAttributesOnFile(DestName, File->File.Attributes);
838 
839     Buffer = (PUCHAR)malloc(CAB_BLOCKSIZE + 12); // This should be enough
840     if (!Buffer)
841     {
842         fclose(DestFile);
843         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
844         return CAB_STATUS_NOMEMORY;
845     }
846 
847     /* Call OnExtract event handler */
848     OnExtract(&File->File, FileName);
849 
850     /* Search to start of file */
851     if (fseek(FileHandle, (off_t)File->DataBlock->AbsoluteOffset, SEEK_SET) != 0)
852     {
853         DPRINT(MIN_TRACE, ("fseek() failed.\n"));
854         fclose(DestFile);
855         free(Buffer);
856         return CAB_STATUS_INVALID_CAB;
857     }
858 
859     Size   = File->File.FileSize;
860     Offset = File->File.FileOffset;
861     CurrentOffset = File->DataBlock->UncompOffset;
862 
863     Skip = true;
864 
865     ReuseBlock = (CurrentDataNode == File->DataBlock);
866     if (Size > 0)
867     {
868         do
869         {
870             DPRINT(MAX_TRACE, ("CO (0x%X)    ReuseBlock (%u)    Offset (0x%X)   Size (%d)  BytesLeftInBlock (%d)\n",
871                 (UINT)File->DataBlock->UncompOffset, (UINT)ReuseBlock, (UINT)Offset, (UINT)Size,
872                 (UINT)BytesLeftInBlock));
873 
874             if (/*(CurrentDataNode != File->DataBlock) &&*/ (!ReuseBlock) || (BytesLeftInBlock <= 0))
875             {
876                 DPRINT(MAX_TRACE, ("Filling buffer. ReuseBlock (%u)\n", (UINT)ReuseBlock));
877 
878                 CurrentBuffer  = Buffer;
879                 TotalBytesRead = 0;
880                 do
881                 {
882                     DPRINT(MAX_TRACE, ("Size (%u bytes).\n", (UINT)Size));
883 
884                     if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) !=
885                         CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
886                     {
887                         fclose(DestFile);
888                         free(Buffer);
889                         DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
890                         return CAB_STATUS_INVALID_CAB;
891                     }
892 
893                     DPRINT(MAX_TRACE, ("Data block: Checksum (0x%X)  CompSize (%u bytes)  UncompSize (%u bytes)\n",
894                         (UINT)CFData.Checksum,
895                         CFData.CompSize,
896                         CFData.UncompSize));
897 
898                     ASSERT(CFData.CompSize <= CAB_BLOCKSIZE + 12);
899 
900                     BytesToRead = CFData.CompSize;
901 
902                     DPRINT(MAX_TRACE, ("Read: (0x%lX,0x%lX).\n",
903                         (unsigned long)CurrentBuffer, (unsigned long)Buffer));
904 
905                     if (((Status = ReadBlock(CurrentBuffer, BytesToRead, &BytesRead)) !=
906                         CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead))
907                     {
908                         fclose(DestFile);
909                         free(Buffer);
910                         DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
911                         return CAB_STATUS_INVALID_CAB;
912                     }
913 
914                     /* FIXME: Does not work with files generated by makecab.exe */
915 /*
916                     if (CFData.Checksum != 0)
917                     {
918                         ULONG Checksum = ComputeChecksum(CurrentBuffer, BytesRead, 0);
919                         if (Checksum != CFData.Checksum)
920                         {
921                             CloseFile(DestFile);
922                             free(Buffer);
923                             DPRINT(MIN_TRACE, ("Bad checksum (is 0x%X, should be 0x%X).\n",
924                                 Checksum, CFData.Checksum));
925                             return CAB_STATUS_INVALID_CAB;
926                         }
927                     }
928 */
929                     TotalBytesRead += BytesRead;
930 
931                     CurrentBuffer += BytesRead;
932 
933                     if (CFData.UncompSize == 0)
934                     {
935                         if (strlen(DiskNext) == 0)
936                         {
937                             fclose(DestFile);
938                             free(Buffer);
939                             return CAB_STATUS_NOFILE;
940                         }
941 
942                         /* CloseCabinet() will destroy all file entries so in case
943                            FileName refers to the FileName field of a CFFOLDER_NODE
944                            structure, we have to save a copy of the filename */
945                         strcpy(TempName, FileName);
946 
947                         CloseCabinet();
948 
949                         SetCabinetName(CabinetNext);
950 
951                         OnDiskChange(CabinetNext, DiskNext);
952 
953                         Status = Open();
954                         if (Status != CAB_STATUS_SUCCESS)
955                         {
956                             fclose(DestFile);
957                             free(Buffer);
958                             return Status;
959                         }
960 
961                         /* The first data block of the file will not be
962                            found as it is located in the previous file */
963                         Status = LocateFile(TempName, &File);
964                         if (Status == CAB_STATUS_NOFILE)
965                         {
966                             DPRINT(MID_TRACE, ("Cannot locate file (%u).\n", (UINT)Status));
967                             fclose(DestFile);
968                             free(Buffer);
969                             return Status;
970                         }
971 
972                         /* The file is continued in the first data block in the folder */
973                         File->DataBlock = CurrentFolderNode->DataListHead;
974 
975                         /* Search to start of file */
976                         if (fseek(FileHandle, (off_t)File->DataBlock->AbsoluteOffset, SEEK_SET) != 0)
977                         {
978                             DPRINT(MIN_TRACE, ("fseek() failed.\n"));
979                             fclose(DestFile);
980                             free(Buffer);
981                             return CAB_STATUS_INVALID_CAB;
982                         }
983 
984                         DPRINT(MAX_TRACE, ("Continuing extraction of file at uncompressed offset (0x%X)  Size (%u bytes)  AO (0x%X)  UO (0x%X).\n",
985                             (UINT)File->File.FileOffset,
986                             (UINT)File->File.FileSize,
987                             (UINT)File->DataBlock->AbsoluteOffset,
988                             (UINT)File->DataBlock->UncompOffset));
989 
990                         CurrentDataNode = File->DataBlock;
991                         ReuseBlock = true;
992 
993                         RestartSearch = true;
994                     }
995                 } while (CFData.UncompSize == 0);
996 
997                 DPRINT(MAX_TRACE, ("TotalBytesRead (%u).\n", (UINT)TotalBytesRead));
998 
999                 Status = Codec->Uncompress(OutputBuffer, Buffer, TotalBytesRead, &BytesToWrite);
1000                 if (Status != CS_SUCCESS)
1001                 {
1002                     fclose(DestFile);
1003                     free(Buffer);
1004                     DPRINT(MID_TRACE, ("Cannot uncompress block.\n"));
1005                     if (Status == CS_NOMEMORY)
1006                         return CAB_STATUS_NOMEMORY;
1007                     return CAB_STATUS_INVALID_CAB;
1008                 }
1009 
1010                 if (BytesToWrite != CFData.UncompSize)
1011                 {
1012                     DPRINT(MID_TRACE, ("BytesToWrite (%u) != CFData.UncompSize (%d)\n",
1013                         (UINT)BytesToWrite, CFData.UncompSize));
1014                     fclose(DestFile);
1015                     free(Buffer);
1016                     return CAB_STATUS_INVALID_CAB;
1017                 }
1018 
1019                 BytesLeftInBlock = BytesToWrite;
1020             }
1021             else
1022             {
1023                 DPRINT(MAX_TRACE, ("Using same buffer. ReuseBlock (%u)\n", (UINT)ReuseBlock));
1024 
1025                 BytesToWrite = BytesLeftInBlock;
1026 
1027                 DPRINT(MAX_TRACE, ("Seeking to absolute offset 0x%X.\n",
1028                     (UINT)(CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) + CurrentDataNode->Data.CompSize)));
1029 
1030                 if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) !=
1031                     CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
1032                 {
1033                     fclose(DestFile);
1034                     free(Buffer);
1035                     DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
1036                     return CAB_STATUS_INVALID_CAB;
1037                 }
1038 
1039                 DPRINT(MAX_TRACE, ("CFData.CompSize 0x%X  CFData.UncompSize 0x%X.\n",
1040                     CFData.CompSize, CFData.UncompSize));
1041 
1042                 /* Go to next data block */
1043                 if (fseek(FileHandle, (off_t)CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
1044                     CurrentDataNode->Data.CompSize, SEEK_SET) != 0)
1045                 {
1046                     DPRINT(MIN_TRACE, ("fseek() failed.\n"));
1047                     fclose(DestFile);
1048                     free(Buffer);
1049                     return CAB_STATUS_INVALID_CAB;
1050                 }
1051 
1052                 ReuseBlock = false;
1053             }
1054 
1055             if (Skip)
1056                 BytesSkipped = (Offset - CurrentOffset);
1057             else
1058                 BytesSkipped = 0;
1059 
1060             BytesToWrite -= BytesSkipped;
1061 
1062             if (Size < BytesToWrite)
1063                 BytesToWrite = Size;
1064 
1065             DPRINT(MAX_TRACE, ("Offset (0x%X)  CurrentOffset (0x%X)  ToWrite (%u)  Skipped (%u)(%u)  Size (%u).\n",
1066                 (UINT)Offset,
1067                 (UINT)CurrentOffset,
1068                 (UINT)BytesToWrite,
1069                 (UINT)BytesSkipped, (UINT)Skip,
1070                 (UINT)Size));
1071 
1072             BytesWritten = BytesToWrite;
1073             if (fwrite((void*)((PUCHAR)OutputBuffer + BytesSkipped),
1074                  BytesToWrite, 1, DestFile) < 1)
1075             {
1076                 fclose(DestFile);
1077                 free(Buffer);
1078                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
1079 
1080                 return CAB_STATUS_CANNOT_WRITE;
1081             }
1082 
1083             Size -= BytesToWrite;
1084 
1085             CurrentOffset += BytesToWrite;
1086 
1087             /* Don't skip any more bytes */
1088             Skip = false;
1089         } while (Size > 0);
1090     }
1091 
1092     fclose(DestFile);
1093 
1094     free(Buffer);
1095 
1096     return CAB_STATUS_SUCCESS;
1097 }
1098 
1099 bool CCabinet::IsCodecSelected()
1100 /*
1101  * FUNCTION: Returns the value of CodecSelected
1102  * RETURNS:
1103  *     Whether a codec is selected
1104  */
1105 {
1106     return CodecSelected;
1107 }
1108 
1109 void CCabinet::SelectCodec(LONG Id)
1110 /*
1111  * FUNCTION: Selects codec engine to use
1112  * ARGUMENTS:
1113  *     Id = Codec identifier
1114  */
1115 {
1116     if (CodecSelected)
1117     {
1118         if (Id == CodecId)
1119             return;
1120 
1121         CodecSelected = false;
1122         delete Codec;
1123     }
1124 
1125     switch (Id)
1126     {
1127         case CAB_CODEC_RAW:
1128             Codec = new CRawCodec();
1129             break;
1130 
1131         case CAB_CODEC_MSZIP:
1132             Codec = new CMSZipCodec();
1133             break;
1134 
1135         default:
1136             return;
1137     }
1138 
1139     CodecId       = Id;
1140     CodecSelected = true;
1141 }
1142 
1143 
1144 #ifndef CAB_READ_ONLY
1145 
1146 /* CAB write methods */
1147 
1148 ULONG CCabinet::NewCabinet()
1149 /*
1150  * FUNCTION: Creates a new cabinet
1151  * RETURNS:
1152  *     Status of operation
1153  */
1154 {
1155     ULONG Status;
1156 
1157     CurrentDiskNumber = 0;
1158 
1159     OutputBuffer = malloc(CAB_BLOCKSIZE + 12); // This should be enough
1160     InputBuffer  = malloc(CAB_BLOCKSIZE + 12); // This should be enough
1161     if ((!OutputBuffer) || (!InputBuffer))
1162     {
1163         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1164         return CAB_STATUS_NOMEMORY;
1165     }
1166     CurrentIBuffer     = InputBuffer;
1167     CurrentIBufferSize = 0;
1168 
1169     CABHeader.Signature     = CAB_SIGNATURE;
1170     CABHeader.Reserved1     = 0;            // Not used
1171     CABHeader.CabinetSize   = 0;            // Not yet known
1172     CABHeader.Reserved2     = 0;            // Not used
1173     CABHeader.Reserved3     = 0;            // Not used
1174     CABHeader.Version       = CAB_VERSION;
1175     CABHeader.FolderCount   = 0;            // Not yet known
1176     CABHeader.FileCount     = 0;            // Not yet known
1177     CABHeader.Flags         = 0;            // Not yet known
1178     // FIXME: Should be random
1179     CABHeader.SetID         = 0x534F;
1180     CABHeader.CabinetNumber = 0;
1181 
1182 
1183     TotalFolderSize = 0;
1184     TotalFileSize   = 0;
1185 
1186     DiskSize = sizeof(CFHEADER);
1187 
1188     InitCabinetHeader();
1189 
1190     // NextFolderNumber is 0-based
1191     NextFolderNumber = 0;
1192 
1193     CurrentFolderNode = NULL;
1194     Status = NewFolder();
1195     if (Status != CAB_STATUS_SUCCESS)
1196         return Status;
1197 
1198     CurrentFolderNode->Folder.DataOffset = DiskSize - TotalHeaderSize;
1199 
1200     ScratchFile = new CCFDATAStorage;
1201     if (!ScratchFile)
1202     {
1203         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1204         return CAB_STATUS_NOMEMORY;
1205     }
1206 
1207     Status = ScratchFile->Create();
1208 
1209     CreateNewFolder = false;
1210 
1211     CreateNewDisk = false;
1212 
1213     PrevCabinetNumber = 0;
1214 
1215     return Status;
1216 }
1217 
1218 
1219 ULONG CCabinet::NewDisk()
1220 /*
1221  * FUNCTION: Forces a new disk to be created
1222  * RETURNS:
1223  *     Status of operation
1224  */
1225 {
1226     // NextFolderNumber is 0-based
1227     NextFolderNumber = 1;
1228 
1229     CreateNewDisk = false;
1230 
1231     DiskSize = sizeof(CFHEADER) + TotalFolderSize + TotalFileSize;
1232 
1233     InitCabinetHeader();
1234 
1235     CurrentFolderNode->TotalFolderSize = 0;
1236 
1237     CurrentFolderNode->Folder.DataBlockCount = 0;
1238 
1239     return CAB_STATUS_SUCCESS;
1240 }
1241 
1242 
1243 ULONG CCabinet::NewFolder()
1244 /*
1245  * FUNCTION: Forces a new folder to be created
1246  * RETURNS:
1247  *     Status of operation
1248  */
1249 {
1250     DPRINT(MAX_TRACE, ("Creating new folder.\n"));
1251 
1252     CurrentFolderNode = NewFolderNode();
1253     if (!CurrentFolderNode)
1254     {
1255         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1256         return CAB_STATUS_NOMEMORY;
1257     }
1258 
1259     switch (CodecId) {
1260         case CAB_CODEC_RAW:
1261             CurrentFolderNode->Folder.CompressionType = CAB_COMP_NONE;
1262             break;
1263 
1264         case CAB_CODEC_MSZIP:
1265             CurrentFolderNode->Folder.CompressionType = CAB_COMP_MSZIP;
1266             break;
1267 
1268         default:
1269             return CAB_STATUS_UNSUPPCOMP;
1270     }
1271 
1272     /* FIXME: This won't work if no files are added to the new folder */
1273 
1274     DiskSize += sizeof(CFFOLDER);
1275 
1276     TotalFolderSize += sizeof(CFFOLDER);
1277 
1278     NextFolderNumber++;
1279 
1280     CABHeader.FolderCount++;
1281 
1282     LastBlockStart = 0;
1283 
1284     return CAB_STATUS_SUCCESS;
1285 }
1286 
1287 
1288 ULONG CCabinet::WriteFileToScratchStorage(PCFFILE_NODE FileNode)
1289 /*
1290  * FUNCTION: Writes a file to the scratch file
1291  * ARGUMENTS:
1292  *     FileNode = Pointer to file node
1293  * RETURNS:
1294  *     Status of operation
1295  */
1296 {
1297     ULONG BytesToRead;
1298     ULONG BytesRead;
1299     ULONG Status;
1300     ULONG Size;
1301 
1302     if (!ContinueFile)
1303     {
1304         /* Try to open file */
1305         SourceFile = fopen(FileNode->FileName, "rb");
1306         if (SourceFile == NULL)
1307         {
1308             DPRINT(MID_TRACE, ("File not found (%s).\n", FileNode->FileName));
1309             return CAB_STATUS_NOFILE;
1310         }
1311 
1312         if (CreateNewFolder)
1313         {
1314             /* There is always a new folder after
1315                a split file is completely stored */
1316             Status = NewFolder();
1317             if (Status != CAB_STATUS_SUCCESS)
1318                 return Status;
1319             CreateNewFolder = false;
1320         }
1321 
1322         /* Call OnAdd event handler */
1323         OnAdd(&FileNode->File, FileNode->FileName);
1324 
1325         TotalBytesLeft = FileNode->File.FileSize;
1326 
1327         FileNode->File.FileOffset        = CurrentFolderNode->UncompOffset;
1328         CurrentFolderNode->UncompOffset += TotalBytesLeft;
1329         FileNode->File.FileControlID     = (USHORT)(NextFolderNumber - 1);
1330         CurrentFolderNode->Commit        = true;
1331         PrevCabinetNumber                = CurrentDiskNumber;
1332 
1333         Size = sizeof(CFFILE) + (ULONG)strlen(GetFileName(FileNode->FileName)) + 1;
1334         CABHeader.FileTableOffset += Size;
1335         TotalFileSize += Size;
1336         DiskSize += Size;
1337     }
1338 
1339     FileNode->Commit = true;
1340 
1341     if (TotalBytesLeft > 0)
1342     {
1343         do
1344         {
1345             if (TotalBytesLeft > (ULONG)CAB_BLOCKSIZE - CurrentIBufferSize)
1346                 BytesToRead = CAB_BLOCKSIZE - CurrentIBufferSize;
1347             else
1348                 BytesToRead = TotalBytesLeft;
1349 
1350             if ( (BytesRead = fread(CurrentIBuffer, 1, BytesToRead, SourceFile)) != BytesToRead )
1351             {
1352                 DPRINT(MIN_TRACE, ("Cannot read from file. BytesToRead (%u)  BytesRead (%u)  CurrentIBufferSize (%u).\n",
1353                     (UINT)BytesToRead, (UINT)BytesRead, (UINT)CurrentIBufferSize));
1354                 return CAB_STATUS_INVALID_CAB;
1355             }
1356 
1357             CurrentIBuffer = (unsigned char*)CurrentIBuffer + BytesRead;
1358             CurrentIBufferSize += (USHORT)BytesRead;
1359 
1360             if (CurrentIBufferSize == CAB_BLOCKSIZE)
1361             {
1362                 Status = WriteDataBlock();
1363                 if (Status != CAB_STATUS_SUCCESS)
1364                     return Status;
1365             }
1366             TotalBytesLeft -= BytesRead;
1367         } while ((TotalBytesLeft > 0) && (!CreateNewDisk));
1368     }
1369 
1370     if (TotalBytesLeft == 0)
1371     {
1372         fclose(SourceFile);
1373         FileNode->Delete = true;
1374 
1375         if (FileNode->File.FileControlID > CAB_FILE_MAX_FOLDER)
1376         {
1377             FileNode->File.FileControlID = CAB_FILE_CONTINUED;
1378             CurrentFolderNode->Delete = true;
1379 
1380             if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
1381             {
1382                 Status = WriteDataBlock();
1383                 if (Status != CAB_STATUS_SUCCESS)
1384                     return Status;
1385             }
1386 
1387             CreateNewFolder = true;
1388         }
1389     }
1390     else
1391     {
1392         if (FileNode->File.FileControlID <= CAB_FILE_MAX_FOLDER)
1393             FileNode->File.FileControlID = CAB_FILE_SPLIT;
1394         else
1395             FileNode->File.FileControlID = CAB_FILE_PREV_NEXT;
1396     }
1397 
1398     return CAB_STATUS_SUCCESS;
1399 }
1400 
1401 
1402 ULONG CCabinet::WriteDisk(ULONG MoreDisks)
1403 /*
1404  * FUNCTION: Forces the current disk to be written
1405  * ARGUMENTS:
1406  *     MoreDisks = true if there is one or more disks after this disk
1407  * RETURNS:
1408  *     Status of operation
1409  */
1410 {
1411     PCFFILE_NODE FileNode;
1412     ULONG Status;
1413 
1414     ContinueFile = false;
1415     FileNode = FileListHead;
1416     while (FileNode != NULL)
1417     {
1418         Status = WriteFileToScratchStorage(FileNode);
1419         if (Status != CAB_STATUS_SUCCESS)
1420             return Status;
1421 
1422         if (CreateNewDisk)
1423         {
1424             /* A data block could span more than two
1425                disks if MaxDiskSize is very small */
1426             while (CreateNewDisk)
1427             {
1428                 DPRINT(MAX_TRACE, ("Creating new disk.\n"));
1429                 CommitDisk(true);
1430                 CloseDisk();
1431                 NewDisk();
1432 
1433                 ContinueFile = true;
1434                 CreateNewDisk = false;
1435 
1436                 DPRINT(MAX_TRACE, ("First on new disk. CurrentIBufferSize (%u)  CurrentOBufferSize (%u).\n",
1437                     (UINT)CurrentIBufferSize, (UINT)CurrentOBufferSize));
1438 
1439                 if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
1440                 {
1441                     Status = WriteDataBlock();
1442                     if (Status != CAB_STATUS_SUCCESS)
1443                         return Status;
1444                 }
1445             }
1446         }
1447         else
1448         {
1449             ContinueFile = false;
1450             FileNode = FileNode->Next;
1451         }
1452     }
1453 
1454     if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
1455     {
1456         /* A data block could span more than two
1457            disks if MaxDiskSize is very small */
1458 
1459         ASSERT(CreateNewDisk == false);
1460 
1461         do
1462         {
1463             if (CreateNewDisk)
1464             {
1465                 DPRINT(MID_TRACE, ("Creating new disk 2.\n"));
1466                 CommitDisk(true);
1467                 CloseDisk();
1468                 NewDisk();
1469                 CreateNewDisk = false;
1470 
1471                 ASSERT(FileNode == FileListHead);
1472             }
1473 
1474             if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0))
1475             {
1476                 Status = WriteDataBlock();
1477                 if (Status != CAB_STATUS_SUCCESS)
1478                     return Status;
1479             }
1480         } while (CreateNewDisk);
1481     }
1482     CommitDisk(MoreDisks);
1483 
1484     return CAB_STATUS_SUCCESS;
1485 }
1486 
1487 
1488 ULONG CCabinet::CommitDisk(ULONG MoreDisks)
1489 /*
1490  * FUNCTION: Commits the current disk
1491  * ARGUMENTS:
1492  *     MoreDisks = true if there is one or more disks after this disk
1493  * RETURNS:
1494  *     Status of operation
1495  */
1496 {
1497     PCFFOLDER_NODE FolderNode;
1498     ULONG Status;
1499 
1500     OnCabinetName(CurrentDiskNumber, CabinetName);
1501 
1502     /* Create file, fail if it already exists */
1503     FileHandle = fopen(CabinetName, "rb");
1504     if (FileHandle != NULL)
1505     {
1506         fclose(FileHandle);
1507         /* If file exists, ask to overwrite file */
1508         if (OnOverwrite(NULL, CabinetName))
1509         {
1510             FileHandle = fopen(CabinetName, "w+b");
1511             if (FileHandle == NULL)
1512                 return CAB_STATUS_CANNOT_CREATE;
1513         }
1514         else
1515             return CAB_STATUS_FILE_EXISTS;
1516 
1517     }
1518     else
1519     {
1520         FileHandle = fopen(CabinetName, "w+b");
1521         if (FileHandle == NULL)
1522             return CAB_STATUS_CANNOT_CREATE;
1523     }
1524 
1525     WriteCabinetHeader(MoreDisks != 0);
1526 
1527     Status = WriteFolderEntries();
1528     if (Status != CAB_STATUS_SUCCESS)
1529         return Status;
1530 
1531     /* Write file entries */
1532     WriteFileEntries();
1533 
1534     /* Write data blocks */
1535     FolderNode = FolderListHead;
1536     while (FolderNode != NULL)
1537     {
1538         if (FolderNode->Commit)
1539         {
1540             Status = CommitDataBlocks(FolderNode);
1541             if (Status != CAB_STATUS_SUCCESS)
1542                 return Status;
1543             /* Remove data blocks for folder */
1544             DestroyDataNodes(FolderNode);
1545         }
1546         FolderNode = FolderNode->Next;
1547     }
1548 
1549     fclose(FileHandle);
1550 
1551     ScratchFile->Truncate();
1552 
1553     return CAB_STATUS_SUCCESS;
1554 }
1555 
1556 
1557 ULONG CCabinet::CloseDisk()
1558 /*
1559  * FUNCTION: Closes the current disk
1560  * RETURNS:
1561  *     Status of operation
1562  */
1563 {
1564     DestroyDeletedFileNodes();
1565 
1566     /* Destroy folder nodes that are completely stored */
1567     DestroyDeletedFolderNodes();
1568 
1569     CurrentDiskNumber++;
1570 
1571     return CAB_STATUS_SUCCESS;
1572 }
1573 
1574 
1575 ULONG CCabinet::CloseCabinet()
1576 /*
1577  * FUNCTION: Closes the current cabinet
1578  * RETURNS:
1579  *     Status of operation
1580  */
1581 {
1582     ULONG Status;
1583 
1584     DestroyFileNodes();
1585 
1586     DestroyFolderNodes();
1587 
1588     if (InputBuffer)
1589     {
1590         free(InputBuffer);
1591         InputBuffer = NULL;
1592     }
1593 
1594     if (OutputBuffer)
1595     {
1596         free(OutputBuffer);
1597         OutputBuffer = NULL;
1598     }
1599 
1600     Close();
1601 
1602     if (ScratchFile)
1603     {
1604         Status = ScratchFile->Destroy();
1605         delete ScratchFile;
1606         return Status;
1607     }
1608 
1609     return CAB_STATUS_SUCCESS;
1610 }
1611 
1612 
1613 ULONG CCabinet::AddFile(char* FileName)
1614 /*
1615  * FUNCTION: Adds a file to the current disk
1616  * ARGUMENTS:
1617  *     FileName = Pointer to string with file name (full path)
1618  * RETURNS:
1619  *     Status of operation
1620  */
1621 {
1622     FILE* SrcFile;
1623     PCFFILE_NODE FileNode;
1624     char* NewFileName;
1625 
1626     NewFileName = (char*)malloc(strlen(FileName) + 1);
1627     if (!NewFileName)
1628     {
1629         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1630         return CAB_STATUS_NOMEMORY;
1631     }
1632     strcpy(NewFileName, FileName);
1633     ConvertPath(NewFileName, false);
1634 
1635     /* Try to open file */
1636     SrcFile = fopen(NewFileName, "rb");
1637     if (SrcFile == NULL)
1638     {
1639         DPRINT(MID_TRACE, ("File not found (%s).\n", NewFileName));
1640         free(NewFileName);
1641         return CAB_STATUS_CANNOT_OPEN;
1642     }
1643 
1644     FileNode = NewFileNode();
1645     if (!FileNode)
1646     {
1647         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1648         free(NewFileName);
1649         fclose(SrcFile);
1650         return CAB_STATUS_NOMEMORY;
1651     }
1652 
1653     FileNode->FolderNode = CurrentFolderNode;
1654     FileNode->FileName = NewFileName;
1655 
1656     /* FIXME: Check for and handle large files (>= 2GB) */
1657     FileNode->File.FileSize = GetSizeOfFile(SrcFile);
1658     if (FileNode->File.FileSize == (ULONG)-1)
1659     {
1660         DPRINT(MIN_TRACE, ("Cannot read from file.\n"));
1661         fclose(SrcFile);
1662         return CAB_STATUS_CANNOT_READ;
1663     }
1664 
1665     if (GetFileTimes(SrcFile, FileNode) != CAB_STATUS_SUCCESS)
1666     {
1667         DPRINT(MIN_TRACE, ("Cannot read file times.\n"));
1668         fclose(SrcFile);
1669         return CAB_STATUS_CANNOT_READ;
1670     }
1671 
1672     if (GetAttributesOnFile(FileNode) != CAB_STATUS_SUCCESS)
1673     {
1674         DPRINT(MIN_TRACE, ("Cannot read file attributes.\n"));
1675         fclose(SrcFile);
1676         return CAB_STATUS_CANNOT_READ;
1677     }
1678 
1679     fclose(SrcFile);
1680 
1681     return CAB_STATUS_SUCCESS;
1682 }
1683 
1684 bool CCabinet::CreateSimpleCabinet()
1685 /*
1686  * FUNCTION: Create a simple cabinet based on the files in the criteria list
1687  */
1688 {
1689     bool bRet = false;
1690     char* pszFile;
1691     char szFilePath[PATH_MAX];
1692     char szFile[PATH_MAX];
1693     PSEARCH_CRITERIA Criteria;
1694     ULONG Status;
1695 
1696 #if defined(_WIN32)
1697     HANDLE hFind;
1698     WIN32_FIND_DATA FindFileData;
1699 #else
1700     DIR* dirp;
1701     struct dirent* dp;
1702     struct stat stbuf;
1703 #endif
1704 
1705     // Initialize a new cabinet
1706     Status = NewCabinet();
1707     if (Status != CAB_STATUS_SUCCESS)
1708     {
1709         DPRINT(MIN_TRACE, ("Cannot create cabinet (%u).\n", (UINT)Status));
1710         goto cleanup2;
1711     }
1712 
1713     // Add each file in the criteria list
1714     Criteria = CriteriaListHead;
1715 
1716     while(Criteria)
1717     {
1718         // Store the file path with a trailing slash in szFilePath
1719         ConvertPath(Criteria->Search, false);
1720         pszFile = strrchr(Criteria->Search, DIR_SEPARATOR_CHAR);
1721 
1722         if(pszFile)
1723         {
1724             // Set the pointer to the start of the file name, not the slash
1725             pszFile++;
1726 
1727             strncpy(szFilePath, Criteria->Search, pszFile - Criteria->Search);
1728             szFilePath[pszFile - Criteria->Search] = 0;
1729         }
1730         else
1731         {
1732             pszFile = Criteria->Search;
1733 
1734 #if defined(_WIN32)
1735             szFilePath[0] = 0;
1736 #else
1737             // needed for opendir()
1738             strcpy(szFilePath, "./");
1739 #endif
1740         }
1741 
1742 #if defined(_WIN32)
1743         // Windows: Use the easy FindFirstFile/FindNextFile API for getting all files and checking them against the pattern
1744         hFind = FindFirstFile(Criteria->Search, &FindFileData);
1745 
1746         // Don't stop if a search criteria is not found
1747         if(hFind == INVALID_HANDLE_VALUE && GetLastError() != ERROR_FILE_NOT_FOUND)
1748         {
1749             DPRINT(MIN_TRACE, ("FindFirstFile failed, Criteria: %s, error code is %u\n", Criteria->Search, (UINT)GetLastError()));
1750             goto cleanup;
1751         }
1752 
1753         do
1754         {
1755             if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1756             {
1757                 strcpy(szFile, szFilePath);
1758                 strcat(szFile, FindFileData.cFileName);
1759 
1760                 Status = AddFile(szFile);
1761 
1762                 if(Status != CAB_STATUS_SUCCESS)
1763                 {
1764                     DPRINT(MIN_TRACE, ("Cannot add file to cabinet (%u).\n", (UINT)Status));
1765                     FindClose(hFind);
1766                     goto cleanup;
1767                 }
1768             }
1769         }
1770         while(FindNextFile(hFind, &FindFileData));
1771 
1772         FindClose(hFind);
1773 #else
1774         // Unix: Use opendir/readdir to loop through all entries, stat to check if it's a file and MatchFileNamePattern to match the file against the pattern
1775         dirp = opendir(szFilePath);
1776 
1777         if(dirp)
1778         {
1779             while( (dp = readdir(dirp)) )
1780             {
1781                 strcpy(szFile, szFilePath);
1782                 strcat(szFile, dp->d_name);
1783 
1784                 if(stat(szFile, &stbuf) == 0)
1785                 {
1786                     if(stbuf.st_mode != S_IFDIR)
1787                     {
1788                         if(MatchFileNamePattern(dp->d_name, pszFile))
1789                         {
1790                             Status = AddFile(szFile);
1791 
1792                             if(Status != CAB_STATUS_SUCCESS)
1793                             {
1794                                 DPRINT(MIN_TRACE, ("Cannot add file to cabinet (%u).\n", (UINT)Status));
1795                                 goto cleanup;
1796                             }
1797                         }
1798                     }
1799                 }
1800                 else
1801                 {
1802                     DPRINT(MIN_TRACE, ("stat failed, error code is %i\n", errno));
1803                     goto cleanup;
1804                 }
1805             }
1806 
1807             closedir(dirp);
1808         }
1809 #endif
1810 
1811         Criteria = Criteria->Next;
1812     }
1813 
1814     Status = WriteDisk(false);
1815     if (Status == CAB_STATUS_SUCCESS)
1816         Status = CloseDisk();
1817     if (Status != CAB_STATUS_SUCCESS)
1818     {
1819         DPRINT(MIN_TRACE, ("Cannot write disk (%u).\n", (UINT)Status));
1820         goto cleanup;
1821     }
1822 
1823 cleanup:
1824     CloseCabinet();
1825     bRet = true;
1826 
1827 cleanup2:
1828     DestroySearchCriteria();
1829     return bRet;
1830 }
1831 
1832 void CCabinet::SetMaxDiskSize(ULONG Size)
1833 /*
1834  * FUNCTION: Sets the maximum size of the current disk
1835  * ARGUMENTS:
1836  *     Size = Maximum size of current disk (0 means no maximum size)
1837  */
1838 {
1839     MaxDiskSize = Size;
1840 }
1841 
1842 #endif /* CAB_READ_ONLY */
1843 
1844 
1845 /* Default event handlers */
1846 
1847 bool CCabinet::OnOverwrite(PCFFILE File,
1848                            char* FileName)
1849 /*
1850  * FUNCTION: Called when extracting a file and it already exists
1851  * ARGUMENTS:
1852  *     File     = Pointer to CFFILE for file being extracted
1853  *     FileName = Pointer to buffer with name of file (full path)
1854  * RETURNS
1855  *     true if the file should be overwritten, false if not
1856  */
1857 {
1858     return false;
1859 }
1860 
1861 
1862 void CCabinet::OnExtract(PCFFILE File,
1863                          char* FileName)
1864 /*
1865  * FUNCTION: Called just before extracting a file
1866  * ARGUMENTS:
1867  *     File     = Pointer to CFFILE for file being extracted
1868  *     FileName = Pointer to buffer with name of file (full path)
1869  */
1870 {
1871 }
1872 
1873 
1874 void CCabinet::OnDiskChange(char* CabinetName,
1875                             char* DiskLabel)
1876 /*
1877  * FUNCTION: Called when a new disk is to be processed
1878  * ARGUMENTS:
1879  *     CabinetName = Pointer to buffer with name of cabinet
1880  *     DiskLabel   = Pointer to buffer with label of disk
1881  */
1882 {
1883 }
1884 
1885 
1886 #ifndef CAB_READ_ONLY
1887 
1888 void CCabinet::OnAdd(PCFFILE File,
1889                      char* FileName)
1890 /*
1891  * FUNCTION: Called just before adding a file to a cabinet
1892  * ARGUMENTS:
1893  *     File     = Pointer to CFFILE for file being added
1894  *     FileName = Pointer to buffer with name of file (full path)
1895  */
1896 {
1897 }
1898 
1899 
1900 bool CCabinet::OnDiskLabel(ULONG Number, char* Label)
1901 /*
1902  * FUNCTION: Called when a disk needs a label
1903  * ARGUMENTS:
1904  *     Number = Cabinet number that needs a label
1905  *     Label  = Pointer to buffer to place label of disk
1906  * RETURNS:
1907  *     true if a disk label was returned, false if not
1908  */
1909 {
1910     return false;
1911 }
1912 
1913 
1914 bool CCabinet::OnCabinetName(ULONG Number, char* Name)
1915 /*
1916  * FUNCTION: Called when a cabinet needs a name
1917  * ARGUMENTS:
1918  *     Number = Disk number that needs a name
1919  *     Name   = Pointer to buffer to place name of cabinet
1920  * RETURNS:
1921  *     true if a cabinet name was returned, false if not
1922  */
1923 {
1924     return false;
1925 }
1926 
1927 #endif /* CAB_READ_ONLY */
1928 
1929 PCFFOLDER_NODE CCabinet::LocateFolderNode(ULONG Index)
1930 /*
1931  * FUNCTION: Locates a folder node
1932  * ARGUMENTS:
1933  *     Index = Folder index
1934  * RETURNS:
1935  *     Pointer to folder node or NULL if the folder node was not found
1936  */
1937 {
1938     PCFFOLDER_NODE Node;
1939 
1940     switch (Index)
1941     {
1942         case CAB_FILE_SPLIT:
1943             return FolderListTail;
1944 
1945         case CAB_FILE_CONTINUED:
1946         case CAB_FILE_PREV_NEXT:
1947             return FolderListHead;
1948     }
1949 
1950     Node = FolderListHead;
1951     while (Node != NULL)
1952     {
1953         if (Node->Index == Index)
1954             return Node;
1955         Node = Node->Next;
1956     }
1957     return NULL;
1958 }
1959 
1960 
1961 ULONG CCabinet::GetAbsoluteOffset(PCFFILE_NODE File)
1962 /*
1963  * FUNCTION: Returns the absolute offset of a file
1964  * ARGUMENTS:
1965  *     File = Pointer to CFFILE_NODE structure for file
1966  * RETURNS:
1967  *     Status of operation
1968  */
1969 {
1970     PCFDATA_NODE Node;
1971 
1972     DPRINT(MAX_TRACE, ("FileName '%s'  FileOffset (0x%X)  FileSize (%u).\n",
1973         File->FileName,
1974         (UINT)File->File.FileOffset,
1975         (UINT)File->File.FileSize));
1976 
1977     Node = CurrentFolderNode->DataListHead;
1978     while (Node != NULL)
1979     {
1980         DPRINT(MAX_TRACE, ("GetAbsoluteOffset(): Comparing (0x%X, 0x%X) (%u).\n",
1981             (UINT)Node->UncompOffset,
1982             (UINT)(Node->UncompOffset + Node->Data.UncompSize),
1983             (UINT)Node->Data.UncompSize));
1984 
1985         /* Node->Data.UncompSize will be 0 if the block is split
1986            (ie. it is the last block in this cabinet) */
1987         if ((Node->Data.UncompSize == 0) ||
1988             ((File->File.FileOffset >= Node->UncompOffset) &&
1989             (File->File.FileOffset < Node->UncompOffset +
1990             Node->Data.UncompSize)))
1991         {
1992                 File->DataBlock = Node;
1993                 return CAB_STATUS_SUCCESS;
1994         }
1995 
1996         Node = Node->Next;
1997     }
1998     return CAB_STATUS_INVALID_CAB;
1999 }
2000 
2001 
2002 ULONG CCabinet::LocateFile(char* FileName,
2003                            PCFFILE_NODE *File)
2004 /*
2005  * FUNCTION: Locates a file in the cabinet
2006  * ARGUMENTS:
2007  *     FileName = Pointer to string with name of file to locate
2008  *     File     = Address of pointer to CFFILE_NODE structure to fill
2009  * RETURNS:
2010  *     Status of operation
2011  * NOTES:
2012  *     Current folder is set to the folder of the file
2013  */
2014 {
2015     PCFFILE_NODE Node;
2016     ULONG Status;
2017 
2018     DPRINT(MAX_TRACE, ("FileName '%s'\n", FileName));
2019 
2020     Node = FileListHead;
2021     while (Node != NULL)
2022     {
2023         if (strcasecmp(FileName, Node->FileName) == 0)
2024         {
2025             CurrentFolderNode = LocateFolderNode(Node->File.FileControlID);
2026             if (!CurrentFolderNode)
2027             {
2028                 DPRINT(MID_TRACE, ("Folder with index number (%u) not found.\n",
2029                     Node->File.FileControlID));
2030                 return CAB_STATUS_INVALID_CAB;
2031             }
2032 
2033             if (Node->DataBlock == NULL)
2034                 Status = GetAbsoluteOffset(Node);
2035             else
2036                 Status = CAB_STATUS_SUCCESS;
2037 
2038             *File = Node;
2039             return Status;
2040         }
2041         Node = Node->Next;
2042     }
2043     return CAB_STATUS_NOFILE;
2044 }
2045 
2046 
2047 ULONG CCabinet::ReadString(char* String, LONG MaxLength)
2048 /*
2049  * FUNCTION: Reads a NULL-terminated string from the cabinet
2050  * ARGUMENTS:
2051  *     String    = Pointer to buffer to place string
2052  *     MaxLength = Maximum length of string
2053  * RETURNS:
2054  *     Status of operation
2055  */
2056 {
2057     ULONG BytesRead;
2058     ULONG Status;
2059     LONG Size;
2060     bool Found;
2061 
2062     Found  = false;
2063 
2064     Status = ReadBlock(String, MaxLength, &BytesRead);
2065     if (Status != CAB_STATUS_SUCCESS)
2066     {
2067         DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
2068         return CAB_STATUS_INVALID_CAB;
2069     }
2070 
2071     // Find the terminating NULL character
2072     for (Size = 0; Size < MaxLength; Size++)
2073     {
2074         if (String[Size] == '\0')
2075         {
2076             Found = true;
2077             break;
2078         }
2079     }
2080 
2081     if (!Found)
2082     {
2083         DPRINT(MIN_TRACE, ("Filename in the cabinet file is too long.\n"));
2084         return CAB_STATUS_INVALID_CAB;
2085     }
2086 
2087     // Compute the offset of the next CFFILE.
2088     // We have to subtract from the current offset here, because we read MaxLength characters above and most-probably the file name isn't MaxLength characters long.
2089     // + 1 to skip the terminating NULL character as well.
2090     Size = -(MaxLength - Size) + 1;
2091 
2092     if (fseek(FileHandle, (off_t)Size, SEEK_CUR) != 0)
2093     {
2094         DPRINT(MIN_TRACE, ("fseek() failed.\n"));
2095         return CAB_STATUS_INVALID_CAB;
2096     }
2097 
2098     return CAB_STATUS_SUCCESS;
2099 }
2100 
2101 
2102 ULONG CCabinet::ReadFileTable()
2103 /*
2104  * FUNCTION: Reads the file table from the cabinet file
2105  * RETURNS:
2106  *     Status of operation
2107  */
2108 {
2109     ULONG i;
2110     ULONG Status;
2111     ULONG BytesRead;
2112     PCFFILE_NODE File;
2113 
2114     DPRINT(MAX_TRACE, ("Reading file table at absolute offset (0x%X).\n",
2115         (UINT)CABHeader.FileTableOffset));
2116 
2117     /* Seek to file table */
2118     if (fseek(FileHandle, (off_t)CABHeader.FileTableOffset, SEEK_SET) != 0)
2119     {
2120         DPRINT(MIN_TRACE, ("fseek() failed.\n"));
2121         return CAB_STATUS_INVALID_CAB;
2122     }
2123 
2124     for (i = 0; i < CABHeader.FileCount; i++)
2125     {
2126         File = NewFileNode();
2127         if (!File)
2128         {
2129             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
2130             return CAB_STATUS_NOMEMORY;
2131         }
2132 
2133         if ((Status = ReadBlock(&File->File, sizeof(CFFILE),
2134             &BytesRead)) != CAB_STATUS_SUCCESS)
2135         {
2136             DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
2137             return CAB_STATUS_INVALID_CAB;
2138         }
2139 
2140         File->FileName = (char*)malloc(PATH_MAX);
2141         if (!File->FileName)
2142         {
2143             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
2144             return CAB_STATUS_NOMEMORY;
2145         }
2146 
2147         /* Read file name */
2148         Status = ReadString(File->FileName, PATH_MAX);
2149         if (Status != CAB_STATUS_SUCCESS)
2150             return Status;
2151 
2152         DPRINT(MAX_TRACE, ("Found file '%s' at uncompressed offset (0x%X).  Size (%u bytes)  ControlId (0x%X).\n",
2153             File->FileName,
2154             (UINT)File->File.FileOffset,
2155             (UINT)File->File.FileSize,
2156             File->File.FileControlID));
2157 
2158     }
2159     return CAB_STATUS_SUCCESS;
2160 }
2161 
2162 
2163 ULONG CCabinet::ReadDataBlocks(PCFFOLDER_NODE FolderNode)
2164 /*
2165  * FUNCTION: Reads all CFDATA blocks for a folder from the cabinet file
2166  * ARGUMENTS:
2167  *     FolderNode = Pointer to CFFOLDER_NODE structure for folder
2168  * RETURNS:
2169  *     Status of operation
2170  */
2171 {
2172     ULONG AbsoluteOffset;
2173     ULONG UncompOffset;
2174     PCFDATA_NODE Node;
2175     ULONG BytesRead;
2176     ULONG Status;
2177     ULONG i;
2178 
2179     DPRINT(MAX_TRACE, ("Reading data blocks for folder (%u)  at absolute offset (0x%X).\n",
2180         (UINT)FolderNode->Index, (UINT)FolderNode->Folder.DataOffset));
2181 
2182     AbsoluteOffset = FolderNode->Folder.DataOffset;
2183     UncompOffset   = FolderNode->UncompOffset;
2184 
2185     for (i = 0; i < FolderNode->Folder.DataBlockCount; i++)
2186     {
2187         Node = NewDataNode(FolderNode);
2188         if (!Node)
2189         {
2190             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
2191             return CAB_STATUS_NOMEMORY;
2192         }
2193 
2194         /* Seek to data block */
2195         if (fseek(FileHandle, (off_t)AbsoluteOffset, SEEK_SET) != 0)
2196         {
2197             DPRINT(MIN_TRACE, ("fseek() failed.\n"));
2198             return CAB_STATUS_INVALID_CAB;
2199         }
2200 
2201         if ((Status = ReadBlock(&Node->Data, sizeof(CFDATA),
2202             &BytesRead)) != CAB_STATUS_SUCCESS)
2203         {
2204             DPRINT(MIN_TRACE, ("Cannot read from file (%u).\n", (UINT)Status));
2205             return CAB_STATUS_INVALID_CAB;
2206         }
2207 
2208         DPRINT(MAX_TRACE, ("AbsOffset (0x%X)  UncompOffset (0x%X)  Checksum (0x%X)  CompSize (%u)  UncompSize (%u).\n",
2209             (UINT)AbsoluteOffset,
2210             (UINT)UncompOffset,
2211             (UINT)Node->Data.Checksum,
2212             Node->Data.CompSize,
2213             Node->Data.UncompSize));
2214 
2215         Node->AbsoluteOffset = AbsoluteOffset;
2216         Node->UncompOffset   = UncompOffset;
2217 
2218         AbsoluteOffset += sizeof(CFDATA) + Node->Data.CompSize;
2219         UncompOffset   += Node->Data.UncompSize;
2220     }
2221 
2222     FolderUncompSize = UncompOffset;
2223 
2224     return CAB_STATUS_SUCCESS;
2225 }
2226 
2227 
2228 PCFFOLDER_NODE CCabinet::NewFolderNode()
2229 /*
2230  * FUNCTION: Creates a new folder node
2231  * RETURNS:
2232  *     Pointer to node if there was enough free memory available, otherwise NULL
2233  */
2234 {
2235     PCFFOLDER_NODE Node;
2236 
2237     Node = (PCFFOLDER_NODE)malloc(sizeof(CFFOLDER_NODE));
2238     if (!Node)
2239         return NULL;
2240 
2241     memset(Node, 0, sizeof(CFFOLDER_NODE));
2242 
2243     Node->Folder.CompressionType = CAB_COMP_NONE;
2244 
2245     Node->Prev = FolderListTail;
2246 
2247     if (FolderListTail != NULL)
2248         FolderListTail->Next = Node;
2249     else
2250         FolderListHead = Node;
2251 
2252     FolderListTail = Node;
2253 
2254     return Node;
2255 }
2256 
2257 
2258 PCFFILE_NODE CCabinet::NewFileNode()
2259 /*
2260  * FUNCTION: Creates a new file node
2261  * ARGUMENTS:
2262  *     FolderNode = Pointer to folder node to bind file to
2263  * RETURNS:
2264  *     Pointer to node if there was enough free memory available, otherwise NULL
2265  */
2266 {
2267     PCFFILE_NODE Node;
2268 
2269     Node = (PCFFILE_NODE)malloc(sizeof(CFFILE_NODE));
2270     if (!Node)
2271         return NULL;
2272 
2273     memset(Node, 0, sizeof(CFFILE_NODE));
2274 
2275     Node->Prev = FileListTail;
2276 
2277     if (FileListTail != NULL)
2278         FileListTail->Next = Node;
2279     else
2280         FileListHead = Node;
2281 
2282     FileListTail = Node;
2283 
2284     return Node;
2285 }
2286 
2287 
2288 PCFDATA_NODE CCabinet::NewDataNode(PCFFOLDER_NODE FolderNode)
2289 /*
2290  * FUNCTION: Creates a new data block node
2291  * ARGUMENTS:
2292  *     FolderNode = Pointer to folder node to bind data block to
2293  * RETURNS:
2294  *     Pointer to node if there was enough free memory available, otherwise NULL
2295  */
2296 {
2297     PCFDATA_NODE Node;
2298 
2299     Node = (PCFDATA_NODE)malloc(sizeof(CFDATA_NODE));
2300     if (!Node)
2301         return NULL;
2302 
2303     memset(Node, 0, sizeof(CFDATA_NODE));
2304 
2305     Node->Prev = FolderNode->DataListTail;
2306 
2307     if (FolderNode->DataListTail != NULL)
2308         FolderNode->DataListTail->Next = Node;
2309     else
2310         FolderNode->DataListHead = Node;
2311 
2312     FolderNode->DataListTail = Node;
2313 
2314     return Node;
2315 }
2316 
2317 
2318 void CCabinet::DestroyDataNodes(PCFFOLDER_NODE FolderNode)
2319 /*
2320  * FUNCTION: Destroys data block nodes bound to a folder node
2321  * ARGUMENTS:
2322  *     FolderNode = Pointer to folder node
2323  */
2324 {
2325     PCFDATA_NODE PrevNode;
2326     PCFDATA_NODE NextNode;
2327 
2328     NextNode = FolderNode->DataListHead;
2329     while (NextNode != NULL)
2330     {
2331         PrevNode = NextNode->Next;
2332         free(NextNode);
2333         NextNode = PrevNode;
2334     }
2335     FolderNode->DataListHead = NULL;
2336     FolderNode->DataListTail = NULL;
2337 }
2338 
2339 
2340 void CCabinet::DestroyFileNodes()
2341 /*
2342  * FUNCTION: Destroys file nodes
2343  */
2344 {
2345     PCFFILE_NODE PrevNode;
2346     PCFFILE_NODE NextNode;
2347 
2348     NextNode = FileListHead;
2349     while (NextNode != NULL)
2350     {
2351         PrevNode = NextNode->Next;
2352         if (NextNode->FileName)
2353             free(NextNode->FileName);
2354         free(NextNode);
2355         NextNode = PrevNode;
2356     }
2357     FileListHead = NULL;
2358     FileListTail = NULL;
2359 }
2360 
2361 
2362 void CCabinet::DestroyDeletedFileNodes()
2363 /*
2364  * FUNCTION: Destroys file nodes that are marked for deletion
2365  */
2366 {
2367     PCFFILE_NODE CurNode;
2368     PCFFILE_NODE NextNode;
2369 
2370     CurNode = FileListHead;
2371     while (CurNode != NULL)
2372     {
2373         NextNode = CurNode->Next;
2374 
2375         if (CurNode->Delete)
2376         {
2377             if (CurNode->Prev != NULL)
2378                 CurNode->Prev->Next = CurNode->Next;
2379             else
2380             {
2381                 FileListHead = CurNode->Next;
2382                 if (FileListHead)
2383                     FileListHead->Prev = NULL;
2384             }
2385 
2386             if (CurNode->Next != NULL)
2387                 CurNode->Next->Prev = CurNode->Prev;
2388             else
2389             {
2390                 FileListTail = CurNode->Prev;
2391                 if (FileListTail)
2392                     FileListTail->Next = NULL;
2393             }
2394 
2395             DPRINT(MAX_TRACE, ("Deleting file: '%s'\n", CurNode->FileName));
2396 
2397             TotalFileSize -= (sizeof(CFFILE) + (ULONG)strlen(GetFileName(CurNode->FileName)) + 1);
2398 
2399             if (CurNode->FileName)
2400                 free(CurNode->FileName);
2401             free(CurNode);
2402         }
2403         CurNode = NextNode;
2404     }
2405 }
2406 
2407 
2408 void CCabinet::DestroyFolderNodes()
2409 /*
2410  * FUNCTION: Destroys folder nodes
2411  */
2412 {
2413     PCFFOLDER_NODE PrevNode;
2414     PCFFOLDER_NODE NextNode;
2415 
2416     NextNode = FolderListHead;
2417     while (NextNode != NULL)
2418     {
2419         PrevNode = NextNode->Next;
2420         DestroyDataNodes(NextNode);
2421         free(NextNode);
2422         NextNode = PrevNode;
2423     }
2424     FolderListHead = NULL;
2425     FolderListTail = NULL;
2426 }
2427 
2428 
2429 void CCabinet::DestroyDeletedFolderNodes()
2430 /*
2431  * FUNCTION: Destroys folder nodes that are marked for deletion
2432  */
2433 {
2434     PCFFOLDER_NODE CurNode;
2435     PCFFOLDER_NODE NextNode;
2436 
2437     CurNode = FolderListHead;
2438     while (CurNode != NULL)
2439     {
2440         NextNode = CurNode->Next;
2441 
2442         if (CurNode->Delete)
2443         {
2444             if (CurNode->Prev != NULL)
2445                 CurNode->Prev->Next = CurNode->Next;
2446             else
2447             {
2448                 FolderListHead = CurNode->Next;
2449                 if (FolderListHead)
2450                     FolderListHead->Prev = NULL;
2451             }
2452 
2453             if (CurNode->Next != NULL)
2454                 CurNode->Next->Prev = CurNode->Prev;
2455             else
2456             {
2457                 FolderListTail = CurNode->Prev;
2458                 if (FolderListTail)
2459                     FolderListTail->Next = NULL;
2460             }
2461 
2462             DestroyDataNodes(CurNode);
2463             free(CurNode);
2464 
2465             TotalFolderSize -= sizeof(CFFOLDER);
2466         }
2467         CurNode = NextNode;
2468     }
2469 }
2470 
2471 
2472 ULONG CCabinet::ComputeChecksum(void* Buffer,
2473                                 ULONG Size,
2474                                 ULONG Seed)
2475 /*
2476  * FUNCTION: Computes checksum for data block
2477  * ARGUMENTS:
2478  *     Buffer = Pointer to data buffer
2479  *     Size   = Length of data buffer
2480  *     Seed   = Previously computed checksum
2481  * RETURNS:
2482  *     Checksum of buffer
2483  */
2484 {
2485     int UlongCount; // Number of ULONGs in block
2486     ULONG Checksum; // Checksum accumulator
2487     unsigned char* pb;
2488     ULONG ul;
2489 
2490     /* FIXME: Doesn't seem to be correct. EXTRACT.EXE
2491        won't accept checksums computed by this routine */
2492 
2493     DPRINT(MIN_TRACE, ("Checksumming buffer (0x%p)  Size (%u)\n", Buffer, (UINT)Size));
2494 
2495     UlongCount = Size / 4;              // Number of ULONGs
2496     Checksum   = Seed;                  // Init checksum
2497     pb         = (unsigned char*)Buffer;         // Start at front of data block
2498 
2499     /* Checksum integral multiple of ULONGs */
2500     while (UlongCount-- > 0)
2501     {
2502         /* NOTE: Build ULONG in big/little-endian independent manner */
2503         ul = *pb++;                     // Get low-order byte
2504         ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
2505         ul |= (((ULONG)(*pb++)) << 16); // Add 3nd byte
2506         ul |= (((ULONG)(*pb++)) << 24); // Add 4th byte
2507 
2508         Checksum ^= ul;                 // Update checksum
2509     }
2510 
2511     /* Checksum remainder bytes */
2512     ul = 0;
2513     switch (Size % 4)
2514     {
2515         case 3:
2516             ul |= (((ULONG)(*pb++)) << 16); // Add 3rd byte
2517         case 2:
2518             ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
2519         case 1:
2520             ul |= *pb++;                    // Get low-order byte
2521         default:
2522             break;
2523     }
2524     Checksum ^= ul;                         // Update checksum
2525 
2526     /* Return computed checksum */
2527     return Checksum;
2528 }
2529 
2530 
2531 ULONG CCabinet::ReadBlock(void* Buffer,
2532                           ULONG Size,
2533                           PULONG BytesRead)
2534 /*
2535  * FUNCTION: Read a block of data from file
2536  * ARGUMENTS:
2537  *     Buffer    = Pointer to data buffer
2538  *     Size      = Length of data buffer
2539  *     BytesRead = Pointer to ULONG that on return will contain
2540  *                 number of bytes read
2541  * RETURNS:
2542  *     Status of operation
2543  */
2544 {
2545     *BytesRead = fread(Buffer, 1, Size, FileHandle);
2546     if ( *BytesRead != Size )
2547         return CAB_STATUS_INVALID_CAB;
2548     return CAB_STATUS_SUCCESS;
2549 }
2550 
2551 bool CCabinet::MatchFileNamePattern(char* FileName, char* Pattern)
2552 /*
2553  * FUNCTION: Matches a wildcard character pattern against a file
2554  * ARGUMENTS:
2555  *     FileName = The file name to check
2556  *     Pattern  = The pattern
2557  * RETURNS:
2558  *     Whether the pattern matches the file
2559  *
2560  * COPYRIGHT:
2561  *     This function is based on Busybox code, Copyright (C) 1998 by Erik Andersen, released under GPL2 or any later version.
2562  *     Adapted from code written by Ingo Wilken.
2563  *     Original location: http://www.busybox.net/cgi-bin/viewcvs.cgi/trunk/busybox/utility.c?rev=5&view=markup
2564  */
2565 {
2566     char* retryPattern = NULL;
2567     char* retryFileName = NULL;
2568     char  ch;
2569 
2570     while (*FileName || *Pattern)
2571     {
2572         ch = *Pattern++;
2573 
2574         switch (ch)
2575         {
2576             case '*':
2577                 retryPattern = Pattern;
2578                 retryFileName = FileName;
2579                 break;
2580 
2581             case '?':
2582                 if (*FileName++ == '\0')
2583                     return false;
2584 
2585                 break;
2586 
2587             default:
2588                 if (*FileName == ch)
2589                 {
2590                     if (*FileName)
2591                         FileName++;
2592                     break;
2593                 }
2594 
2595                 if (*FileName)
2596                 {
2597                     Pattern = retryPattern;
2598                     FileName = ++retryFileName;
2599                     break;
2600                 }
2601 
2602                 return false;
2603         }
2604 
2605         if (!Pattern)
2606             return false;
2607     }
2608 
2609     return true;
2610 }
2611 
2612 #ifndef CAB_READ_ONLY
2613 
2614 ULONG CCabinet::InitCabinetHeader()
2615 /*
2616  * FUNCTION: Initializes cabinet header and optional fields
2617  * RETURNS:
2618  *     Status of operation
2619  */
2620 {
2621     ULONG TotalSize;
2622     ULONG Size;
2623 
2624     CABHeader.FileTableOffset = 0;    // Not known yet
2625     CABHeader.FolderCount     = 0;    // Not known yet
2626     CABHeader.FileCount       = 0;    // Not known yet
2627     CABHeader.Flags           = 0;    // Not known yet
2628 
2629     CABHeader.CabinetNumber = (USHORT)CurrentDiskNumber;
2630 
2631     if ((CurrentDiskNumber > 0) && (OnCabinetName(PrevCabinetNumber, CabinetPrev)))
2632     {
2633         CABHeader.Flags |= CAB_FLAG_HASPREV;
2634         if (!OnDiskLabel(PrevCabinetNumber, DiskPrev))
2635             strcpy(CabinetPrev, "");
2636     }
2637 
2638     if (OnCabinetName(CurrentDiskNumber + 1, CabinetNext))
2639     {
2640         CABHeader.Flags |= CAB_FLAG_HASNEXT;
2641         if (!OnDiskLabel(CurrentDiskNumber + 1, DiskNext))
2642             strcpy(DiskNext, "");
2643     }
2644 
2645     TotalSize = 0;
2646 
2647     if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
2648     {
2649 
2650         DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
2651 
2652         /* Calculate size of name of previous cabinet */
2653         TotalSize += (ULONG)strlen(CabinetPrev) + 1;
2654 
2655         /* Calculate size of label of previous disk */
2656         TotalSize += (ULONG)strlen(DiskPrev) + 1;
2657     }
2658 
2659     if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
2660     {
2661 
2662         DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
2663 
2664         /* Calculate size of name of next cabinet */
2665         Size = (ULONG)strlen(CabinetNext) + 1;
2666         TotalSize += Size;
2667         NextFieldsSize = Size;
2668 
2669         /* Calculate size of label of next disk */
2670         Size = (ULONG)strlen(DiskNext) + 1;
2671         TotalSize += Size;
2672         NextFieldsSize += Size;
2673     }
2674     else
2675         NextFieldsSize = 0;
2676 
2677     /* Add cabinet reserved area size if present */
2678     if (CabinetReservedFileSize > 0)
2679     {
2680         CABHeader.Flags |= CAB_FLAG_RESERVE;
2681         TotalSize += CabinetReservedFileSize;
2682         TotalSize += sizeof(ULONG); /* For CabinetResSize, FolderResSize, and FileResSize fields */
2683     }
2684 
2685     DiskSize += TotalSize;
2686 
2687     TotalHeaderSize = sizeof(CFHEADER) + TotalSize;
2688 
2689     return CAB_STATUS_SUCCESS;
2690 }
2691 
2692 
2693 ULONG CCabinet::WriteCabinetHeader(bool MoreDisks)
2694 /*
2695  * FUNCTION: Writes the cabinet header and optional fields
2696  * ARGUMENTS:
2697  *     MoreDisks = true if next cabinet name should be included
2698  * RETURNS:
2699  *     Status of operation
2700  */
2701 {
2702     PCFFOLDER_NODE FolderNode;
2703     PCFFILE_NODE FileNode;
2704     ULONG BytesWritten;
2705     ULONG Size;
2706 
2707     if (MoreDisks)
2708     {
2709         CABHeader.Flags |= CAB_FLAG_HASNEXT;
2710         Size = TotalHeaderSize;
2711     }
2712     else
2713     {
2714         CABHeader.Flags &= ~CAB_FLAG_HASNEXT;
2715         DiskSize -= NextFieldsSize;
2716         Size = TotalHeaderSize - NextFieldsSize;
2717     }
2718 
2719     /* Set absolute folder offsets */
2720     BytesWritten = Size + TotalFolderSize + TotalFileSize;
2721     CABHeader.FolderCount = 0;
2722     FolderNode = FolderListHead;
2723     while (FolderNode != NULL)
2724     {
2725         FolderNode->Folder.DataOffset = BytesWritten;
2726 
2727         BytesWritten += FolderNode->TotalFolderSize;
2728 
2729         CABHeader.FolderCount++;
2730 
2731         FolderNode = FolderNode->Next;
2732     }
2733 
2734     /* Set absolute offset of file table */
2735     CABHeader.FileTableOffset = Size + TotalFolderSize;
2736 
2737     /* Count number of files to be committed */
2738     CABHeader.FileCount = 0;
2739     FileNode = FileListHead;
2740     while (FileNode != NULL)
2741     {
2742         if (FileNode->Commit)
2743             CABHeader.FileCount++;
2744         FileNode = FileNode->Next;
2745     }
2746 
2747     CABHeader.CabinetSize = DiskSize;
2748 
2749     /* Write header */
2750     if (fwrite(&CABHeader, sizeof(CFHEADER), 1, FileHandle) < 1)
2751     {
2752         DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2753         return CAB_STATUS_CANNOT_WRITE;
2754     }
2755 
2756     /* Write per-cabinet reserved area if present */
2757     if (CABHeader.Flags & CAB_FLAG_RESERVE)
2758     {
2759         ULONG ReservedSize;
2760 
2761         ReservedSize = CabinetReservedFileSize & 0xffff;
2762         ReservedSize |= (0 << 16); /* Folder reserved area size */
2763         ReservedSize |= (0 << 24); /* Folder reserved area size */
2764 
2765         BytesWritten = sizeof(ULONG);
2766         if (fwrite(&ReservedSize, sizeof(ULONG), 1, FileHandle) < 1)
2767         {
2768             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2769             return CAB_STATUS_CANNOT_WRITE;
2770         }
2771 
2772         BytesWritten = CabinetReservedFileSize;
2773         if (fwrite(CabinetReservedFileBuffer, CabinetReservedFileSize, 1, FileHandle) < 1)
2774         {
2775             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2776             return CAB_STATUS_CANNOT_WRITE;
2777         }
2778     }
2779 
2780     if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
2781     {
2782         DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
2783 
2784         /* Write name of previous cabinet */
2785         Size = (ULONG)strlen(CabinetPrev) + 1;
2786         BytesWritten = Size;
2787         if (fwrite(CabinetPrev, Size, 1, FileHandle) < 1)
2788         {
2789             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2790             return CAB_STATUS_CANNOT_WRITE;
2791         }
2792 
2793         DPRINT(MAX_TRACE, ("DiskPrev '%s'.\n", DiskPrev));
2794 
2795         /* Write label of previous disk */
2796         Size = (ULONG)strlen(DiskPrev) + 1;
2797         BytesWritten = Size;
2798         if (fwrite(DiskPrev, Size, 1, FileHandle) < 1)
2799         {
2800             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2801             return CAB_STATUS_CANNOT_WRITE;
2802         }
2803     }
2804 
2805     if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
2806     {
2807         DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
2808 
2809         /* Write name of next cabinet */
2810         Size = (ULONG)strlen(CabinetNext) + 1;
2811         BytesWritten = Size;
2812         if (fwrite(CabinetNext, Size, 1, FileHandle) < 1)
2813         {
2814             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2815             return CAB_STATUS_CANNOT_WRITE;
2816         }
2817 
2818         DPRINT(MAX_TRACE, ("DiskNext '%s'.\n", DiskNext));
2819 
2820         /* Write label of next disk */
2821         Size = (ULONG)strlen(DiskNext) + 1;
2822         BytesWritten = Size;
2823         if (fwrite(DiskNext, Size, 1, FileHandle) < 1)
2824         {
2825             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2826             return CAB_STATUS_CANNOT_WRITE;
2827         }
2828     }
2829 
2830     return CAB_STATUS_SUCCESS;
2831 }
2832 
2833 
2834 ULONG CCabinet::WriteFolderEntries()
2835 /*
2836  * FUNCTION: Writes folder entries
2837  * RETURNS:
2838  *     Status of operation
2839  */
2840 {
2841     PCFFOLDER_NODE FolderNode;
2842 
2843     DPRINT(MAX_TRACE, ("Writing folder table.\n"));
2844 
2845     FolderNode = FolderListHead;
2846     while (FolderNode != NULL)
2847     {
2848         if (FolderNode->Commit)
2849         {
2850             DPRINT(MAX_TRACE, ("Writing folder entry. CompressionType (0x%X)  DataBlockCount (%d)  DataOffset (0x%X).\n",
2851                 FolderNode->Folder.CompressionType, FolderNode->Folder.DataBlockCount, (UINT)FolderNode->Folder.DataOffset));
2852 
2853             if (fwrite(&FolderNode->Folder, sizeof(CFFOLDER), 1, FileHandle) < 1)
2854             {
2855                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2856                 return CAB_STATUS_CANNOT_WRITE;
2857             }
2858         }
2859         FolderNode = FolderNode->Next;
2860     }
2861 
2862     return CAB_STATUS_SUCCESS;
2863 }
2864 
2865 
2866 ULONG CCabinet::WriteFileEntries()
2867 /*
2868  * FUNCTION: Writes file entries for all files
2869  * RETURNS:
2870  *     Status of operation
2871  */
2872 {
2873     PCFFILE_NODE File;
2874     bool SetCont = false;
2875 
2876     DPRINT(MAX_TRACE, ("Writing file table.\n"));
2877 
2878     File = FileListHead;
2879     while (File != NULL)
2880     {
2881         if (File->Commit)
2882         {
2883             /* Remove any continued files that ends in this disk */
2884             if (File->File.FileControlID == CAB_FILE_CONTINUED)
2885                 File->Delete = true;
2886 
2887             /* The file could end in the last (split) block and should therefore
2888                appear in the next disk too */
2889 
2890             if ((File->File.FileOffset + File->File.FileSize >= LastBlockStart) &&
2891                 (File->File.FileControlID <= CAB_FILE_MAX_FOLDER) && (BlockIsSplit))
2892             {
2893                 File->File.FileControlID = CAB_FILE_SPLIT;
2894                 File->Delete = false;
2895                 SetCont = true;
2896             }
2897 
2898             DPRINT(MAX_TRACE, ("Writing file entry. FileControlID (0x%X)  FileOffset (0x%X)  FileSize (%u)  FileName (%s).\n",
2899                 File->File.FileControlID, (UINT)File->File.FileOffset, (UINT)File->File.FileSize, File->FileName));
2900 
2901             if (fwrite(&File->File, sizeof(CFFILE), 1, FileHandle) < 1)
2902             {
2903                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2904                 return CAB_STATUS_CANNOT_WRITE;
2905             }
2906 
2907             if (fwrite(GetFileName(File->FileName), strlen(GetFileName(File->FileName)) + 1, 1, FileHandle) < 1)
2908             {
2909                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2910                 return CAB_STATUS_CANNOT_WRITE;
2911             }
2912 
2913             if (SetCont)
2914             {
2915                 File->File.FileControlID = CAB_FILE_CONTINUED;
2916                 SetCont = false;
2917             }
2918         }
2919 
2920         File = File->Next;
2921     }
2922     return CAB_STATUS_SUCCESS;
2923 }
2924 
2925 
2926 ULONG CCabinet::CommitDataBlocks(PCFFOLDER_NODE FolderNode)
2927 /*
2928  * FUNCTION: Writes data blocks to the cabinet
2929  * ARGUMENTS:
2930  *     FolderNode = Pointer to folder node containing the data blocks
2931  * RETURNS:
2932  *     Status of operation
2933  */
2934 {
2935     PCFDATA_NODE DataNode;
2936     ULONG BytesRead;
2937     ULONG Status;
2938 
2939     DataNode = FolderNode->DataListHead;
2940     if (DataNode != NULL)
2941         Status = ScratchFile->Seek(DataNode->ScratchFilePosition);
2942 
2943     while (DataNode != NULL)
2944     {
2945         DPRINT(MAX_TRACE, ("Reading block at (0x%X)  CompSize (%u)  UncompSize (%u).\n",
2946             (UINT)DataNode->ScratchFilePosition,
2947             DataNode->Data.CompSize,
2948             DataNode->Data.UncompSize));
2949 
2950         /* InputBuffer is free for us to use here, so we use it and avoid a
2951            memory allocation. OutputBuffer can't be used here because it may
2952            still contain valid data (if a data block spans two or more disks) */
2953         Status = ScratchFile->ReadBlock(&DataNode->Data, InputBuffer, &BytesRead);
2954         if (Status != CAB_STATUS_SUCCESS)
2955         {
2956             DPRINT(MIN_TRACE, ("Cannot read from scratch file (%u).\n", (UINT)Status));
2957             return Status;
2958         }
2959 
2960         if (fwrite(&DataNode->Data, sizeof(CFDATA), 1, FileHandle) < 1)
2961         {
2962             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2963             return CAB_STATUS_CANNOT_WRITE;
2964         }
2965 
2966         if (fwrite(InputBuffer, DataNode->Data.CompSize, 1, FileHandle) < 1)
2967         {
2968             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2969             return CAB_STATUS_CANNOT_WRITE;
2970         }
2971 
2972         DataNode = DataNode->Next;
2973     }
2974     return CAB_STATUS_SUCCESS;
2975 }
2976 
2977 
2978 ULONG CCabinet::WriteDataBlock()
2979 /*
2980  * FUNCTION: Writes the current data block to the scratch file
2981  * RETURNS:
2982  *     Status of operation
2983  */
2984 {
2985     ULONG Status;
2986     ULONG BytesWritten;
2987     PCFDATA_NODE DataNode;
2988 
2989     if (!BlockIsSplit)
2990     {
2991         Status = Codec->Compress(OutputBuffer,
2992             InputBuffer,
2993             CurrentIBufferSize,
2994             &TotalCompSize);
2995 
2996         DPRINT(MAX_TRACE, ("Block compressed. CurrentIBufferSize (%u)  TotalCompSize(%u).\n",
2997             (UINT)CurrentIBufferSize, (UINT)TotalCompSize));
2998 
2999         CurrentOBuffer     = OutputBuffer;
3000         CurrentOBufferSize = TotalCompSize;
3001     }
3002 
3003     DataNode = NewDataNode(CurrentFolderNode);
3004     if (!DataNode)
3005     {
3006         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
3007         return CAB_STATUS_NOMEMORY;
3008     }
3009 
3010     DiskSize += sizeof(CFDATA);
3011 
3012     if (MaxDiskSize > 0)
3013         /* Disk size is limited */
3014         BlockIsSplit = (DiskSize + CurrentOBufferSize > MaxDiskSize);
3015     else
3016         BlockIsSplit = false;
3017 
3018     if (BlockIsSplit)
3019     {
3020         DataNode->Data.CompSize   = (USHORT)(MaxDiskSize - DiskSize);
3021         DataNode->Data.UncompSize = 0;
3022         CreateNewDisk = true;
3023     }
3024     else
3025     {
3026         DataNode->Data.CompSize   = (USHORT)CurrentOBufferSize;
3027         DataNode->Data.UncompSize = (USHORT)CurrentIBufferSize;
3028     }
3029 
3030     DataNode->Data.Checksum = 0;
3031     DataNode->ScratchFilePosition = ScratchFile->Position();
3032 
3033     // FIXME: MAKECAB.EXE does not like this checksum algorithm
3034     //DataNode->Data.Checksum = ComputeChecksum(CurrentOBuffer, DataNode->Data.CompSize, 0);
3035 
3036     DPRINT(MAX_TRACE, ("Writing block. Checksum (0x%X)  CompSize (%u)  UncompSize (%u).\n",
3037         (UINT)DataNode->Data.Checksum,
3038         DataNode->Data.CompSize,
3039         DataNode->Data.UncompSize));
3040 
3041     Status = ScratchFile->WriteBlock(&DataNode->Data,
3042         CurrentOBuffer, &BytesWritten);
3043     if (Status != CAB_STATUS_SUCCESS)
3044         return Status;
3045 
3046     DiskSize += BytesWritten;
3047 
3048     CurrentFolderNode->TotalFolderSize += (BytesWritten + sizeof(CFDATA));
3049     CurrentFolderNode->Folder.DataBlockCount++;
3050 
3051     CurrentOBuffer = (unsigned char*)CurrentOBuffer + DataNode->Data.CompSize;
3052     CurrentOBufferSize -= DataNode->Data.CompSize;
3053 
3054     LastBlockStart += DataNode->Data.UncompSize;
3055 
3056     if (!BlockIsSplit)
3057     {
3058         CurrentIBufferSize = 0;
3059         CurrentIBuffer     = InputBuffer;
3060     }
3061 
3062     return CAB_STATUS_SUCCESS;
3063 }
3064 
3065 #if !defined(_WIN32)
3066 
3067 void CCabinet::ConvertDateAndTime(time_t* Time,
3068                                   PUSHORT DosDate,
3069                                   PUSHORT DosTime)
3070 /*
3071  * FUNCTION: Returns file times of a file
3072  * ARGUMENTS:
3073  *      FileHandle = File handle of file to get file times from
3074  *      File       = Pointer to CFFILE node for file
3075  * RETURNS:
3076  *     Status of operation
3077  */
3078 {
3079     struct tm *timedef;
3080 
3081     timedef = localtime(Time);
3082 
3083     DPRINT(MAX_TRACE, ("day: %d, mon: %d, year:%d, hour: %d, min: %d, sec: %d\n",
3084         timedef->tm_mday, timedef->tm_mon, timedef->tm_year,
3085         timedef->tm_sec, timedef->tm_min, timedef->tm_hour));
3086 
3087     *DosDate = ((timedef->tm_mday + 1) << 0)
3088         | ((timedef->tm_mon + 1) << 5)
3089         | (((timedef->tm_year + 1900) - 1980) << 9);
3090 
3091     *DosTime = (timedef->tm_sec << 0)
3092         | (timedef->tm_min << 5)
3093         | (timedef->tm_hour << 11);
3094 }
3095 
3096 #endif // !_WIN32
3097 
3098 
3099 ULONG CCabinet::GetFileTimes(FILE* FileHandle, PCFFILE_NODE File)
3100 /*
3101  * FUNCTION: Returns file times of a file
3102  * ARGUMENTS:
3103  *      FileHandle = File handle of file to get file times from
3104  *      File       = Pointer to CFFILE node for file
3105  * RETURNS:
3106  *     Status of operation
3107  */
3108 {
3109 #if defined(_WIN32)
3110     FILETIME FileTime;
3111     HANDLE FileNo = UlongToHandle(_fileno(FileHandle));
3112 
3113     if (GetFileTime(FileNo, NULL, NULL, &FileTime))
3114         FileTimeToDosDateTime(&FileTime,
3115             &File->File.FileDate,
3116             &File->File.FileTime);
3117 #else
3118     struct stat stbuf;
3119     char buf[PATH_MAX];
3120 
3121     // Check for an absolute path
3122     if (IsSeparator(File->FileName[0]))
3123         strcpy(buf, File->FileName);
3124     else
3125     {
3126         if (!getcwd(buf, sizeof(buf)))
3127             return CAB_STATUS_CANNOT_READ;
3128         strcat(buf, DIR_SEPARATOR_STRING);
3129         strcat(buf, File->FileName);
3130     }
3131 
3132     if (stat(buf, &stbuf) == -1)
3133         return CAB_STATUS_CANNOT_READ;
3134 
3135     ConvertDateAndTime(&stbuf.st_mtime, &File->File.FileDate, &File->File.FileTime);
3136 #endif
3137     return CAB_STATUS_SUCCESS;
3138 }
3139 
3140 
3141 ULONG CCabinet::GetAttributesOnFile(PCFFILE_NODE File)
3142 /*
3143  * FUNCTION: Returns attributes on a file
3144  * ARGUMENTS:
3145  *      File = Pointer to CFFILE node for file
3146  * RETURNS:
3147  *     Status of operation
3148  */
3149 {
3150 #if defined(_WIN32)
3151     LONG Attributes;
3152 
3153     Attributes = GetFileAttributes(File->FileName);
3154     if (Attributes == -1)
3155         return CAB_STATUS_CANNOT_READ;
3156 
3157     // 0x37 = READONLY | HIDDEN | SYSTEM | DIRECTORY | ARCHIVE
3158     // The IDs for these attributes are the same in the CAB file and under Windows
3159     // If the file has any other attributes, strip them off by the logical AND.
3160     File->File.Attributes = (USHORT)(Attributes & 0x37);
3161 #else
3162     struct stat stbuf;
3163     char buf[PATH_MAX];
3164 
3165     // Check for an absolute path
3166     if (IsSeparator(File->FileName[0]))
3167         strcpy(buf, File->FileName);
3168     else
3169     {
3170         if (!getcwd(buf, sizeof(buf)))
3171             return CAB_STATUS_CANNOT_READ;
3172         strcat(buf, DIR_SEPARATOR_STRING);
3173         strcat(buf, File->FileName);
3174     }
3175 
3176     if (stat(buf, &stbuf) == -1)
3177         return CAB_STATUS_CANNOT_READ;
3178 
3179 #if 0
3180     File->File.Attributes |= CAB_ATTRIB_READONLY;
3181     File->File.Attributes |= CAB_ATTRIB_HIDDEN;
3182     File->File.Attributes |= CAB_ATTRIB_SYSTEM;
3183 #endif
3184 
3185     if (stbuf.st_mode & S_IFDIR)
3186         File->File.Attributes |= CAB_ATTRIB_DIRECTORY;
3187 
3188     File->File.Attributes |= CAB_ATTRIB_ARCHIVE;
3189 
3190 #endif
3191     return CAB_STATUS_SUCCESS;
3192 }
3193 
3194 
3195 ULONG CCabinet::SetAttributesOnFile(char* FileName, USHORT FileAttributes)
3196 /*
3197  * FUNCTION: Sets attributes on a file
3198  * ARGUMENTS:
3199  *      FileName       = File name with path
3200  *      FileAttributes = Attributes of that file
3201  * RETURNS:
3202  *     Status of operation
3203  */
3204 {
3205 #if defined(_WIN32)
3206     // 0x37 = READONLY | HIDDEN | SYSTEM | DIRECTORY | ARCHIVE
3207     // The IDs for these attributes are the same in the CAB file and under Windows
3208     // If the file has any other attributes, strip them off by the logical AND.
3209     SetFileAttributes(FileName, (DWORD)(FileAttributes & 0x37));
3210 
3211     return CAB_STATUS_SUCCESS;
3212 #else
3213     //DPRINT(MIN_TRACE, ("FIXME: SetAttributesOnFile() is unimplemented\n"));
3214     return CAB_STATUS_SUCCESS;
3215 #endif
3216 }
3217 
3218 #endif /* CAB_READ_ONLY */
3219 
3220 /* EOF */
3221