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