1#!@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