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