1:   // TarBuffer.cs
2:   // Copyright (C) 2001 Mike Krueger
3:   //
4:   // This program is free software; you can redistribute it and/or
5:   // modify it under the terms of the GNU General Public License
6:   // as published by the Free Software Foundation; either version 2
7:   // of the License, or (at your option) any later version.
8:   //
9:   // This program is distributed in the hope that it will be useful,
10:   // but WITHOUT ANY WARRANTY; without even the implied warranty of
11:   // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12:   // GNU General Public License for more details.
13:   //
14:   // You should have received a copy of the GNU General Public License
15:   // along with this program; if not, write to the Free Software
16:   // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17:   //
18:   // Linking this library statically or dynamically with other modules is
19:   // making a combined work based on this library.  Thus, the terms and
20:   // conditions of the GNU General Public License cover the whole
21:   // combination.
22:   //
23:   // As a special exception, the copyright holders of this library give you
24:   // permission to link this library with independent modules to produce an
25:   // executable, regardless of the license terms of these independent
26:   // modules, and to copy and distribute the resulting executable under
27:   // terms of your choice, provided that you also meet, for each linked
28:   // independent module, the terms and conditions of the license of that
29:   // module.  An independent module is a module which is not derived from
30:   // or based on this library.  If you modify this library, you may extend
31:   // this exception to your version of the library, but you are not
32:   // obligated to do so.  If you do not wish to do so, delete this
33:   // exception statement from your version.
34:  
35:   using System;
36:   using System.IO;
37:   using System.Text;
38:  
39:   namespace ICSharpCode.SharpZipLib.Tar {
40:       
41:       /// <summary>
42:       /// The TarBuffer class implements the tar archive concept
43:       /// of a buffered input stream. This concept goes back to the
44:       /// days of blocked tape drives and special io devices. In the
45:       /// C# universe, the only real function that this class
46:       /// performs is to ensure that files have the correct "block"
47:       /// size, or other tars will complain.
48:       /// <p>
49:       /// You should never have a need to access this class directly.
50:       /// TarBuffers are created by Tar IO Streams.
51:       /// </p>
52:       /// </summary>
53:       public class TarBuffer
54:       {
55:           public static readonly int DEFAULT_RCDSIZE 512;
56:           public static readonly int DEFAULT_BLKSIZE DEFAULT_RCDSIZE 20;
57:           
58:           Stream inputStream;
59:           Stream outputStream;
60:           
61:           byte[] blockBuffer;
62:           int    currBlkIdx;
63:           int    currRecIdx;
64:           int    blockSize;
65:           int    recordSize;
66:           int    recsPerBlock;
67:           
68:           bool   debug;
69:           
70:           protected TarBuffer()
71:           {
72:           }
73:           
74:           public static TarBuffer CreateInputTarBuffer(Stream inputStream)
75:           {
76:               return CreateInputTarBuffer(inputStreamTarBuffer.DEFAULT_BLKSIZE);
77:           }
78:           public static TarBuffer CreateInputTarBuffer(Stream inputStreamint blockSize )
79:           {
80:               return CreateInputTarBuffer(inputStreamblockSizeTarBuffer.DEFAULT_RCDSIZE);
81:           }
82:           public static TarBuffer CreateInputTarBuffer(Stream inputStreamint blockSizeint recordSize)
83:           {
84:               TarBuffer tarBuffer new TarBuffer();
85:               tarBuffer.inputStream  inputStream;
86:               tarBuffer.outputStream null;
87:               tarBuffer.Initialize(blockSizerecordSize);
88:               
89:               return tarBuffer;
90:           }
91:  
92:           public static TarBuffer CreateOutputTarBuffer(Stream outputStream)
93:           {
94:               return CreateOutputTarBuffer(outputStreamTarBuffer.DEFAULT_BLKSIZE);
95:           }
96:           public static TarBuffer CreateOutputTarBuffer(Stream outputStreamint blockSize )
97:           {
98:               return CreateOutputTarBuffer(outputStreamblockSizeTarBuffer.DEFAULT_RCDSIZE);
99:           }
100:           public static TarBuffer CreateOutputTarBuffer(Stream outputStreamint blockSizeint recordSize)
101:           {
102:               TarBuffer tarBuffer new TarBuffer();
103:               tarBuffer.inputStream  null;
104:               tarBuffer.outputStream outputStream;
105:               tarBuffer.Initialize(blockSizerecordSize);
106:               
107:               return tarBuffer;
108:           }
109:           
110:           /// <summary>
111:           /// Initialization common to all constructors.
112:           /// </summary>
113:           void Initialize(int blockSizeint recordSize)
114:           {
115:               this.debug        false;
116:               this.blockSize    blockSize;
117:               this.recordSize   recordSize;
118:               this.recsPerBlock this.blockSize this.recordSize;
119:               this.blockBuffer  new byte[this.blockSize];
120:               
121:               if (inputStream != null) {
122:                   this.currBlkIdx = -1;
123:                   this.currRecIdx this.recsPerBlock;
124:               else {
125:                   this.currBlkIdx 0;
126:                   this.currRecIdx 0;
127:               }
128:           }
129:           
130:           /// <summary>
131:           /// Get the TAR Buffer's block size. Blocks consist of multiple records.
132:           /// </summary>
133:           public int GetBlockSize()
134:           {
135:               return this.blockSize;
136:           }
137:           
138:           /// <summary>
139:           /// Get the TAR Buffer's record size.
140:           /// </summary>
141:           public int GetRecordSize()
142:           {
143:               return this.recordSize;
144:           }
145:           
146:           /// <summary>
147:           /// Set the debugging flag for the buffer.
148:           /// </summary>
149:           public void SetDebug(bool debug)
150:           {
151:               this.debug debug;
152:           }
153:           
154:           /// <summary>
155:           /// Determine if an archive record indicate End of Archive. End of
156:           /// archive is indicated by a record that consists entirely of null bytes.
157:           /// </summary>
158:           /// <param name = "record">
159:           /// The record data to check.
160:           /// </param>
161:           public bool IsEOFRecord(byte[] record)
162:           {
163:               for (int 0sz this.GetRecordSize(); sz; ++i) {
164:                   if (record[i] != 0) {
165:                       return false;
166:                   }
167:               }
168:               
169:               return true;
170:           }
171:           
172:           /// <summary>
173:           /// Skip over a record on the input stream.
174:           /// </summary>
175:           public void SkipRecord()
176:           {
177:               if (this.debug) {
178:                   Console.Error.WriteLine("SkipRecord: recIdx = " this.currRecIdx " blkIdx = " this.currBlkIdx);
179:               }
180:               
181:               if (this.inputStream == null) {
182:                   throw new System.IO.IOException("no input stream defined");
183:               }
184:               
185:               if (this.currRecIdx >= this.recsPerBlock) {
186:                   if (!this.ReadBlock()) {
187:                       return// UNDONE
188:                   }
189:               }
190:               
191:               this.currRecIdx++;
192:           }
193:           
194:           /// <summary>
195:           /// Read a record from the input stream and return the data.
196:           /// </summary>
197:           /// <returns>
198:           /// The record data.
199:           /// </returns>
200:           public byte[] ReadRecord()
201:           {
202:               if (this.debug) {
203:                   Console.Error.WriteLine"ReadRecord: recIdx = " this.currRecIdx " blkIdx = " this.currBlkIdx );
204:               }
205:               
206:               if (this.inputStream == null) {
207:                   throw new System.IO.IOException("no input stream defined");
208:               }
209:               
210:               if (this.currRecIdx >= this.recsPerBlock) {
211:                   if (!this.ReadBlock()) {
212:                       return null;
213:                   }
214:               }
215:               
216:               byte[] result new byte[this.recordSize];
217:               
218:               Array.Copy(this.blockBuffer, (this.currRecIdx this.recordSize), result0this.recordSize );
219:               this.currRecIdx++;
220:               return result;
221:           }
222:           
223:           /// <returns>
224:           /// false if End-Of-File, else true
225:           /// </returns>
226:           bool ReadBlock()
227:           {
228:               Console.WriteLine(this.debug);
229:               if (this.debug) {
230:                   Console.Error.WriteLine("ReadBlock: blkIdx = " this.currBlkIdx);
231:               }
232:               
233:               if (this.inputStream == null) {
234:                   throw new System.IO.IOException("no input stream stream defined");
235:               }
236:                           
237:               this.currRecIdx 0;
238:               
239:               int offset 0;
240:               int bytesNeeded this.blockSize;
241:               for (; bytesNeeded ;) {
242:                   long numBytes this.inputStream.Read(this.blockBufferoffsetbytesNeeded);
243:                   
244:                   //
245:                   // NOTE
246:                   // We have fit EOF, and the block is not full!
247:                   //
248:                   // This is a broken archive. It does not follow the standard
249:                   // blocking algorithm. However, because we are generous, and
250:                   // it requires little effort, we will simply ignore the error
251:                   // and continue as if the entire block were read. This does
252:                   // not appear to break anything upstream. We used to return
253:                   // false in this case.
254:                   //
255:                   // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
256:                   //
257:                   if (numBytes <= 0) {
258:                       break;
259:                   }
260:                   
261:                   offset      += (int)numBytes;
262:                   bytesNeeded -= (int)numBytes;
263:                   if (numBytes != this.blockSize) {
264:                       if (this.debug) {
265:                           Console.Error.WriteLine("ReadBlock: INCOMPLETE READ " numBytes " of " this.blockSize " bytes read.");
266:                       }
267:                   }
268:               }
269:               
270:               this.currBlkIdx++;
271:               return true;
272:           }
273:           
274:           /// <summary>
275:           /// Get the current block number, zero based.
276:           /// </summary>
277:           /// <returns>
278:           /// The current zero based block number.
279:           /// </returns>
280:           public int GetCurrentBlockNum()
281:           {
282:               return this.currBlkIdx;
283:           }
284:           
285:           /// <summary>
286:           /// Get the current record number, within the current block, zero based.
287:           /// Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
288:           /// </summary>
289:           /// <returns>
290:           /// The current zero based record number.
291:           /// </returns>
292:           public int GetCurrentRecordNum()
293:           {
294:               return this.currRecIdx 1;
295:           }
296:           
297:           /// <summary>
298:           /// Write an archive record to the archive.
299:           /// </summary>
300:           /// <param name="record">
301:           /// The record data to write to the archive.
302:           /// </param>
303:           /// 
304:           public void WriteRecord(byte[] record)
305:           {
306:               if (this.debug) {
307:                   Console.Error.WriteLine("WriteRecord: recIdx = " this.currRecIdx " blkIdx = " this.currBlkIdx );
308:               }
309:               
310:               if (this.outputStream == null) {
311:                   throw new System.IO.IOException("no output stream defined");
312:               }
313:                           
314:               if (record.Length != this.recordSize) {
315:                   throw new IOException("record to write has length '" record.Length "' which is not the record size of '" this.recordSize "'" );
316:               }
317:               
318:               if (this.currRecIdx >= this.recsPerBlock) {
319:                   this.WriteBlock();
320:               }
321:               Array.Copy(record0this.blockBuffer, (this.currRecIdx this.recordSize), this.recordSize );
322:               this.currRecIdx++;
323:           }
324:           
325:           /// <summary>
326:           /// Write an archive record to the archive, where the record may be
327:           /// inside of a larger array buffer. The buffer must be "offset plus
328:           /// record size" long.
329:           /// </summary>
330:           /// <param name="buf">
331:           /// The buffer containing the record data to write.
332:           /// </param>
333:           /// <param name="offset">
334:           /// The offset of the record data within buf.
335:           /// </param>
336:           public void WriteRecord(byte[] bufint offset)
337:           {
338:               if (this.debug) {
339:                   Console.Error.WriteLine("WriteRecord: recIdx = " this.currRecIdx " blkIdx = " this.currBlkIdx );
340:               }
341:               
342:               if (this.outputStream == null) {
343:                   throw new System.IO.IOException("no output stream stream defined");
344:               }
345:                           
346:               if ((offset this.recordSize) > buf.Length) {
347:                   throw new IOException("record has length '" buf.Length "' with offset '" offset "' which is less than the record size of '" this.recordSize "'" );
348:               }
349:               
350:               if (this.currRecIdx >= this.recsPerBlock) {
351:                   this.WriteBlock();
352:               }
353:               
354:               Array.Copy(bufoffsetthis.blockBuffer, (this.currRecIdx this.recordSize), this.recordSize );
355:               
356:               this.currRecIdx++;
357:           }
358:           
359:           /// <summary>
360:           /// Write a TarBuffer block to the archive.
361:           /// </summary>
362:           void WriteBlock()
363:           {
364:               if (this.debug) {
365:                   Console.Error.WriteLine("WriteBlock: blkIdx = " this.currBlkIdx);
366:               }
367:               
368:               if (this.outputStream == null) {
369:                   throw new System.IO.IOException("no output stream defined");
370:               }
371:               
372:               this.outputStream.Write(this.blockBuffer0this.blockSize);
373:               this.outputStream.Flush();
374:               
375:               this.currRecIdx 0;
376:               this.currBlkIdx++;
377:           }
378:           
379:           /// <summary>
380:           /// Flush the current data block if it has any data in it.
381:           /// </summary>
382:           void Flush()
383:     &nbs