aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs653
1 files changed, 653 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
new file mode 100644
index 00000000..ec6e3bda
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Cab/CabPacker.cs
@@ -0,0 +1,653 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Cab
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
11 using System.Diagnostics.CodeAnalysis;
12
13 internal class CabPacker : CabWorker
14 {
15 private const string TempStreamName = "%%TEMP%%";
16
17 private NativeMethods.FCI.Handle fciHandle;
18
19 // These delegates need to be saved as member variables
20 // so that they don't get GC'd.
21 private NativeMethods.FCI.PFNALLOC fciAllocMemHandler;
22 private NativeMethods.FCI.PFNFREE fciFreeMemHandler;
23 private NativeMethods.FCI.PFNOPEN fciOpenStreamHandler;
24 private NativeMethods.FCI.PFNREAD fciReadStreamHandler;
25 private NativeMethods.FCI.PFNWRITE fciWriteStreamHandler;
26 private NativeMethods.FCI.PFNCLOSE fciCloseStreamHandler;
27 private NativeMethods.FCI.PFNSEEK fciSeekStreamHandler;
28 private NativeMethods.FCI.PFNFILEPLACED fciFilePlacedHandler;
29 private NativeMethods.FCI.PFNDELETE fciDeleteFileHandler;
30 private NativeMethods.FCI.PFNGETTEMPFILE fciGetTempFileHandler;
31
32 private NativeMethods.FCI.PFNGETNEXTCABINET fciGetNextCabinet;
33 private NativeMethods.FCI.PFNSTATUS fciCreateStatus;
34 private NativeMethods.FCI.PFNGETOPENINFO fciGetOpenInfo;
35
36 private IPackStreamContext context;
37
38 private FileAttributes fileAttributes;
39 private DateTime fileLastWriteTime;
40
41 private int maxCabBytes;
42
43 private long totalFolderBytesProcessedInCurrentCab;
44
45 private CompressionLevel compressionLevel;
46 private bool dontUseTempFiles;
47 private IList<Stream> tempStreams;
48
49 public CabPacker(CabEngine cabEngine)
50 : base(cabEngine)
51 {
52 this.fciAllocMemHandler = this.CabAllocMem;
53 this.fciFreeMemHandler = this.CabFreeMem;
54 this.fciOpenStreamHandler = this.CabOpenStreamEx;
55 this.fciReadStreamHandler = this.CabReadStreamEx;
56 this.fciWriteStreamHandler = this.CabWriteStreamEx;
57 this.fciCloseStreamHandler = this.CabCloseStreamEx;
58 this.fciSeekStreamHandler = this.CabSeekStreamEx;
59 this.fciFilePlacedHandler = this.CabFilePlaced;
60 this.fciDeleteFileHandler = this.CabDeleteFile;
61 this.fciGetTempFileHandler = this.CabGetTempFile;
62 this.fciGetNextCabinet = this.CabGetNextCabinet;
63 this.fciCreateStatus = this.CabCreateStatus;
64 this.fciGetOpenInfo = this.CabGetOpenInfo;
65 this.tempStreams = new List<Stream>();
66 this.compressionLevel = CompressionLevel.Normal;
67 }
68
69 public bool UseTempFiles
70 {
71 get
72 {
73 return !this.dontUseTempFiles;
74 }
75
76 set
77 {
78 this.dontUseTempFiles = !value;
79 }
80 }
81
82 public CompressionLevel CompressionLevel
83 {
84 get
85 {
86 return this.compressionLevel;
87 }
88
89 set
90 {
91 this.compressionLevel = value;
92 }
93 }
94
95 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
96 private void CreateFci(long maxArchiveSize)
97 {
98 NativeMethods.FCI.CCAB ccab = new NativeMethods.FCI.CCAB();
99 if (maxArchiveSize > 0 && maxArchiveSize < ccab.cb)
100 {
101 ccab.cb = Math.Max(
102 NativeMethods.FCI.MIN_DISK, (int) maxArchiveSize);
103 }
104
105 object maxFolderSizeOption = this.context.GetOption(
106 "maxFolderSize", null);
107 if (maxFolderSizeOption != null)
108 {
109 long maxFolderSize = Convert.ToInt64(
110 maxFolderSizeOption, CultureInfo.InvariantCulture);
111 if (maxFolderSize > 0 && maxFolderSize < ccab.cbFolderThresh)
112 {
113 ccab.cbFolderThresh = (int) maxFolderSize;
114 }
115 }
116
117 this.maxCabBytes = ccab.cb;
118 ccab.szCab = this.context.GetArchiveName(0);
119 if (ccab.szCab == null)
120 {
121 throw new FileNotFoundException(
122 "Cabinet name not provided by stream context.");
123 }
124 ccab.setID = (short) new Random().Next(
125 Int16.MinValue, Int16.MaxValue + 1);
126 this.CabNumbers[ccab.szCab] = 0;
127 this.currentArchiveName = ccab.szCab;
128 this.totalArchives = 1;
129 this.CabStream = null;
130
131 this.Erf.Clear();
132 this.fciHandle = NativeMethods.FCI.Create(
133 this.ErfHandle.AddrOfPinnedObject(),
134 this.fciFilePlacedHandler,
135 this.fciAllocMemHandler,
136 this.fciFreeMemHandler,
137 this.fciOpenStreamHandler,
138 this.fciReadStreamHandler,
139 this.fciWriteStreamHandler,
140 this.fciCloseStreamHandler,
141 this.fciSeekStreamHandler,
142 this.fciDeleteFileHandler,
143 this.fciGetTempFileHandler,
144 ccab,
145 IntPtr.Zero);
146 this.CheckError(false);
147 }
148
149 public void Pack(
150 IPackStreamContext streamContext,
151 IEnumerable<string> files,
152 long maxArchiveSize)
153 {
154 if (streamContext == null)
155 {
156 throw new ArgumentNullException("streamContext");
157 }
158
159 if (files == null)
160 {
161 throw new ArgumentNullException("files");
162 }
163
164 lock (this)
165 {
166 try
167 {
168 this.context = streamContext;
169
170 this.ResetProgressData();
171
172 this.CreateFci(maxArchiveSize);
173
174 foreach (string file in files)
175 {
176 FileAttributes attributes;
177 DateTime lastWriteTime;
178 Stream fileStream = this.context.OpenFileReadStream(
179 file,
180 out attributes,
181 out lastWriteTime);
182 if (fileStream != null)
183 {
184 this.totalFileBytes += fileStream.Length;
185 this.totalFiles++;
186 this.context.CloseFileReadStream(file, fileStream);
187 }
188 }
189
190 long uncompressedBytesInFolder = 0;
191 this.currentFileNumber = -1;
192
193 foreach (string file in files)
194 {
195 FileAttributes attributes;
196 DateTime lastWriteTime;
197 Stream fileStream = this.context.OpenFileReadStream(
198 file, out attributes, out lastWriteTime);
199 if (fileStream == null)
200 {
201 continue;
202 }
203
204 if (fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER)
205 {
206 throw new NotSupportedException(String.Format(
207 CultureInfo.InvariantCulture,
208 "File {0} exceeds maximum file size " +
209 "for cabinet format.",
210 file));
211 }
212
213 if (uncompressedBytesInFolder > 0)
214 {
215 // Automatically create a new folder if this file
216 // won't fit in the current folder.
217 bool nextFolder = uncompressedBytesInFolder
218 + fileStream.Length >= (long) NativeMethods.FCI.MAX_FOLDER;
219
220 // Otherwise ask the client if it wants to
221 // move to the next folder.
222 if (!nextFolder)
223 {
224 object nextFolderOption = streamContext.GetOption(
225 "nextFolder",
226 new object[] { file, this.currentFolderNumber });
227 nextFolder = Convert.ToBoolean(
228 nextFolderOption, CultureInfo.InvariantCulture);
229 }
230
231 if (nextFolder)
232 {
233 this.FlushFolder();
234 uncompressedBytesInFolder = 0;
235 }
236 }
237
238 if (this.currentFolderTotalBytes > 0)
239 {
240 this.currentFolderTotalBytes = 0;
241 this.currentFolderNumber++;
242 uncompressedBytesInFolder = 0;
243 }
244
245 this.currentFileName = file;
246 this.currentFileNumber++;
247
248 this.currentFileTotalBytes = fileStream.Length;
249 this.currentFileBytesProcessed = 0;
250 this.OnProgress(ArchiveProgressType.StartFile);
251
252 uncompressedBytesInFolder += fileStream.Length;
253
254 this.AddFile(
255 file,
256 fileStream,
257 attributes,
258 lastWriteTime,
259 false,
260 this.CompressionLevel);
261 }
262
263 this.FlushFolder();
264 this.FlushCabinet();
265 }
266 finally
267 {
268 if (this.CabStream != null)
269 {
270 this.context.CloseArchiveWriteStream(
271 this.currentArchiveNumber,
272 this.currentArchiveName,
273 this.CabStream);
274 this.CabStream = null;
275 }
276
277 if (this.FileStream != null)
278 {
279 this.context.CloseFileReadStream(
280 this.currentFileName, this.FileStream);
281 this.FileStream = null;
282 }
283 this.context = null;
284
285 if (this.fciHandle != null)
286 {
287 this.fciHandle.Dispose();
288 this.fciHandle = null;
289 }
290 }
291 }
292 }
293
294 internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
295 {
296 if (this.CabNumbers.ContainsKey(path))
297 {
298 Stream stream = this.CabStream;
299 if (stream == null)
300 {
301 short cabNumber = this.CabNumbers[path];
302
303 this.currentFolderTotalBytes = 0;
304
305 stream = this.context.OpenArchiveWriteStream(cabNumber, path, true, this.CabEngine);
306 if (stream == null)
307 {
308 throw new FileNotFoundException(
309 String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
310 }
311 this.currentArchiveName = path;
312
313 this.currentArchiveTotalBytes = Math.Min(
314 this.totalFolderBytesProcessedInCurrentCab, this.maxCabBytes);
315 this.currentArchiveBytesProcessed = 0;
316
317 this.OnProgress(ArchiveProgressType.StartArchive);
318 this.CabStream = stream;
319 }
320 path = CabWorker.CabStreamName;
321 }
322 else if (path == CabPacker.TempStreamName)
323 {
324 // Opening memory stream for a temp file.
325 Stream stream = new MemoryStream();
326 this.tempStreams.Add(stream);
327 int streamHandle = this.StreamHandles.AllocHandle(stream);
328 err = 0;
329 return streamHandle;
330 }
331 else if (path != CabWorker.CabStreamName)
332 {
333 // Opening a file on disk for a temp file.
334 path = Path.Combine(Path.GetTempPath(), path);
335 Stream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
336 this.tempStreams.Add(stream);
337 stream = new DuplicateStream(stream);
338 int streamHandle = this.StreamHandles.AllocHandle(stream);
339 err = 0;
340 return streamHandle;
341 }
342 return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
343 }
344
345 internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
346 {
347 int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
348 if (count > 0 && err == 0)
349 {
350 Stream stream = this.StreamHandles[streamHandle];
351 if (DuplicateStream.OriginalStream(stream) ==
352 DuplicateStream.OriginalStream(this.CabStream))
353 {
354 this.currentArchiveBytesProcessed += cb;
355 if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
356 {
357 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
358 }
359 }
360 }
361 return count;
362 }
363
364 internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
365 {
366 Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
367
368 if (stream == DuplicateStream.OriginalStream(this.FileStream))
369 {
370 this.context.CloseFileReadStream(this.currentFileName, stream);
371 this.FileStream = null;
372 long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
373 this.currentFileBytesProcessed += remainder;
374 this.fileBytesProcessed += remainder;
375 this.OnProgress(ArchiveProgressType.FinishFile);
376
377 this.currentFileTotalBytes = 0;
378 this.currentFileBytesProcessed = 0;
379 this.currentFileName = null;
380 }
381 else if (stream == DuplicateStream.OriginalStream(this.CabStream))
382 {
383 if (stream.CanWrite)
384 {
385 stream.Flush();
386 }
387
388 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
389 this.OnProgress(ArchiveProgressType.FinishArchive);
390 this.currentArchiveNumber++;
391 this.totalArchives++;
392
393 this.context.CloseArchiveWriteStream(
394 this.currentArchiveNumber,
395 this.currentArchiveName,
396 stream);
397
398 this.currentArchiveName = this.NextCabinetName;
399 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
400 this.totalFolderBytesProcessedInCurrentCab = 0;
401
402 this.CabStream = null;
403 }
404 else // Must be a temp stream
405 {
406 stream.Close();
407 this.tempStreams.Remove(stream);
408 }
409 return base.CabCloseStreamEx(streamHandle, out err, pv);
410 }
411
412 /// <summary>
413 /// Disposes of resources allocated by the cabinet engine.
414 /// </summary>
415 /// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
416 /// so managed and unmanaged resources will be disposed. If false, the method has been called by the
417 /// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
418 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
419 protected override void Dispose(bool disposing)
420 {
421 try
422 {
423 if (disposing)
424 {
425 if (this.fciHandle != null)
426 {
427 this.fciHandle.Dispose();
428 this.fciHandle = null;
429 }
430 }
431 }
432 finally
433 {
434 base.Dispose(disposing);
435 }
436 }
437
438 private static NativeMethods.FCI.TCOMP GetCompressionType(CompressionLevel compLevel)
439 {
440 if (compLevel < CompressionLevel.Min)
441 {
442 return NativeMethods.FCI.TCOMP.TYPE_NONE;
443 }
444 else
445 {
446 if (compLevel > CompressionLevel.Max)
447 {
448 compLevel = CompressionLevel.Max;
449 }
450
451 int lzxWindowMax =
452 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_HI >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW) -
453 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO >> (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW);
454 int lzxWindow = lzxWindowMax *
455 (compLevel - CompressionLevel.Min) / (CompressionLevel.Max - CompressionLevel.Min);
456
457 return (NativeMethods.FCI.TCOMP) ((int) NativeMethods.FCI.TCOMP.TYPE_LZX |
458 ((int) NativeMethods.FCI.TCOMP.LZX_WINDOW_LO +
459 (lzxWindow << (int) NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW)));
460 }
461 }
462
463 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
464 private void AddFile(
465 string name,
466 Stream stream,
467 FileAttributes attributes,
468 DateTime lastWriteTime,
469 bool execute,
470 CompressionLevel compLevel)
471 {
472 this.FileStream = stream;
473 this.fileAttributes = attributes &
474 (FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
475 this.fileLastWriteTime = lastWriteTime;
476 this.currentFileName = name;
477
478 NativeMethods.FCI.TCOMP tcomp = CabPacker.GetCompressionType(compLevel);
479
480 IntPtr namePtr = IntPtr.Zero;
481 try
482 {
483 Encoding nameEncoding = Encoding.ASCII;
484 if (Encoding.UTF8.GetByteCount(name) > name.Length)
485 {
486 nameEncoding = Encoding.UTF8;
487 this.fileAttributes |= FileAttributes.Normal; // _A_NAME_IS_UTF
488 }
489
490 byte[] nameBytes = nameEncoding.GetBytes(name);
491 namePtr = Marshal.AllocHGlobal(nameBytes.Length + 1);
492 Marshal.Copy(nameBytes, 0, namePtr, nameBytes.Length);
493 Marshal.WriteByte(namePtr, nameBytes.Length, 0);
494
495 this.Erf.Clear();
496 NativeMethods.FCI.AddFile(
497 this.fciHandle,
498 String.Empty,
499 namePtr,
500 execute,
501 this.fciGetNextCabinet,
502 this.fciCreateStatus,
503 this.fciGetOpenInfo,
504 tcomp);
505 }
506 finally
507 {
508 if (namePtr != IntPtr.Zero)
509 {
510 Marshal.FreeHGlobal(namePtr);
511 }
512 }
513
514 this.CheckError(false);
515 this.FileStream = null;
516 this.currentFileName = null;
517 }
518
519 private void FlushFolder()
520 {
521 this.Erf.Clear();
522 NativeMethods.FCI.FlushFolder(this.fciHandle, this.fciGetNextCabinet, this.fciCreateStatus);
523 this.CheckError(false);
524 }
525
526 private void FlushCabinet()
527 {
528 this.Erf.Clear();
529 NativeMethods.FCI.FlushCabinet(this.fciHandle, false, this.fciGetNextCabinet, this.fciCreateStatus);
530 this.CheckError(false);
531 }
532
533 private int CabGetOpenInfo(
534 string path,
535 out short date,
536 out short time,
537 out short attribs,
538 out int err,
539 IntPtr pv)
540 {
541 CompressionEngine.DateTimeToDosDateAndTime(this.fileLastWriteTime, out date, out time);
542 attribs = (short) this.fileAttributes;
543
544 Stream stream = this.FileStream;
545 this.FileStream = new DuplicateStream(stream);
546 int streamHandle = this.StreamHandles.AllocHandle(stream);
547 err = 0;
548 return streamHandle;
549 }
550
551 private int CabFilePlaced(
552 IntPtr pccab,
553 string filePath,
554 long fileSize,
555 int continuation,
556 IntPtr pv)
557 {
558 return 0;
559 }
560
561 private int CabGetNextCabinet(IntPtr pccab, uint prevCabSize, IntPtr pv)
562 {
563 NativeMethods.FCI.CCAB nextCcab = new NativeMethods.FCI.CCAB();
564 Marshal.PtrToStructure(pccab, nextCcab);
565
566 nextCcab.szDisk = String.Empty;
567 nextCcab.szCab = this.context.GetArchiveName(nextCcab.iCab);
568 this.CabNumbers[nextCcab.szCab] = (short) nextCcab.iCab;
569 this.NextCabinetName = nextCcab.szCab;
570
571 Marshal.StructureToPtr(nextCcab, pccab, false);
572 return 1;
573 }
574
575 private int CabCreateStatus(NativeMethods.FCI.STATUS typeStatus, uint cb1, uint cb2, IntPtr pv)
576 {
577 switch (typeStatus)
578 {
579 case NativeMethods.FCI.STATUS.FILE:
580 if (cb2 > 0 && this.currentFileBytesProcessed < this.currentFileTotalBytes)
581 {
582 if (this.currentFileBytesProcessed + cb2 > this.currentFileTotalBytes)
583 {
584 cb2 = (uint) this.currentFileTotalBytes - (uint) this.currentFileBytesProcessed;
585 }
586 this.currentFileBytesProcessed += cb2;
587 this.fileBytesProcessed += cb2;
588
589 this.OnProgress(ArchiveProgressType.PartialFile);
590 }
591 break;
592
593 case NativeMethods.FCI.STATUS.FOLDER:
594 if (cb1 == 0)
595 {
596 this.currentFolderTotalBytes = cb2 - this.totalFolderBytesProcessedInCurrentCab;
597 this.totalFolderBytesProcessedInCurrentCab = cb2;
598 }
599 else if (this.currentFolderTotalBytes == 0)
600 {
601 this.OnProgress(ArchiveProgressType.PartialArchive);
602 }
603 break;
604
605 case NativeMethods.FCI.STATUS.CABINET:
606 break;
607 }
608 return 0;
609 }
610
611 private int CabGetTempFile(IntPtr tempNamePtr, int tempNameSize, IntPtr pv)
612 {
613 string tempFileName;
614 if (this.UseTempFiles)
615 {
616 tempFileName = Path.GetFileName(Path.GetTempFileName());
617 }
618 else
619 {
620 tempFileName = CabPacker.TempStreamName;
621 }
622
623 byte[] tempNameBytes = Encoding.ASCII.GetBytes(tempFileName);
624 if (tempNameBytes.Length >= tempNameSize)
625 {
626 return -1;
627 }
628
629 Marshal.Copy(tempNameBytes, 0, tempNamePtr, tempNameBytes.Length);
630 Marshal.WriteByte(tempNamePtr, tempNameBytes.Length, 0); // null-terminator
631 return 1;
632 }
633
634 private int CabDeleteFile(string path, out int err, IntPtr pv)
635 {
636 try
637 {
638 // Deleting a temp file - don't bother if it is only a memory stream.
639 if (path != CabPacker.TempStreamName)
640 {
641 path = Path.Combine(Path.GetTempPath(), path);
642 File.Delete(path);
643 }
644 }
645 catch (IOException)
646 {
647 // Failure to delete a temp file is not fatal.
648 }
649 err = 0;
650 return 1;
651 }
652 }
653}