1#!@PERL@ 2 3# 4# Upgrade version 1 CNID databases to version 2 5# 6# 7# Copyright (C) Joerg Lenneis 2003 8# All Rights Reserved. See COPYING. 9# 10# 11 12use strict; 13 14 15### Globals 16 17my $toplevel = $ARGV[0]; 18 19# Assume our current directory is .AppleDB in the share directory as the default. 20 21$toplevel = ".." unless $toplevel; 22 23# Main file information data structure. Each entry in did_table points 24# to a list of hashes. Key is a DID and each list member is a hash 25# describing a file or directory in the directory corresponding to the 26# DID. n_entries is the number of items found, output_list contains 27# all entries that are eventually written to the output file. 28 29my %did_table; 30my $n_entries; 31my @output_list; 32 33# RootInfo values in the new format 34 35my $ri_cnid = "00000000"; 36my $ri_dev = "1122334400000000"; 37my $ri_ino = "0000000000000000"; 38my $ri_did = "00000000"; 39my $ri_hexname = "526f6f74496e666f00"; # RootInfo\0 40 41 42# Current CNID found in didname.db 43my $current_cnid; 44 45# Largest CNID from cnid.db, used if we cannot find the current CNID in didname.db 46my $max_cnid; 47 48# Number of bogus/invalid entries found in the DB 49my $errors_found; 50 51# Filenames 52 53my $didname_dump = "didname.dump"; 54my $cnid_dump = "cnid.dump"; 55my $cnid2_dump = "cnid2.dump"; 56 57### Subroutines 58 59sub skip_header($) 60{ 61 my $handle = shift; 62 my $header_ok; 63 64 while (<$handle>) { 65 chomp; 66 if ($_ eq "HEADER=END") { 67 $header_ok = 1; 68 last; 69 } 70 } 71 die "No valid header found, invalid input file?" unless $header_ok; 72} 73 74sub print_eentry($$) 75{ 76 my $reason = shift; 77 my $entry = shift; 78 79 printf "??:%s %-9s%-9s%-9s%-9s%s\n", 80 $reason, 81 $entry->{cnid}, 82 $entry->{dev}, 83 $entry->{ino}, 84 $entry->{did}, 85 $entry->{name}; 86 87 $errors_found++; 88} 89 90sub find_current_cnid() 91{ 92 my $key; 93 my $val; 94 my $cnid; 95 my $compare = " " . $ri_did . $ri_hexname; 96 97 # get rid of "00" at the end, RootInfo in the old format does not have a trailing \0 98 99 $compare =~ s/00$//; 100 101 open DIDNAME, "< $didname_dump" or die "Unable to open $didname_dump: $!"; 102 skip_header(*DIDNAME); 103 104 while (1) { 105 $key = <DIDNAME>; 106 chomp $key; 107 last if $key eq "DATA=END"; 108 $val = <DIDNAME>; 109 chomp $val; 110 last unless defined($key) and defined($val); 111 if ($key eq $compare) { 112 # \00\00\00\00RootInfo 113 $val =~ s/^ //; 114 $cnid = $val; 115 last; 116 } 117 } 118 close DIDNAME; 119 return $cnid; 120} 121 122sub verify_entry($) 123{ 124 my $entry = shift; 125 126 if (length($entry->{cnid}) != 8 or length($entry->{name}) == 0) { 127 print_eentry("fmt", $entry); 128 return 0; 129 } else { 130 return 1; 131 } 132} 133 134sub create_did_table() 135{ 136 my $data_ok; 137 my $key; 138 my $val; 139 my $len; 140 my $i; 141 my $name; 142 my $cmax; 143 144 open CNID, "< $cnid_dump" or die "Unable to open $cnid_dump: $!"; 145 skip_header(*CNID); 146 147 while (1) { 148 $key = <CNID>; 149 chomp $key; 150 $key =~ s/^ //; 151 if ($key eq "DATA=END") { 152 $data_ok = 1; 153 last; 154 } 155 my $val = <CNID>; 156 chomp $val; 157 $val =~ s/^ //; 158 159 last unless defined($key) and defined($val); 160 161 # We do not worry about converting any of the 162 # integer values into binary form. They are in network byte order, 163 # so we know how to extend them. We just treat them as hexadecimal ASCII strings. 164 # The file name is also stored as a proper string, since we need to 165 # look for it in the file system. 166 167 my $entry; 168 169 $entry->{cnid} = $key; 170 $entry->{dev} = substr($val, 0, 8); 171 $entry->{ino} = substr($val, 8, 8); 172 $entry->{did} = substr($val, 16, 8); 173 $entry->{hexname} = substr($val, 24); 174 175 $len = length($entry->{hexname}) - 2; 176 $i = 0; 177 $name = ''; 178 while ($i < $len) { 179 $name .= chr(hex(substr($entry->{hexname}, $i, 2))); 180 $i += 2; 181 } 182 $entry->{name} = $name; 183 184 if (verify_entry($entry)) { 185 push @{$did_table{$entry->{did}}}, $entry; 186 $n_entries++; 187 $cmax = $entry->{cnid} if $entry->{cnid} gt $cmax; 188 } 189 } 190 close CNID; 191 die "No valid end of data found, invalid input file?" unless $data_ok; 192 return $cmax; 193} 194 195sub output_header($$$) 196{ 197 my $handle = shift; 198 my $dbname = shift; 199 my $n = shift; 200 201 printf $handle "VERSION=3\nformat=bytevalue\ndatabase=%s\ntype=btree\nHEADER=END\n", $dbname; 202} 203 204sub expand_did_table($$) 205{ 206 my $did = shift; 207 my $basename = shift; 208 my $entry; 209 my $name; 210 211 foreach $entry (@{$did_table{$did}}) { 212 $name = $basename . "/" . $entry->{name}; 213 214 if ( -e $name ) { 215 if ( -d $name ) { 216 $entry->{type} = "00000001"; 217 } else { 218 $entry->{type} = "00000000"; 219 } 220 } else { 221 222 # The file/dir does not exist in the file system. This could result 223 # from a non-afpd rename that has not yet been picked up by afpd and is 224 # fixable. We need a guess at the type, though. 225 226 if ($did_table{$entry->{cnid}} and scalar(@{$did_table{$entry->{cnid}}}) > 0) { 227 228 # We have entries hanging off this entry in our table, 229 # so this must be a directory 230 $entry->{type} = "00000001"; 231 } else { 232 # If this is actually an empty directory that was renamed, 233 # the entry will be deleted by afpd on the next access, 234 # so this should be safe 235 $entry->{type} = "00000000"; 236 } 237 } 238 239 $entry->{reached} = 1; 240 push @output_list, $entry; 241 expand_did_table($entry->{cnid}, $name); 242 } 243} 244 245sub find_unreachable() 246{ 247 my $did; 248 my $list; 249 my $entry; 250 251 while (($did, $list) = each %did_table) { 252 foreach $entry (@{$list}) { 253 print_eentry("reach", $entry) unless $entry->{reached}; 254 } 255 } 256} 257 258sub find_duplicates() 259{ 260 my %seen_cnid; 261 my %seen_devino; 262 my %seen_didname; 263 my $cnid; 264 my $devino; 265 my $didname; 266 my $err; 267 my $entry; 268 269 for (my $i = 0; $i < scalar(@output_list); $i++) { 270 $err = 0; 271 $entry = $output_list[$i]; 272 $cnid = $entry->{cnid}; 273 $devino = $entry->{dev} . $entry->{ino}; 274 $didname = $entry->{did} . $entry->{name}; 275 if ($seen_cnid{$cnid}) { 276 print_eentry("exist_cnid", $entry); 277 $err++; 278 } 279 if ($seen_cnid{$cnid}) { 280 print_eentry("dupl_cnid", $entry); 281 $err++; 282 } 283 if ($seen_devino{$devino}) { 284 print_eentry("dupl_devino", $entry); 285 $err++; 286 } 287 if ($seen_didname{$didname}) { 288 print_eentry("dupl_didname", $entry); 289 $err++; 290 } 291 if ($err) { 292 splice(@output_list, $i, 1); 293 } else { 294 $seen_cnid{$cnid} = 1; 295 $seen_devino{$devino} = 1; 296 $seen_didname{$didname} = 1; 297 } 298 } 299} 300 301 302print "Finding the current CNID from $didname_dump\n"; 303$current_cnid = find_current_cnid(); 304print "Reading in entries from $cnid_dump\n"; 305$max_cnid = create_did_table(); 306print "Found $n_entries entries\n"; 307 308if (not $current_cnid) { 309 print "Warning: could not find a valid entry for the current CNID in $didname_dump.\n"; 310 print "Using maximum CNID value $max_cnid from $cnid_dump instead\n"; 311 $current_cnid = $max_cnid; 312} else { 313 print "Current CNID is $current_cnid\n"; 314} 315 316print "Building directory tree according to cnid.db\n"; 317expand_did_table("00000002", $toplevel); 318 319if ($n_entries > scalar(@output_list)) { 320 print "Looking for unreachable nodes in the directory tree:\n"; 321 find_unreachable(); 322} 323 324print "Looking for duplicate entries\n"; 325find_duplicates(); 326 327print "Creating $cnid2_dump\n"; 328open CNID2, "> $cnid2_dump" or die "Unable to open $cnid2_dump for writing: $!"; 329output_header(*CNID2, "cnid2.db", scalar(@output_list) + 1); 330foreach my $entry (@output_list) { 331 print CNID2 " ", $entry->{cnid}, "\n"; 332 print CNID2 " ", $entry->{cnid}, 333 "00000000", $entry->{dev}, "00000000", $entry->{ino}, 334 $entry->{type}, 335 $entry->{did}, $entry->{hexname}, "\n"; 336} 337print CNID2 " ", $ri_cnid, "\n"; 338print CNID2 " ", $ri_cnid, $ri_dev, $ri_ino, $current_cnid, $ri_did, $ri_hexname, "\n"; 339print CNID2 "DATA=END\n"; 340 341output_header(*CNID2, "devino.db", scalar(@output_list) + 1); 342foreach my $entry (@output_list) { 343 print CNID2 " ", "00000000", $entry->{dev}, "00000000", $entry->{ino}, "\n"; 344 print CNID2 " ", $entry->{cnid}, "\n"; 345} 346print CNID2 " ", $ri_dev, $ri_ino, "\n"; 347print CNID2 " ", $ri_cnid, "\n"; 348print CNID2 "DATA=END\n"; 349 350output_header(*CNID2, "didname.db", scalar(@output_list) + 1); 351foreach my $entry (@output_list) { 352 print CNID2 " ", $entry->{did}, $entry->{hexname}, "\n"; 353 print CNID2 " ", $entry->{cnid}, "\n"; 354} 355print CNID2 " ", $ri_did, $ri_hexname, "\n"; 356print CNID2 " ", $ri_cnid, "\n"; 357print CNID2 "DATA=END\n"; 358 359close CNID2; 360 361exit 0; 362