001: /*
002: ** Authored by Timothy Gerard Endres
003: ** <mailto:time@gjt.org> <http://www.trustice.com>
004: **
005: ** This work has been placed into the public domain.
006: ** You may use this work in any way and for any purpose you wish.
007: **
008: ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
009: ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
010: ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
011: ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
012: ** REDISTRIBUTION OF THIS SOFTWARE.
013: **
014: */
015:
016: package installer;
017:
018: import java.io.*;
019: import java.util.Date;
020:
021: /**
022: *
023: * This class represents an entry in a Tar archive. It consists
024: * of the entry's header, as well as the entry's File. Entries
025: * can be instantiated in one of three ways, depending on how
026: * they are to be used.
027: * <p>
028: * TarEntries that are created from the header bytes read from
029: * an archive are instantiated with the TarEntry( byte[] )
030: * constructor. These entries will be used when extracting from
031: * or listing the contents of an archive. These entries have their
032: * header filled in using the header bytes. They also set the File
033: * to null, since they reference an archive entry not a file.
034: * <p>
035: * TarEntries that are created from Files that are to be written
036: * into an archive are instantiated with the TarEntry( File )
037: * constructor. These entries have their header filled in using
038: * the File's information. They also keep a reference to the File
039: * for convenience when writing entries.
040: * <p>
041: * Finally, TarEntries can be constructed from nothing but a name.
042: * This allows the programmer to construct the entry by hand, for
043: * instance when only an InputStream is available for writing to
044: * the archive, and the header information is constructed from
045: * other information. In this case the header fields are set to
046: * defaults and the File is set to null.
047: *
048: * <p>
049: * The C structure for a Tar Entry's header is:
050: * <pre>
051: * struct header {
052: * char name[NAMSIZ];
053: * char mode[8];
054: * char uid[8];
055: * char gid[8];
056: * char size[12];
057: * char mtime[12];
058: * char chksum[8];
059: * char linkflag;
060: * char linkname[NAMSIZ];
061: * char magic[8];
062: * char uname[TUNMLEN];
063: * char gname[TGNMLEN];
064: * char devmajor[8];
065: * char devminor[8];
066: * } header;
067: * </pre>
068: *
069: * @see TarHeader
070: *
071: */
072:
073: public class TarEntry extends Object {
074: /**
075: * If this entry represents a File, this references it.
076: */
077: protected File file;
078:
079: /**
080: * This is the entry's header information.
081: */
082: protected TarHeader header;
083:
084: /**
085: * Construct an entry with only a name. This allows the programmer
086: * to construct the entry's header "by hand". File is set to null.
087: */
088: public TarEntry(String name) {
089: this .initialize();
090: this .nameTarHeader(this .header, name);
091: }
092:
093: /**
094: * Construct an entry for a file. File is set to file, and the
095: * header is constructed from information from the file.
096: *
097: * @param file The file that the entry represents.
098: */
099: public TarEntry(File file) throws InvalidHeaderException {
100: this .initialize();
101: this .getFileTarHeader(this .header, file);
102: }
103:
104: /**
105: * Construct an entry from an archive's header bytes. File is set
106: * to null.
107: *
108: * @param headerBuf The header bytes from a tar archive entry.
109: */
110: public TarEntry(byte[] headerBuf) throws InvalidHeaderException {
111: this .initialize();
112: this .parseTarHeader(this .header, headerBuf);
113: }
114:
115: /**
116: * Initialization code common to all constructors.
117: */
118: private void initialize() {
119: this .file = null;
120: this .header = new TarHeader();
121: }
122:
123: /**
124: * Determine if the two entries are equal. Equality is determined
125: * by the header names being equal.
126: *
127: * @return it Entry to be checked for equality.
128: * @return True if the entries are equal.
129: */
130: public boolean equals(TarEntry it) {
131: return this .header.name.toString().equals(
132: it.header.name.toString());
133: }
134:
135: /**
136: * Determine if the given entry is a descendant of this entry.
137: * Descendancy is determined by the name of the descendant
138: * starting with this entry's name.
139: *
140: * @param desc Entry to be checked as a descendent of this.
141: * @return True if entry is a descendant of this.
142: */
143: public boolean isDescendent(TarEntry desc) {
144: return desc.header.name.toString().startsWith(
145: this .header.name.toString());
146: }
147:
148: /**
149: * Get this entry's header.
150: *
151: * @return This entry's TarHeader.
152: */
153: public TarHeader getHeader() {
154: return this .header;
155: }
156:
157: /**
158: * Get this entry's name.
159: *
160: * @return This entry's name.
161: */
162: public String getName() {
163: return this .header.name.toString();
164: }
165:
166: /**
167: * Set this entry's name.
168: *
169: * @param name This entry's new name.
170: */
171: public void setName(String name) {
172: this .header.name = new StringBuffer(name);
173: }
174:
175: /**
176: * Get this entry's user id.
177: *
178: * @return This entry's user id.
179: */
180: public int getUserId() {
181: return this .header.userId;
182: }
183:
184: /**
185: * Set this entry's user id.
186: *
187: * @param userId This entry's new user id.
188: */
189: public void setUserId(int userId) {
190: this .header.userId = userId;
191: }
192:
193: /**
194: * Get this entry's group id.
195: *
196: * @return This entry's group id.
197: */
198: public int getGroupId() {
199: return this .header.groupId;
200: }
201:
202: /**
203: * Set this entry's group id.
204: *
205: * @param groupId This entry's new group id.
206: */
207: public void setGroupId(int groupId) {
208: this .header.groupId = groupId;
209: }
210:
211: /**
212: * Get this entry's user name.
213: *
214: * @return This entry's user name.
215: */
216: public String getUserName() {
217: return this .header.userName.toString();
218: }
219:
220: /**
221: * Set this entry's user name.
222: *
223: * @param userName This entry's new user name.
224: */
225: public void setUserName(String userName) {
226: this .header.userName = new StringBuffer(userName);
227: }
228:
229: /**
230: * Get this entry's group name.
231: *
232: * @return This entry's group name.
233: */
234: public String getGroupName() {
235: return this .header.groupName.toString();
236: }
237:
238: /**
239: * Set this entry's group name.
240: *
241: * @param groupName This entry's new group name.
242: */
243: public void setGroupName(String groupName) {
244: this .header.groupName = new StringBuffer(groupName);
245: }
246:
247: /**
248: * Convenience method to set this entry's group and user ids.
249: *
250: * @param userId This entry's new user id.
251: * @param groupId This entry's new group id.
252: */
253: public void setIds(int userId, int groupId) {
254: this .setUserId(userId);
255: this .setGroupId(groupId);
256: }
257:
258: /**
259: * Convenience method to set this entry's group and user names.
260: *
261: * @param userName This entry's new user name.
262: * @param groupName This entry's new group name.
263: */
264: public void setNames(String userName, String groupName) {
265: this .setUserName(userName);
266: this .setGroupName(groupName);
267: }
268:
269: /**
270: * Set this entry's modification time. The parameter passed
271: * to this method is in "Java time".
272: *
273: * @param time This entry's new modification time.
274: */
275: public void setModTime(long time) {
276: this .header.modTime = time / 1000;
277: }
278:
279: /**
280: * Set this entry's modification time.
281: *
282: * @param time This entry's new modification time.
283: */
284: public void setModTime(Date time) {
285: this .header.modTime = time.getTime() / 1000;
286: }
287:
288: /**
289: * Set this entry's modification time.
290: *
291: * @param time This entry's new modification time.
292: */
293: public Date getModTime() {
294: return new Date(this .header.modTime * 1000);
295: }
296:
297: /**
298: * Get this entry's file.
299: *
300: * @return This entry's file.
301: */
302: public File getFile() {
303: return this .file;
304: }
305:
306: /**
307: * Get this entry's file size.
308: *
309: * @return This entry's file size.
310: */
311: public long getSize() {
312: return this .header.size;
313: }
314:
315: /**
316: * Set this entry's file size.
317: *
318: * @param size This entry's new file size.
319: */
320: public void setSize(long size) {
321: this .header.size = size;
322: }
323:
324: /**
325: * Convenience method that will modify an entry's name directly
326: * in place in an entry header buffer byte array.
327: *
328: * @param outbuf The buffer containing the entry header to modify.
329: * @param newName The new name to place into the header buffer.
330: */
331: public void adjustEntryName(byte[] outbuf, String newName) {
332: int offset = 0;
333: offset = TarHeader.getNameBytes(new StringBuffer(newName),
334: outbuf, offset, TarHeader.NAMELEN);
335: }
336:
337: /**
338: * Return whether or not this entry represents a directory.
339: *
340: * @return True if this entry is a directory.
341: */
342: public boolean isDirectory() {
343: if (this .file != null)
344: return this .file.isDirectory();
345:
346: if (this .header != null) {
347: if (this .header.linkFlag == TarHeader.LF_DIR)
348: return true;
349:
350: if (this .header.name.toString().endsWith("/"))
351: return true;
352: }
353:
354: return false;
355: }
356:
357: /**
358: * Fill in a TarHeader with information from a File.
359: *
360: * @param hdr The TarHeader to fill in.
361: * @param file The file from which to get the header information.
362: */
363: public void getFileTarHeader(TarHeader hdr, File file)
364: throws InvalidHeaderException {
365: this .file = file;
366:
367: String name = file.getPath();
368: String osname = System.getProperty("os.name");
369: if (osname != null) {
370: // Strip off drive letters!
371: // REVIEW Would a better check be "(File.separator == '\')"?
372:
373: // String Win32Prefix = "Windows";
374: // String prefix = osname.substring( 0, Win32Prefix.length() );
375: // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
376:
377: // if ( File.separatorChar == '\\' )
378:
379: // Per Patrick Beard:
380: String Win32Prefix = "windows";
381: if (osname.toLowerCase().startsWith(Win32Prefix)) {
382: if (name.length() > 2) {
383: char ch1 = name.charAt(0);
384: char ch2 = name.charAt(1);
385: if (ch2 == ':'
386: && ((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z'))) {
387: name = name.substring(2);
388: }
389: }
390: }
391: }
392:
393: name = name.replace(File.separatorChar, '/');
394:
395: // No absolute pathnames
396: // Windows (and Posix?) paths can start with "\\NetworkDrive\",
397: // so we loop on starting /'s.
398:
399: for (; name.startsWith("/");)
400: name = name.substring(1);
401:
402: hdr.linkName = new StringBuffer("");
403:
404: hdr.name = new StringBuffer(name);
405:
406: if (file.isDirectory()) {
407: hdr.mode = 040755;
408: hdr.linkFlag = TarHeader.LF_DIR;
409: if (hdr.name.charAt(hdr.name.length() - 1) != '/')
410: hdr.name.append("/");
411: } else {
412: hdr.mode = 0100644;
413: hdr.linkFlag = TarHeader.LF_NORMAL;
414: }
415:
416: // UNDONE When File lets us get the userName, use it!
417:
418: hdr.size = file.length();
419: hdr.modTime = file.lastModified() / 1000;
420: hdr.checkSum = 0;
421: hdr.devMajor = 0;
422: hdr.devMinor = 0;
423: }
424:
425: /**
426: * If this entry represents a file, and the file is a directory, return
427: * an array of TarEntries for this entry's children.
428: *
429: * @return An array of TarEntry's for this entry's children.
430: */
431: public TarEntry[] getDirectoryEntries()
432: throws InvalidHeaderException {
433: if (this .file == null || !this .file.isDirectory()) {
434: return new TarEntry[0];
435: }
436:
437: String[] list = this .file.list();
438:
439: TarEntry[] result = new TarEntry[list.length];
440:
441: for (int i = 0; i < list.length; ++i) {
442: result[i] = new TarEntry(new File(this .file, list[i]));
443: }
444:
445: return result;
446: }
447:
448: /**
449: * Compute the checksum of a tar entry header.
450: *
451: * @param buf The tar entry's header buffer.
452: * @return The computed checksum.
453: */
454: public long computeCheckSum(byte[] buf) {
455: long sum = 0;
456:
457: for (int i = 0; i < buf.length; ++i) {
458: sum += 255 & buf[i];
459: }
460:
461: return sum;
462: }
463:
464: /**
465: * Write an entry's header information to a header buffer.
466: *
467: * @param outbuf The tar entry header buffer to fill in.
468: */
469: public void writeEntryHeader(byte[] outbuf) {
470: int offset = 0;
471:
472: offset = TarHeader.getNameBytes(this .header.name, outbuf,
473: offset, TarHeader.NAMELEN);
474:
475: offset = TarHeader.getOctalBytes(this .header.mode, outbuf,
476: offset, TarHeader.MODELEN);
477:
478: offset = TarHeader.getOctalBytes(this .header.userId, outbuf,
479: offset, TarHeader.UIDLEN);
480:
481: offset = TarHeader.getOctalBytes(this .header.groupId, outbuf,
482: offset, TarHeader.GIDLEN);
483:
484: long size = this .header.size;
485:
486: offset = TarHeader.getLongOctalBytes(size, outbuf, offset,
487: TarHeader.SIZELEN);
488:
489: offset = TarHeader.getLongOctalBytes(this .header.modTime,
490: outbuf, offset, TarHeader.MODTIMELEN);
491:
492: int csOffset = offset;
493: for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
494: outbuf[offset++] = (byte) ' ';
495:
496: outbuf[offset++] = this .header.linkFlag;
497:
498: offset = TarHeader.getNameBytes(this .header.linkName, outbuf,
499: offset, TarHeader.NAMELEN);
500:
501: offset = TarHeader.getNameBytes(this .header.magic, outbuf,
502: offset, TarHeader.MAGICLEN);
503:
504: offset = TarHeader.getNameBytes(this .header.userName, outbuf,
505: offset, TarHeader.UNAMELEN);
506:
507: offset = TarHeader.getNameBytes(this .header.groupName, outbuf,
508: offset, TarHeader.GNAMELEN);
509:
510: offset = TarHeader.getOctalBytes(this .header.devMajor, outbuf,
511: offset, TarHeader.DEVLEN);
512:
513: offset = TarHeader.getOctalBytes(this .header.devMinor, outbuf,
514: offset, TarHeader.DEVLEN);
515:
516: for (; offset < outbuf.length;)
517: outbuf[offset++] = 0;
518:
519: long checkSum = this .computeCheckSum(outbuf);
520:
521: TarHeader.getCheckSumOctalBytes(checkSum, outbuf, csOffset,
522: TarHeader.CHKSUMLEN);
523: }
524:
525: /**
526: * Parse an entry's TarHeader information from a header buffer.
527: *
528: * @param hdr The TarHeader to fill in from the buffer information.
529: * @param header The tar entry header buffer to get information from.
530: */
531: public void parseTarHeader(TarHeader hdr, byte[] header)
532: throws InvalidHeaderException {
533: int offset = 0;
534:
535: hdr.name = TarHeader.parseName(header, offset,
536: TarHeader.NAMELEN);
537:
538: offset += TarHeader.NAMELEN;
539:
540: hdr.mode = (int) TarHeader.parseOctal(header, offset,
541: TarHeader.MODELEN);
542:
543: offset += TarHeader.MODELEN;
544:
545: hdr.userId = (int) TarHeader.parseOctal(header, offset,
546: TarHeader.UIDLEN);
547:
548: offset += TarHeader.UIDLEN;
549:
550: hdr.groupId = (int) TarHeader.parseOctal(header, offset,
551: TarHeader.GIDLEN);
552:
553: offset += TarHeader.GIDLEN;
554:
555: hdr.size = TarHeader.parseOctal(header, offset,
556: TarHeader.SIZELEN);
557:
558: offset += TarHeader.SIZELEN;
559:
560: hdr.modTime = TarHeader.parseOctal(header, offset,
561: TarHeader.MODTIMELEN);
562:
563: offset += TarHeader.MODTIMELEN;
564:
565: hdr.checkSum = (int) TarHeader.parseOctal(header, offset,
566: TarHeader.CHKSUMLEN);
567:
568: offset += TarHeader.CHKSUMLEN;
569:
570: hdr.linkFlag = header[offset++];
571:
572: hdr.linkName = TarHeader.parseName(header, offset,
573: TarHeader.NAMELEN);
574:
575: offset += TarHeader.NAMELEN;
576:
577: hdr.magic = TarHeader.parseName(header, offset,
578: TarHeader.MAGICLEN);
579:
580: offset += TarHeader.MAGICLEN;
581:
582: hdr.userName = TarHeader.parseName(header, offset,
583: TarHeader.UNAMELEN);
584:
585: offset += TarHeader.UNAMELEN;
586:
587: hdr.groupName = TarHeader.parseName(header, offset,
588: TarHeader.GNAMELEN);
589:
590: offset += TarHeader.GNAMELEN;
591:
592: hdr.devMajor = (int) TarHeader.parseOctal(header, offset,
593: TarHeader.DEVLEN);
594:
595: offset += TarHeader.DEVLEN;
596:
597: hdr.devMinor = (int) TarHeader.parseOctal(header, offset,
598: TarHeader.DEVLEN);
599: }
600:
601: /**
602: * Fill in a TarHeader given only the entry's name.
603: *
604: * @param hdr The TarHeader to fill in.
605: * @param name The tar entry name.
606: */
607: public void nameTarHeader(TarHeader hdr, String name) {
608: boolean isDir = name.endsWith("/");
609:
610: hdr.checkSum = 0;
611: hdr.devMajor = 0;
612: hdr.devMinor = 0;
613:
614: hdr.name = new StringBuffer(name);
615: hdr.mode = isDir ? 040755 : 0100644;
616: hdr.userId = 0;
617: hdr.groupId = 0;
618: hdr.size = 0;
619: hdr.checkSum = 0;
620:
621: hdr.modTime = (new java.util.Date()).getTime() / 1000;
622:
623: hdr.linkFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
624:
625: hdr.linkName = new StringBuffer("");
626: hdr.userName = new StringBuffer("");
627: hdr.groupName = new StringBuffer("");
628:
629: hdr.devMajor = 0;
630: hdr.devMinor = 0;
631: }
632:
633: }
|