1 /***
2 *
3 */
4 package com.fernsroth.squashfs;
5
6 import java.io.ByteArrayInputStream;
7 import java.io.ByteArrayOutputStream;
8 import java.io.IOException;
9 import java.io.OutputStream;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.zip.InflaterInputStream;
15
16 import org.apache.commons.logging.Log;
17 import org.apache.commons.logging.LogFactory;
18
19 import com.fernsroth.easyio.EasyIOFormatter;
20 import com.fernsroth.easyio.EasyIOInputStream;
21 import com.fernsroth.easyio.IRandomAccessSource;
22 import com.fernsroth.easyio.exception.EasyIOException;
23 import com.fernsroth.squashfs.model.Directory;
24 import com.fernsroth.squashfs.model.SFSSquashedFile;
25 import com.fernsroth.squashfs.model.SymLink;
26 import com.fernsroth.squashfs.model.squashfs.dir;
27 import com.fernsroth.squashfs.model.squashfs.dir_ent;
28 import com.fernsroth.squashfs.model.squashfs.squashfs_base_inode_header;
29 import com.fernsroth.squashfs.model.squashfs.squashfs_constants;
30 import com.fernsroth.squashfs.model.squashfs.squashfs_dir_entry;
31 import com.fernsroth.squashfs.model.squashfs.squashfs_dir_header;
32 import com.fernsroth.squashfs.model.squashfs.squashfs_dir_inode_header;
33 import com.fernsroth.squashfs.model.squashfs.squashfs_fragment_entry;
34 import com.fernsroth.squashfs.model.squashfs.squashfs_ldir_inode_header;
35 import com.fernsroth.squashfs.model.squashfs.squashfs_reg_inode_header;
36 import com.fernsroth.squashfs.model.squashfs.squashfs_super_block;
37 import com.fernsroth.squashfs.model.squashfs.squashfs_symlink_inode_header;
38
39 /***
40 *
41 * @author Joseph M. Ferner (Near Infinity Corporation)
42 */
43 public final class SquashFSReader {
44
45 /***
46 * logging.
47 */
48 private static Log log = LogFactory.getLog(SquashFSReader.class);
49
50 /***
51 * the read in directory.
52 */
53 private Directory rootDirectory;
54
55 /***
56 * the source to read.
57 */
58 private IRandomAccessSource source;
59
60 /***
61 * the super block.
62 */
63 private squashfs_super_block superBlock;
64
65 /***
66 * see unsquashfs.c.
67 */
68 private long[] uid_table;
69
70 /***
71 * see unsquashfs.c.
72 */
73 private long[] guid_table;
74
75 /***
76 * see unsquashfs.c.
77 */
78 private squashfs_fragment_entry[] fragment_table;
79
80 /***
81 * see unsquashfs.c.
82 */
83 private ByteArrayOutputStream inode_table = new ByteArrayOutputStream();
84
85 /***
86 * see unsquashfs.c.
87 * <start, offset>
88 */
89 private Map<Long, Long> inode_table_hash = new HashMap<Long, Long>();
90
91 /***
92 * see unsquashfs.c.
93 */
94 private ByteArrayOutputStream directory_table = new ByteArrayOutputStream();
95
96 /***
97 * see unsquashfs.c.
98 * <start, offset>
99 */
100 private Map<Long, Long> directory_table_hash = new HashMap<Long, Long>();
101
102 /***
103 * constructor.
104 * @param source the data source to read.
105 * @throws IOException
106 * @throws EasyIOException
107 */
108 public SquashFSReader(IRandomAccessSource source) throws EasyIOException,
109 IOException {
110 this.source = source;
111 this.source.seek(0);
112
113 read_super();
114
115 read_uids_guids();
116 read_fragment_table();
117 uncompress_inode_table(this.superBlock.inode_table_start,
118 this.superBlock.directory_table_start);
119 uncompress_directory_table(this.superBlock.directory_table_start,
120 this.superBlock.fragment_table_start);
121 this.rootDirectory = dir_scan(null, squashfs_constants
122 .SQUASHFS_INODE_BLK(this.superBlock.root_inode),
123 squashfs_constants
124 .SQUASHFS_INODE_OFFSET(this.superBlock.root_inode));
125 }
126
127 /***
128 * @param name the name of the directory.
129 * @param start_block the block to start reading at.
130 * @param offset the offset of the first block.
131 * @return the directory.
132 * @throws IOException
133 * @throws EasyIOException
134 */
135 private Directory dir_scan(String name, long start_block, long offset)
136 throws IOException, EasyIOException {
137 dir dir = squashfs_openddir(start_block, offset);
138 if (dir == null) {
139 throw new IOException("dir_scan: Failed to read directory ("
140 + start_block + ":" + offset + ")");
141 }
142 Directory result = new Directory(name, dir.mode, dir.mtime, getGuid(
143 dir.guid, dir.uid), getUid(dir.uid));
144
145 if (dir.dirs != null) {
146 for (dir_ent dirent : dir.dirs) {
147 log.trace("dir_scan: name " + dirent.name + ", start_block "
148 + dirent.start_block + ", offset " + dirent.offset
149 + ", type " + dirent.type);
150
151 switch (dirent.type) {
152 case squashfs_constants.SQUASHFS_DIR_TYPE:
153 Directory subdir = dir_scan(dirent.name,
154 dirent.start_block, dirent.offset);
155 result.addSubentry(subdir);
156 break;
157
158 case squashfs_constants.SQUASHFS_FILE_TYPE:
159 SFSSquashedFile subfile = read_file(dirent.name,
160 dirent.start_block, dirent.offset);
161 result.addSubentry(subfile);
162 break;
163
164 case squashfs_constants.SQUASHFS_SYMLINK_TYPE:
165 SymLink subsymlink = read_symlink(dirent.name,
166 dirent.start_block, dirent.offset);
167 result.addSubentry(subsymlink);
168 break;
169
170 case squashfs_constants.SQUASHFS_BLKDEV_TYPE:
171 throw new RuntimeException("not implemented");
172
173 case squashfs_constants.SQUASHFS_CHRDEV_TYPE:
174 throw new RuntimeException("not implemented");
175
176 case squashfs_constants.SQUASHFS_FIFO_TYPE:
177 throw new RuntimeException("not implemented");
178
179 case squashfs_constants.SQUASHFS_SOCKET_TYPE:
180 throw new RuntimeException("not implemented");
181
182 case squashfs_constants.SQUASHFS_LDIR_TYPE:
183 throw new RuntimeException("not implemented");
184
185 case squashfs_constants.SQUASHFS_LREG_TYPE:
186 throw new RuntimeException("not implemented");
187
188 }
189 }
190 }
191 return result;
192 }
193
194 /***
195 * reads a symbolic link from the squashfs.
196 * @param name the name of the link.
197 * @param start_block the start block.
198 * @param offset the offset.
199 * @return the symbolic link object.
200 * @throws IOException
201 * @throws EasyIOException
202 */
203 private SymLink read_symlink(String name, long start_block, long offset)
204 throws EasyIOException, IOException {
205 long start = this.superBlock.inode_table_start + start_block;
206 long block_ptr;
207 long bytes = this.inode_table_hash.get(start);
208 byte[] inode_table_bytes = this.inode_table.toByteArray();
209
210 log.trace("create_inode: start " + start + ", offset " + offset);
211
212 if (bytes == -1) {
213 throw new IOException(
214 "create_inode: inode block start out of range!");
215 }
216 block_ptr = bytes + offset;
217
218
219
220
221
222
223
224
225 EasyIOInputStream in = new EasyIOInputStream(new ByteArrayInputStream(
226 inode_table_bytes, (int) block_ptr,
227 squashfs_constants.SQUASHFS_SYMLINK_INODE_HEADER_SIZE));
228 squashfs_symlink_inode_header inodep = in
229 .read(new squashfs_symlink_inode_header());
230
231
232 log.trace("create_inode: symlink, symlink_size " + inodep.symlink_size);
233
234 inodep.symlink = new String(
235 inode_table_bytes,
236 (int) (block_ptr + squashfs_constants.SQUASHFS_SYMLINK_INODE_HEADER_SIZE),
237 inodep.symlink_size);
238
239 return new SymLink(name, (int) inodep.mode, inodep.mtime, getGuid(
240 inodep.guid, inodep.uid), getUid(inodep.uid), inodep.symlink);
241 }
242
243 /***
244 * @param uid
245 * @return get uid.
246 */
247 private long getUid(long uid) {
248 return this.uid_table[(int) uid];
249 }
250
251 /***
252 * @param guid
253 * @param uid
254 * @return get the guid.
255 */
256 private long getGuid(long guid, long uid) {
257 return (guid == squashfs_constants.SQUASHFS_GUIDS) ? uid
258 : this.guid_table[(int) guid];
259 }
260
261 /***
262 * reads a file from the squashfs.
263 * @param name the name of the file.
264 * @param start_block the start block.
265 * @param offset the offset.
266 * @return the file object.
267 * @throws IOException
268 * @throws EasyIOException
269 */
270 private SFSSquashedFile read_file(String name, long start_block, long offset)
271 throws IOException, EasyIOException {
272 long start = this.superBlock.inode_table_start + start_block;
273 long block_ptr;
274 long bytes = this.inode_table_hash.get(start);
275 byte[] inode_table_bytes = this.inode_table.toByteArray();
276
277 log.trace("create_inode: start " + start + ", offset " + offset);
278
279 if (bytes == -1) {
280 throw new IOException(
281 "create_inode: inode block start out of range!");
282 }
283 block_ptr = bytes + offset;
284
285
286
287
288
289
290
291
292 EasyIOInputStream in = new EasyIOInputStream(new ByteArrayInputStream(
293 inode_table_bytes, (int) block_ptr,
294 squashfs_constants.SQUASHFS_REG_INODE_HEADER_SIZE));
295 squashfs_reg_inode_header inode = in
296 .read(new squashfs_reg_inode_header());
297
298
299 int blocks;
300
301 long frag_bytes = (inode.fragment == squashfs_constants.SQUASHFS_INVALID_FRAG) ? 0
302 : (inode.file_size % this.superBlock.block_size);
303 offset = inode.offset;
304 blocks = (int) (inode.fragment == squashfs_constants.SQUASHFS_INVALID_FRAG ? (inode.file_size
305 + this.superBlock.block_size - 1) >> this.superBlock.block_log
306 : inode.file_size >> this.superBlock.block_log);
307 start = inode.start_block;
308
309 log.trace("create_inode: regular file, file_size " + inode.file_size
310 + ", blocks " + blocks);
311
312 squashfs_fragment_entry fragmentEntry = null;
313 if (frag_bytes != 0) {
314 fragmentEntry = this.fragment_table[(int) inode.fragment];
315 }
316
317 return new SFSSquashedFile(name, (int) inode.mode, inode.mtime,
318 getGuid(inode.guid, inode.uid), getUid(inode.uid), start,
319 blocks, offset, block_ptr
320 + squashfs_constants.SQUASHFS_REG_INODE_HEADER_SIZE,
321 fragmentEntry, frag_bytes);
322 }
323
324 /***
325 * @param block_start the start block.
326 * @param offset the offset.
327 * @return the new dir entry.
328 * @throws IOException
329 * @throws EasyIOException
330 */
331 private dir squashfs_openddir(long block_start, long offset)
332 throws IOException, EasyIOException {
333 long start = this.superBlock.inode_table_start + block_start;
334 Long bytes = this.inode_table_hash.get(start);
335 squashfs_base_inode_header header;
336 long dir_count, size;
337 byte[] directory_table_bytes = this.directory_table.toByteArray();
338 byte[] inode_table_bytes = this.inode_table.toByteArray();
339
340 log.trace("squashfs_opendir: inode start block " + block_start
341 + ", offset " + offset);
342
343 if (bytes == -1) {
344 throw new IOException("squashfs_opendir: inode block "
345 + block_start + " not found!");
346 }
347
348 int baOffset = (int) (bytes + offset);
349 int baLength = (int) (inode_table_bytes.length - (bytes + offset));
350 EasyIOInputStream in = new EasyIOInputStream(new ByteArrayInputStream(
351 inode_table_bytes, baOffset, baLength));
352
353 if (log.isTraceEnabled()) {
354 log.trace("data \n"
355 + EasyIOFormatter.print(new ByteArrayInputStream(
356 inode_table_bytes, baOffset, baLength)));
357 }
358
359
360
361
362
363
364
365
366 header = in.read(new squashfs_dir_inode_header());
367
368
369 switch ((int) header.inode_type) {
370 case squashfs_constants.SQUASHFS_DIR_TYPE:
371 block_start = ((squashfs_dir_inode_header) header).start_block;
372 offset = ((squashfs_dir_inode_header) header).offset;
373 size = ((squashfs_dir_inode_header) header).file_size;
374 break;
375 case squashfs_constants.SQUASHFS_LDIR_TYPE:
376
377
378
379
380
381
382 in = new EasyIOInputStream(new ByteArrayInputStream(
383 inode_table_bytes, (int) (bytes + offset),
384 (int) (inode_table_bytes.length - (bytes + offset))));
385 header = in.read(new squashfs_ldir_inode_header());
386
387 block_start = ((squashfs_ldir_inode_header) header).start_block;
388 offset = ((squashfs_ldir_inode_header) header).offset;
389 size = ((squashfs_ldir_inode_header) header).file_size;
390 break;
391 default:
392 throw new IOException("squashfs_opendir: inode not a directory");
393 }
394
395 start = this.superBlock.directory_table_start + block_start;
396 bytes = this.directory_table_hash.get(start);
397
398 if (bytes == null) {
399 throw new IOException("squashfs_opendir: directory block "
400 + block_start + " not found!");
401 }
402 bytes += offset;
403 size += bytes - 3;
404
405 squashfs_dir_entry dire = new squashfs_dir_entry();
406 squashfs_dir_header dirh = new squashfs_dir_header();
407 dir dir = new dir();
408
409 dir.dir_count = 0;
410 dir.cur_entry = 0;
411 dir.mode = (int) header.mode;
412 dir.uid = header.uid;
413 dir.guid = header.guid;
414 dir.mtime = header.mtime;
415 dir.dirs = null;
416
417 while (bytes < size) {
418
419
420
421
422
423
424
425 baOffset = (int) bytes.longValue();
426 baLength = squashfs_constants.SQUASHFS_DIR_HEADER_SIZE;
427 in = new EasyIOInputStream(new ByteArrayInputStream(
428 directory_table_bytes, baOffset, baLength));
429 dirh = in.read(new squashfs_dir_header());
430
431
432 if (log.isTraceEnabled()) {
433 log.trace("squashfs_dir_header \n"
434 + EasyIOFormatter.print(new ByteArrayInputStream(
435 directory_table_bytes, baOffset, baLength)));
436 }
437
438 dir_count = dirh.count + 1;
439 log
440 .trace("squashfs_opendir: Read directory header @ byte position "
441 + bytes + ", " + dir_count + " directory entries");
442 bytes += squashfs_constants.SQUASHFS_DIR_HEADER_SIZE;
443
444 while (dir_count-- != 0) {
445
446
447
448
449
450
451
452 baOffset = (int) bytes.longValue();
453 baLength = squashfs_constants.SQUASHFS_DIR_ENTRY_SIZE;
454 in = new EasyIOInputStream(new ByteArrayInputStream(
455 directory_table_bytes, baOffset, baLength));
456 dire = in.read(new squashfs_dir_entry());
457
458 bytes += squashfs_constants.SQUASHFS_DIR_ENTRY_SIZE;
459
460 if (log.isTraceEnabled()) {
461 log
462 .trace("squashfs_dir_entry \n"
463 + EasyIOFormatter
464 .print(new ByteArrayInputStream(
465 directory_table_bytes,
466 baOffset, baLength)));
467 }
468
469 dire.name = new String(directory_table_bytes, (int) bytes
470 .longValue(), dire.size + 1);
471
472 log.trace("squashfs_opendir: directory entry " + dire.name
473 + ", inode " + dirh.start_block + ":" + dire.offset
474 + ", type " + dire.type);
475 if ((dir.dir_count % squashfs_constants.DIR_ENT_SIZE) == 0) {
476
477
478
479
480
481
482
483
484
485 }
486 dir_ent dirent = new dir_ent();
487 dirent.name = dire.name;
488 dirent.start_block = dirh.start_block;
489 dirent.offset = dire.offset;
490 dirent.type = dire.type;
491 if (dir.dirs == null) {
492 dir.dirs = new ArrayList<dir_ent>();
493 }
494 dir.dirs.add(dirent);
495 dir.dir_count++;
496 bytes += dire.size + 1;
497 }
498 }
499
500 return dir;
501 }
502
503 /***
504 * @param start start offset.
505 * @param end end offset.
506 * @throws IOException
507 */
508 private void uncompress_directory_table(long start, long end)
509 throws IOException {
510 while (start < end) {
511 log.trace("uncompress_directory_table: reading block " + start);
512
513 long[] next = new long[1];
514 byte[] readdata = read_block(start, next);
515 this.directory_table_hash.put(start, (long) this.directory_table
516 .size());
517 this.directory_table.write(readdata, 0, readdata.length);
518 start = next[0];
519 }
520 }
521
522 /***
523 * @param start start offset.
524 * @param end end offset.
525 * @throws IOException
526 */
527 private void uncompress_inode_table(long start, long end)
528 throws IOException {
529 while (start < end) {
530 log.trace("uncompress_inode_table: reading block " + start);
531
532 long[] next = new long[1];
533 byte[] readdata = read_block(start, next);
534 this.inode_table_hash.put(start, (long) this.inode_table.size());
535 this.inode_table.write(readdata, 0, readdata.length);
536 start = next[0];
537 }
538 }
539
540 /***
541 * @param squashfs the squashfs to read into.
542 * @throws IOException
543 * @throws EasyIOException
544 */
545 private void read_super() throws EasyIOException, IOException {
546 EasyIOInputStream easyin = new EasyIOInputStream(this.source);
547
548 this.superBlock = easyin.read(new squashfs_super_block());
549 if (this.superBlock.s_magic != squashfs_constants.SQUASHFS_MAGIC) {
550 throw new IOException("magic does not match");
551 }
552
553 log.debug("superblock.s_magic:" + this.superBlock.s_magic);
554 log.debug("superblock.inodes:" + this.superBlock.inodes);
555 log.debug("superblock.bytes_used_2:" + this.superBlock.bytes_used_2);
556 log.debug("superblock.uid_start_2:" + this.superBlock.uid_start_2);
557 log.debug("superblock.guid_start_2:" + this.superBlock.guid_start_2);
558 log.debug("superblock.inode_table_start_2:"
559 + this.superBlock.inode_table_start_2);
560 log.debug("superblock.directory_table_start_2:"
561 + this.superBlock.directory_table_start_2);
562 log.debug("superblock.s_major:" + this.superBlock.s_major);
563 log.debug("superblock.s_minor:" + this.superBlock.s_minor);
564 log.debug("superblock.block_size_1:" + this.superBlock.block_size_1);
565 log.debug("superblock.block_log:" + this.superBlock.block_log);
566 log.debug("superblock.flags:" + this.superBlock.flags);
567 log.debug("superblock.no_uids:" + this.superBlock.no_uids);
568 log.debug("superblock.no_guids:" + this.superBlock.no_guids);
569 log.debug("superblock.mkfs_time:" + this.superBlock.mkfs_time);
570 log.debug("superblock.root_inode:" + this.superBlock.root_inode);
571 log.debug("superblock.block_size:" + this.superBlock.block_size);
572 log.debug("superblock.fragments:" + this.superBlock.fragments);
573 log.debug("superblock.fragment_table_start_2:"
574 + this.superBlock.fragment_table_start_2);
575 log.debug("superblock.bytes_used:" + this.superBlock.bytes_used);
576 log.debug("superblock.uid_start:" + this.superBlock.uid_start);
577 log.debug("superblock.guid_start:" + this.superBlock.guid_start);
578 log.debug("superblock.inode_table_start:"
579 + this.superBlock.inode_table_start);
580 log.debug("superblock.directory_table_start:"
581 + this.superBlock.directory_table_start);
582 log.debug("superblock.fragment_table_start:"
583 + this.superBlock.fragment_table_start);
584 log.debug("superblock.unused:" + this.superBlock.unused);
585 }
586
587 /***
588 * @param squashfs the squash fs structure.
589 * @throws IOException
590 */
591 private void read_uids_guids() throws IOException {
592 this.uid_table = new long[this.superBlock.no_uids];
593 this.guid_table = new long[this.superBlock.no_guids];
594
595 this.source.seek((int) this.superBlock.uid_start);
596
597 EasyIOInputStream in = new EasyIOInputStream(this.source);
598 for (int i = 0; i < this.uid_table.length; i++) {
599 this.uid_table[i] = in.readUINT32();
600 }
601 for (int i = 0; i < this.guid_table.length; i++) {
602 this.guid_table[i] = in.readUINT32();
603 }
604 }
605
606 /***
607 * @throws IOException
608 * @throws EasyIOException
609 */
610 private void read_fragment_table() throws IOException, EasyIOException {
611 EasyIOInputStream in = new EasyIOInputStream(this.source);
612 int i, indexes = (int) squashfs_constants
613 .SQUASHFS_FRAGMENT_INDEXES(this.superBlock.fragments);
614 long[] fragment_table_index = new long[indexes];
615
616 log.trace("read_fragment_table: " + this.superBlock.fragments
617 + " fragments, reading " + indexes + " fragment indexes from "
618 + this.superBlock.fragment_table_start);
619 if (this.superBlock.fragments == 0) {
620 return;
621 }
622
623 this.fragment_table = new squashfs_fragment_entry[(int) this.superBlock.fragments];
624
625
626
627
628
629
630
631
632 this.source.seek((int) this.superBlock.fragment_table_start);
633 for (i = 0; i < squashfs_constants
634 .SQUASHFS_FRAGMENT_INDEX_BYTES(this.superBlock.fragments) / 8; i++) {
635 fragment_table_index[i] = in.readINT64();
636 }
637
638
639 for (i = 0; i < indexes; i++) {
640 byte[] buffer = read_block(fragment_table_index[i], null);
641 EasyIOInputStream fragmentIn = new EasyIOInputStream(
642 new ByteArrayInputStream(buffer));
643 this.fragment_table[i * squashfs_constants.SQUASHFS_METADATA_SIZE] = fragmentIn
644 .read(new squashfs_fragment_entry());
645 log.trace("Read fragment table block " + i + ", from "
646 + fragment_table_index[i] + ", length " + buffer.length);
647 }
648
649
650
651
652
653
654
655
656
657
658 }
659
660 /***
661 * reads a block.
662 * @param start the start of the block.
663 * @param next the next block location (1 length array).
664 * @return the data.
665 * @throws IOException
666 */
667 private byte[] read_block(long start, long[] next) throws IOException {
668 int c_byte;
669 int offset = 2;
670
671 this.source.seek((int) start);
672 EasyIOInputStream in = new EasyIOInputStream(this.source);
673
674
675
676
677
678
679
680
681
682 c_byte = in.readUINT16();
683
684 log
685 .trace("read_block: block "
686 + start
687 + ", "
688 + squashfs_constants.SQUASHFS_COMPRESSED_SIZE(c_byte)
689 + " "
690 + (squashfs_constants.SQUASHFS_COMPRESSED(c_byte) ? "compressed"
691 : "uncompressed") + " bytes");
692
693 if (squashfs_constants.SQUASHFS_CHECK_DATA(this.superBlock.flags))
694 offset = 3;
695 if (squashfs_constants.SQUASHFS_COMPRESSED(c_byte)) {
696 byte[] buffer = new byte[squashfs_constants.SQUASHFS_METADATA_SIZE];
697
698 c_byte = squashfs_constants.SQUASHFS_COMPRESSED_SIZE(c_byte);
699 this.source.seek((int) (start + offset));
700 if (this.source.read(buffer, 0, c_byte) != c_byte) {
701 throw new IOException("read past end of stream");
702 }
703
704 byte[] block = uncompress(buffer, c_byte);
705
706 if (next != null) {
707 next[0] = start + offset + c_byte;
708 }
709 return block;
710 } else {
711 c_byte = squashfs_constants.SQUASHFS_COMPRESSED_SIZE(c_byte);
712 this.source.seek((int) (start + offset));
713 byte[] block = new byte[c_byte];
714 if (this.source.read(block, 0, c_byte) != c_byte) {
715 throw new IOException("read past end of file");
716 }
717 if (next != null) {
718 next[0] = start + offset + c_byte;
719 }
720 return block;
721 }
722 }
723
724 /***
725 * reads a data block.
726 * @param start the start offset.
727 * @param size the size.
728 * @return the data read.
729 * @throws IOException
730 */
731 private byte[] read_data_block(long start, long size) throws IOException {
732 if (size == 0) {
733 return new byte[0];
734 }
735
736 long c_byte = squashfs_constants.SQUASHFS_COMPRESSED_SIZE_BLOCK(size);
737
738 if (log.isTraceEnabled()) {
739 log
740 .trace("read_data_block: block @"
741 + start
742 + ", "
743 + squashfs_constants
744 .SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte)
745 + " "
746 + (squashfs_constants
747 .SQUASHFS_COMPRESSED_BLOCK(c_byte) ? "compressed"
748 : "uncompressed") + " bytes");
749 }
750
751 byte[] readdata = new byte[(int) c_byte];
752 this.source.seek((int) start);
753 int rc;
754 if ((rc = this.source.read(readdata)) != readdata.length) {
755 throw new IOException("could not read file at offset " + start
756 + " read " + rc + " bytes, expected " + readdata.length
757 + " bytes.");
758 }
759
760 if (squashfs_constants.SQUASHFS_COMPRESSED_BLOCK(size)) {
761 readdata = uncompress(readdata, readdata.length);
762 }
763 return readdata;
764 }
765
766 /***
767 * @param fragment_entry
768 * @return the fragment data.
769 * @throws IOException
770 */
771 private byte[] read_fragment(squashfs_fragment_entry fragment_entry)
772 throws IOException {
773 log.trace("read_fragment: reading fragment "
774 + fragment_entry.start_block);
775 return read_data_block(fragment_entry.start_block, fragment_entry.size);
776 }
777
778 /***
779 * uncompress compressed data.
780 * @param buffer the data to uncompress.
781 * @param length the length to uncompress.
782 * @return the uncompressed data.
783 * @throws IOException
784 */
785 private byte[] uncompress(byte[] buffer, int length) throws IOException {
786 InflaterInputStream in = new InflaterInputStream(
787 new ByteArrayInputStream(buffer, 0, length));
788 ByteArrayOutputStream baos = new ByteArrayOutputStream();
789 int r;
790 while ((r = in.read()) != -1) {
791 baos.write(r);
792 }
793 return baos.toByteArray();
794 }
795
796 /***
797 * @param sourceFile the source squashfs file.
798 * @param squashFile the squash file to write out.
799 * @param destFile the destination file.
800 * @throws IOException
801 */
802 public void writeFile(SFSSquashedFile squashFile, OutputStream destFile)
803 throws IOException {
804 List<Long> block_list = new ArrayList<Long>();
805
806 log.trace("write_file: regular file, blocks " + squashFile.getBlocks());
807
808
809
810
811
812
813
814
815 byte[] inode_table_bytes = this.inode_table.toByteArray();
816 EasyIOInputStream in = new EasyIOInputStream(new ByteArrayInputStream(
817 inode_table_bytes, (int) squashFile.getBlockPtr(), squashFile
818 .getBlocks() * 4));
819 for (int i = 0; i < squashFile.getBlocks(); i++) {
820 block_list.add(in.readUINT32());
821 }
822
823
824 long start = squashFile.getStart();
825 for (int i = 0; i < squashFile.getBlocks(); i++) {
826 byte[] file_data = read_data_block(start, block_list.get(i));
827
828 destFile.write(file_data);
829
830 start += squashfs_constants
831 .SQUASHFS_COMPRESSED_SIZE_BLOCK(block_list.get(i));
832 }
833
834 if (squashFile.getFragmentEntry() != null) {
835 byte[] fragment_data = read_fragment(squashFile.getFragmentEntry());
836
837 int offset = (int) squashFile.getOffset();
838 int size = (int) squashFile.getFragmentBytes();
839 destFile.write(fragment_data, offset, size);
840 }
841 }
842
843 /***
844 * gets the root directory.
845 * @return the root directory.
846 */
847 public Directory getRootDirectory() {
848 return this.rootDirectory;
849 }
850 }