#!/usr/bin/perl # Nasty script for doing various stuff with UFS filesystems. Argument # order is important. # # Usage: # ufs.pl [-d] [-x] Recursively list inode information, # block numbers etc. # ufs.pl -i [-d] [-x] List all inodes in order with block # numbers and type information. # ufs.pl -s [-d] [-x] Recursively list inode information # beginning at the specified inode. # # -d Output file data to stdout. # -x Extract each listed regular file to the # current working directory. In recursive # mode, subdirectories are created to match # the hierarchy in the filesystem. $dosub = 1; $startinode = 2; $startname = '/'; $showdata = 0; $extract = 0; if ($ARGV[0] eq "-i") { $inodemode = 1; $dosub = 0; shift; } if ($ARGV[0] eq "-s") { shift; $startinode = shift; $startname = "inode_$startinode"; } if ($ARGV[0] eq "-d") { shift; $showdata = 1; } if ($ARGV[0] eq "-x") { shift; $extract = 1; } $fsdev = shift; die "No device specified\n" unless defined($fsdev); open(FS, $fsdev) || die "$fsdev: $!\n"; sub myseek { my($fh, $bn) = @_; my($ret); $how = 0; while ($bn > 2097152) { sysseek($fh, 2097152 * 512, $how); $how = 1; $bn -= 2097152; } sysseek($fh, $bn * 512, $how); return 1; } sub fsread { my($bn, $offset, $len) = @_; # print "fsread bn=$bn off=$offset len=$len\n"; if ($bn > $sb{'fs_size'} || $bn < 0) { die "fsread: eek, bad block number $bn\n"; } $bn *= ($sb{'fs_fsize'}/512); my $bn1 = int($offset/512); $bn += $bn1; $offset -= $bn1 * 512; myseek(FS, $bn) || die "myseek($bn): $!\n"; my($data); my($readlen) = ($len + 511) & ~511; # print "fsread read bn=$bn off=$offset len=$readlen\n"; die "read $len\@sector $bn: $!\n" unless (sysread(FS, $data, $readlen) == $readlen); return substr($data, $offset, $len); } sub fsread1 { my($offset, $len) = @_; my($bn) = int($offset/512); my($bo) = $offset - ($bn * 512); # print "read $len\@$offset [$bn+$bo]\n"; if (($offset & 0x1ff) || $len < 512) { myseek(FS, $bn) || die "seek($offset): $!\n"; my($data); die "readsmall $len\@$offset: $!\n" unless (sysread(FS, $data, 512) == 512); return substr($data, $offset & 0x1ff, $len); } myseek(FS, $bn) || die "seek($offset): $!\n"; my($data); die "read $len\@$offset: $!\n" unless (sysread(FS, $data, $len) == $len); return $data; } sub writeout { my ($dat, $name) = @_; my $dir; $name =~ s/^\/+//; $dir = $name; $dir =~ s/\/[^\/]+$//; if (! -d $dir) { print "making '$dir'\n"; system "mkdir -p '$dir'"; } open(OUT, "> $name") || die "$name: $!\n"; print OUT $dat; close(OUT); } sub tunpack { my($data, @spec) = @_; my($spec, $pstr, @pres, %res); for $spec (@spec) { my $s = $spec; my($cnt) = ""; $cnt = $1 if ($s =~ s/^([\d]+)//); $s =~ s/:.*//; $s = 'L2' if ($s eq 'Q' || $s eq 'q'); $pstr .= "$s$cnt"; } @pres = unpack($pstr, $data); for $spec (@spec) { my($cnt, $s, $name) = ($spec =~ /^([\d]*)([^:]+):(.*)/); $cnt = 1 if ($cnt == 0); $cnt *= 2 if ($s eq 'Q' || $s eq 'q'); my(@res1) = splice(@pres, 0, $cnt); if ($s eq 'Q' || $s eq 'q') { my($i); for($i = 0; $i<@res1; $i++) { splice(@res1, $i, 2, $res1[0] * 1.0 + $res1[1] * 65536.0 * 65536.0); } $cnt /= 2; } $res1[0] =~ s/\0[\0-\xff]*// if ($s =~ /^a\d+$/); if ($cnt == 1) { $res{$name} = $res1[0]; } else { $res{$name} = \@res1; } # print "$name = $res{$name}\n"; } return %res; } $sb = fsread(0, 0x2000, 0x2000); @sbfields = qw(l:fs_firstfield l:fs_unused_1 l:fs_sblkno l:fs_cblkno l:fs_iblkno l:fs_dblkno l:fs_cgoffset l:fs_cgmask l:fs_time l:fs_size l:fs_dsize l:fs_ncg l:fs_bsize l:fs_fsize l:fs_frag l:fs_minfree l:fs_rotdelay l:fs_rps l:fs_bmask l:fs_fmask l:fs_bshift l:fs_fshift l:fs_maxcontig l:fs_maxbpg l:fs_fragshift l:fs_fsbtodb l:fs_sbsize l:fs_csmask l:fs_csshift l:fs_nindir l:fs_inopb l:fs_nspf l:fs_optim l:fs_npsect l:fs_interleave l:fs_trackskew l:fs_id1 l:fs_id2 l:fs_csaddr l:fs_cssize l:fs_cgsize l:fs_ntrak l:fs_nsect l:fs_spc l:fs_ncyl l:fs_cpg l:fs_ipg l:fs_fpg l:cs_ndir l:cs_nbfree l:cs_nifree l:cs_nffree c:fs_fmod c:fs_clean c:fs_ronly c:fs_flags a512:fs_fsmnt l:fs_cgrotor 32l:fs_csp l:fs_cpc 128s:fs_opostbl 50l:fs_sparecon l:fs_contigsumsize l:fs_maxsymlinklen l:fs_inodefmt Q:fs_maxfilesize q:fs_qbmask q:fs_qfmask l:fs_state l:fs_postblformat l:fs_nrpos l:fs_postbloff l:fs_rotbloff l:fs_magic); %sb = tunpack($sb, @sbfields); sub cgbase {return $sb{'fs_fpg'} * shift;} sub cgstart { my($c) = shift; return cgbase($c) + $sb{'fs_cgoffset'} * ($c & ~$sb{'fs_cgmask'}); } sub cgimin {return cgstart(shift) + $sb{'fs_iblkno'};} sub ino_to_cg {return int(shift() / $sb{'fs_ipg'});} sub blkstofrags {return shift() << $sb{'fs_fragshift'};} sub ino_to_fsba { my($x) = shift; return cgimin(ino_to_cg($x)) + blkstofrags(int(($x % $sb{'fs_ipg'}) / $sb{'fs_inopb'})) } sub ino_to_fsbo {return shift() % $sb{'fs_inopb'};} sub rdinode { my($i) = shift; if ($i < 0 || $i > $sb{'fs_ipg'} * $sb{'fs_ncg'}) { print "rdinode: bad inode $i\n"; return undef; } return fsread(ino_to_fsba($i), ino_to_fsbo($i) * 128, 128); } @ifields = qw(S:di_mode s:di_nlink l:lfs_inumber Q:di_size l:di_atime l:di_atimensec l:di_mtime l:di_mtimensec l:di_ctime l:di_ctimensec 12l:di_db 3l:di_ib L:di_flags l:di_blocks l:di_gen L:di_uid L:di_gid); %ITYP = ( 4 => 'd', 8 => '-', 2 => 'c', 6 => 'b', 10 => 'l' ); sub prinode { my($inum, $nam, $dosub) = @_; my $idat = rdinode($inum); return unless defined($idat); my(%idat) = tunpack($idat, @ifields); my $typ = ($idat{'di_mode'} & 0170000) >> 12; my $mode = $idat{'di_mode'} & 07777; my $ityp = $ITYP{$typ} || '?'; my $m = $ityp; $m .= ($mode & 00400) ? 'r' : '-'; $m .= ($mode & 00200) ? 'w' : '-'; $m .= ($mode & 00100) ? ($mode & 04000) ? 's' : 'x' : ($mode & 04000) ? 'S' : '-'; $m .= ($mode & 00040) ? 'r' : '-'; $m .= ($mode & 00020) ? 'w' : '-'; $m .= ($mode & 00010) ? ($mode & 02000) ? 's' : 'x' : ($mode & 02000) ? 'S' : '-'; $m .= ($mode & 00004) ? 'r' : '-'; $m .= ($mode & 00002) ? 'w' : '-'; $m .= ($mode & 00001) ? ($mode & 01000) ? 't' : 'x' : ($mode & 01000) ? 'T' : '-'; printf("%-5d %s %3d %5d %5d %6d %s %s\n", $inum, $m, $idat{'di_nlink'}, $idat{'di_uid'}, $idat{'di_gid'}, $idat{'di_size'}, scalar(localtime($idat{'di_mtime'})), $nam); if ($ityp eq 'l' && $idat{'di_size'} < (15*4)) { printf("-> '%s'\n", substr($idat, 40, $idat{'di_size'})); next; } my $dat; my(@blocks) = iblocks(\%idat); print "blocks: " . join(" ", @blocks) . "\n"; if ($showdata || $extract || $ityp eq 'd') { my($b, $bn, $lbn); foreach $b (@blocks) { ($lbn, $bn) = split(":", $b); next if ($bn =~ /^BAD/); # print "read lbn $lbn block $bn\n"; substr($dat, $lbn * $sb{'fs_bsize'}, $sb{'fs_bsize'}) = fsread($bn, 0, $sb{'fs_bsize'}); } } $dat = substr($dat, 0, $idat{'di_size'}); if ($ITYP{$typ} eq 'd') { prdir($dat, $nam, $dosub); } elsif ($ITYP{$typ} eq '-') { print "file contents: '$dat'\n" if ($showdata); writeout($dat, $nam) if ($extract); } } sub blocks { my($bn, $lbn, $level) = @_; my(@blocks); return ("$lbn:BAD;block=$bn") if ($bn <= 0 || $bn > $sb{'fs_size'}); return ("$lbn:$bn") if ($level == 0); my $bdat = fsread($bn, 0, $sb{'fs_bsize'}); for $bn (unpack("L*", $bdat)) { push(@blocks, blocks($bn, $lbn, $level - 1)) if ($bn != 0); $lbn += ($sb{'fs_bsize'} / 4) ** ($level - 1); } return @blocks; } sub iblocks { my($idat) = @_; my($i, $n); my(@blocks); my $lbn = 0; for ($i = 0; $i < 12; $i++) { my $bn = $idat->{'di_db'}->[$i]; push(@blocks, blocks($bn, $lbn, 0)) if ($bn != 0); $lbn++; } for ($i = 0; $i < 3; $i++) { my $bn = $idat->{'di_ib'}->[$i]; push(@blocks, blocks($bn, $lbn, $i + 1)) if ($bn != 0); $lbn += ($sb{'fs_bsize'} / 4) ** ($i + 1); } return @blocks; } sub prdir { my($dat, $nam, $dosub) = @_; my(%dir); my($off); $off = 0; while (length($dat) > 0) { ($dir{'d_ino'}, $dir{'d_reclen'}, $dir{'d_type'}, $dir{'d_namlen'}) = unpack("LSCC", $dat); $dir{'d_name'} = substr($dat, 8, $dir{'d_namlen'}); if ($dir{'d_reclen'} == 0 || $dir{'d_reclen'} > (($off + 512) & ~511) - $pos) { print "bad entry at offset $off\n"; $dir{'d_reclen'} = ($off + 512) & ~511; $off += $dir{'d_reclen'}; $dat = substr($dat, $dir{'d_reclen'}); next; } $off += $dir{'d_reclen'}; $dat = substr($dat, $dir{'d_reclen'}); next if ($dir{'d_ino'} == 0); printf("ino %d reclen 0x%x type 0%o namelen %d name '%s'\n", $dir{'d_ino'}, $dir{'d_reclen'}, $dir{'d_type'}, $dir{'d_namlen'}, $dir{'d_name'}); if ($dir{'d_name'} ne '.' && $dir{'d_name'} ne '..' && $dosub) { prinode($dir{'d_ino'}, ($nam eq "/" ? "" : $nam)."/".$dir{'d_name'}, $dosub); } } print "----- end of $nam -----\n"; } if ($inodemode) { for ($i = $startinode; $i < $sb{'fs_ipg'} * $sb{'fs_ncg'}; $i++) { my $offset = ino_to_fsba($i) * $sb{'fs_fsize'} + ino_to_fsbo($i) * 128; my($bn) = int($offset/512); my($bo) = $offset - ($bn * 512); print "inode $i: $offset [$bn+$bo]\n"; prinode($i, "inode_$i", $dosub); } } else { prinode($startinode, $startname, $dosub); }