| 1 | require 'fileutils' |
|---|
| 2 | |
|---|
| 3 | # TODO when working with an image created with ntfs-3g one can not count |
|---|
| 4 | # on the file or directory names being present. |
|---|
| 5 | |
|---|
| 6 | deps = { |
|---|
| 7 | 'ils' => false, |
|---|
| 8 | 'fsstat' => false, |
|---|
| 9 | 'ifind' => false, |
|---|
| 10 | 'icat' => false, |
|---|
| 11 | 'istat' => false |
|---|
| 12 | } |
|---|
| 13 | |
|---|
| 14 | deps.each_key do |dep| |
|---|
| 15 | ENV['PATH'].split( ':' ).each do |path| |
|---|
| 16 | begin |
|---|
| 17 | if File.stat( "#{path}/#{dep}" ) |
|---|
| 18 | deps[dep] = true |
|---|
| 19 | break |
|---|
| 20 | end |
|---|
| 21 | rescue |
|---|
| 22 | end |
|---|
| 23 | end |
|---|
| 24 | end |
|---|
| 25 | |
|---|
| 26 | deps.each_value do |value| |
|---|
| 27 | raise Exception if not value |
|---|
| 28 | end |
|---|
| 29 | |
|---|
| 30 | module Sleuth |
|---|
| 31 | $haystack = String.new |
|---|
| 32 | |
|---|
| 33 | class Image |
|---|
| 34 | def initialize( image ) |
|---|
| 35 | begin |
|---|
| 36 | raise Exception if not File.stat( image ).readable? |
|---|
| 37 | rescue |
|---|
| 38 | raise Exception |
|---|
| 39 | end |
|---|
| 40 | |
|---|
| 41 | $haystack = image |
|---|
| 42 | @type = "" |
|---|
| 43 | end |
|---|
| 44 | |
|---|
| 45 | # TODO search is crappy |
|---|
| 46 | # Inode has to be expanded and Directory should inherit from it |
|---|
| 47 | def searchDir( needle ) |
|---|
| 48 | matches = [] |
|---|
| 49 | |
|---|
| 50 | `ils -Am #{$haystack} | grep '#{needle}'`.split( "\n" ).each do |match| |
|---|
| 51 | matches.push( Directory.new( match ) ) |
|---|
| 52 | matches.delete_if do |i| i.type != 100 end |
|---|
| 53 | end |
|---|
| 54 | |
|---|
| 55 | matches |
|---|
| 56 | end |
|---|
| 57 | |
|---|
| 58 | def type |
|---|
| 59 | if @type.empty? |
|---|
| 60 | @type = `fsstat #{$haystack} | grep Type`.split( ' ' )[3].downcase |
|---|
| 61 | end |
|---|
| 62 | |
|---|
| 63 | @type |
|---|
| 64 | end |
|---|
| 65 | end |
|---|
| 66 | |
|---|
| 67 | class Directory |
|---|
| 68 | attr_reader :name, :inode, :mode, :type, :atime |
|---|
| 69 | |
|---|
| 70 | def initialize( line ) |
|---|
| 71 | tmp = line.split('|') |
|---|
| 72 | |
|---|
| 73 | @name = tmp[1] |
|---|
| 74 | @inode = Inode.new( tmp[2].to_i ) |
|---|
| 75 | @mode = tmp[3] |
|---|
| 76 | @type = @mode[2] |
|---|
| 77 | @atime = Time.at( tmp[7].to_i ) |
|---|
| 78 | |
|---|
| 79 | basename = $haystack.gsub( /^\/.+\//, "" ) |
|---|
| 80 | @name.gsub!( "<#{basename}-", "" ) |
|---|
| 81 | @name.gsub!( "-dead-#{@inode}>", "" ) |
|---|
| 82 | |
|---|
| 83 | @path = "" |
|---|
| 84 | end |
|---|
| 85 | |
|---|
| 86 | def to_s |
|---|
| 87 | "#{name} (#{atime.asctime}) #{inode}" |
|---|
| 88 | end |
|---|
| 89 | |
|---|
| 90 | def path |
|---|
| 91 | @path = inode.path if @path.empty? |
|---|
| 92 | @path |
|---|
| 93 | end |
|---|
| 94 | |
|---|
| 95 | def recover( *args ) |
|---|
| 96 | if args.empty? |
|---|
| 97 | inode = Inode.new( self.inode ) |
|---|
| 98 | else |
|---|
| 99 | inode = Inode.new( args[0] ) |
|---|
| 100 | end |
|---|
| 101 | |
|---|
| 102 | FileUtils.mkdir_p( self.path ) |
|---|
| 103 | `ifind -l -p #{inode} #{$haystack}`.each do |item| |
|---|
| 104 | inode = Inode.new( item.split( " " )[2].to_i ) |
|---|
| 105 | if item[2] == 100 |
|---|
| 106 | FileUtils.mkdir_p( inode.path ) |
|---|
| 107 | recover( inode ) |
|---|
| 108 | else |
|---|
| 109 | # TODO would be cool to have sth like this: |
|---|
| 110 | # d.recover do |item| puts item.path end |
|---|
| 111 | # print( "#{inode.path}\n" ) |
|---|
| 112 | yield |
|---|
| 113 | begin |
|---|
| 114 | File.stat( inode.path ) |
|---|
| 115 | rescue |
|---|
| 116 | `icat #{$haystack} #{inode} > '#{inode.path}'` |
|---|
| 117 | end |
|---|
| 118 | end |
|---|
| 119 | # TODO dir mtime is not written |
|---|
| 120 | # (although it works in tests with irb) |
|---|
| 121 | FileUtils.touch( inode.path, :mtime => inode.mtime ) |
|---|
| 122 | end |
|---|
| 123 | end |
|---|
| 124 | end |
|---|
| 125 | |
|---|
| 126 | class Inode |
|---|
| 127 | attr_reader :inode |
|---|
| 128 | |
|---|
| 129 | def initialize( inode ) |
|---|
| 130 | @inode = inode |
|---|
| 131 | @path = "" |
|---|
| 132 | @mtime = 0 |
|---|
| 133 | end |
|---|
| 134 | |
|---|
| 135 | def to_s |
|---|
| 136 | @inode.to_s |
|---|
| 137 | end |
|---|
| 138 | |
|---|
| 139 | # TODO do not recover whole path for every file |
|---|
| 140 | def path |
|---|
| 141 | if @path.empty? |
|---|
| 142 | name = String.new |
|---|
| 143 | parent = inode |
|---|
| 144 | tmp = Array.new |
|---|
| 145 | |
|---|
| 146 | while name != "." |
|---|
| 147 | tmp.push( name ) if not name.empty? |
|---|
| 148 | out = `istat #{$haystack} #{parent} | egrep "^Parent|^Name"`.split( "\n" ) |
|---|
| 149 | name = out[0][6..-1] |
|---|
| 150 | parent = out[1].gsub( /^Par.*ry:\ /, "" ).to_i |
|---|
| 151 | end |
|---|
| 152 | |
|---|
| 153 | @path = tmp.reverse.join( "/" ) |
|---|
| 154 | end |
|---|
| 155 | |
|---|
| 156 | @path |
|---|
| 157 | end |
|---|
| 158 | |
|---|
| 159 | def mtime |
|---|
| 160 | if @mtime == 0 |
|---|
| 161 | out = `istat #{$haystack} #{@inode} | grep 'File Modified'` |
|---|
| 162 | out = out.split( "\t" ).last.chomp |
|---|
| 163 | @mtime = Time.mktime( |
|---|
| 164 | out[20..23].to_i, # year |
|---|
| 165 | out[4..6], # month |
|---|
| 166 | out[8..9].to_i, # day |
|---|
| 167 | out[11..12].to_i, # hour |
|---|
| 168 | out[14..15].to_i, # min |
|---|
| 169 | out[17..18].to_i #sec |
|---|
| 170 | ) |
|---|
| 171 | end |
|---|
| 172 | |
|---|
| 173 | @mtime |
|---|
| 174 | end |
|---|
| 175 | end |
|---|
| 176 | end |
|---|