View Javadoc

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          if(swap) {
220          squashfs_base_inode_header sinode;
221          memcpy(&sinode, block_ptr, sizeof(header.base));
222          SQUASHFS_SWAP_BASE_INODE_HEADER(&header.base, &sinode, sizeof(squashfs_base_inode_header));
223          } else {
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          if(swap) {
287          squashfs_base_inode_header sinode;
288          memcpy(&sinode, block_ptr, sizeof(header.base));
289          SQUASHFS_SWAP_BASE_INODE_HEADER(&header.base, &sinode, sizeof(squashfs_base_inode_header));
290          } else {
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          if(swap) {
361          squashfs_dir_inode_header sinode;
362          memcpy(&sinode, block_ptr, sizeof(header.dir));
363          SQUASHFS_SWAP_DIR_INODE_HEADER(&header.dir, &sinode);
364          } else {
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              if(swap) {
378              squashfs_ldir_inode_header sinode;
379              memcpy(&sinode, block_ptr, sizeof(header.ldir));
380              SQUASHFS_SWAP_LDIR_INODE_HEADER(&header.ldir, &sinode);
381              } else { */
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              if(swap) {
420              squashfs_dir_header sdirh;
421              memcpy(&sdirh, directory_table + bytes, sizeof(sdirh));
422              SQUASHFS_SWAP_DIR_HEADER(&dirh, &sdirh);
423              } else {
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                  if(swap) {
447                  squashfs_dir_entry sdire;
448                  memcpy(&sdire, directory_table + bytes, sizeof(sdire));
449                  SQUASHFS_SWAP_DIR_ENTRY(dire, &sdire);
450                  } else {
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                     /*TODO not sure.
477                      if((new_dir = realloc(dir->dirs, (dir->dir_count + DIR_ENT_SIZE) * sizeof(struct dir_ent))) == NULL) {
478                      log.error("squashfs_opendir: realloc failed!\n");
479                      free(dir->dirs);
480                      free(dir);
481                      return NULL;
482                      }
483                      */
484                     //TODO dir.dirs = new_dir;
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          if(squashfs.swap) {
627          long[] sfragment_table_index = new long[indexes];
628          read_bytes(squashfs.superBlock.fragment_table_start, SQUASHFS_FRAGMENT_INDEX_BYTES(squashfs.superBlock.fragments), (char *) sfragment_table_index);
629          SQUASHFS_SWAP_FRAGMENT_INDEXES(fragment_table_index, sfragment_table_index, indexes);
630          } else {
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          if(swap) {
651          squashfs_fragment_entry sfragment;
652          for(i = 0; i < sBlk->fragments; i++) {
653          SQUASHFS_SWAP_FRAGMENT_ENTRY((&sfragment), (&fragment_table[i]));
654          memcpy((char *) &fragment_table[i], (char *) &sfragment, sizeof(squashfs_fragment_entry));
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          if(swap) {
676          if(read_bytes(start, 2, block) == FALSE)
677          goto failed;
678          ((unsigned char *) &c_byte)[1] = block[0];
679          ((unsigned char *) &c_byte)[0] = block[1]; 
680          } else
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          if(swap) {
810          unsigned int sblock_list[blocks];
811          memcpy(sblock_list, block_ptr, blocks * sizeof(unsigned int));
812          SQUASHFS_SWAP_INTS(block_list, sblock_list, blocks);
813          } else {
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 }