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