]> arthur.barton.de Git - netatalk.git/blob - bin/cnid/cnid2_create.in
Merge remote branch 'sf/branch-allea' into branch-allea
[netatalk.git] / bin / cnid / cnid2_create.in
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
13 use strict;
14
15
16 ### Globals
17
18 my $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
30 my %did_table;
31 my $n_entries;
32 my @output_list;
33
34 # RootInfo values in the new format
35
36 my $ri_cnid    = "00000000";
37 my $ri_dev     = "1122334400000000";
38 my $ri_ino     = "0000000000000000";
39 my $ri_did     = "00000000";
40 my $ri_hexname = "526f6f74496e666f00";  # RootInfo\0
41
42
43 # Current CNID found in didname.db
44 my $current_cnid;
45
46 # Largest CNID from cnid.db, used if we cannot find the current CNID in didname.db 
47 my $max_cnid;
48
49 # Number of bogus/invalid entries found in the DB 
50 my $errors_found;
51
52 # Filenames
53
54 my $didname_dump = "didname.dump";
55 my $cnid_dump    = "cnid.dump";
56 my $cnid2_dump   = "cnid2.dump";
57
58 ### Subroutines
59
60 sub 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
75 sub 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
91 sub 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
123 sub 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
135 sub 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
196 sub 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
205 sub 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
246 sub 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
259 sub 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
303 print "Finding the current CNID from $didname_dump\n";
304 $current_cnid = find_current_cnid();
305 print "Reading in entries from $cnid_dump\n";
306 $max_cnid = create_did_table();
307 print "Found $n_entries entries\n";
308
309 if (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
317 print "Building directory tree according to cnid.db\n";
318 expand_did_table("00000002", $toplevel);
319
320 if ($n_entries > scalar(@output_list)) {
321     print "Looking for unreachable nodes in the directory tree:\n";
322     find_unreachable();
323 }
324
325 print "Looking for duplicate entries\n";
326 find_duplicates();
327
328 print "Creating $cnid2_dump\n";
329 open CNID2, "> $cnid2_dump" or die "Unable to open $cnid2_dump for writing: $!";
330 output_header(*CNID2, "cnid2.db", scalar(@output_list) + 1);
331 foreach 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 }
338 print CNID2 " ", $ri_cnid, "\n";
339 print CNID2 " ", $ri_cnid, $ri_dev, $ri_ino, $current_cnid, $ri_did, $ri_hexname, "\n";
340 print CNID2 "DATA=END\n";
341
342 output_header(*CNID2, "devino.db", scalar(@output_list) + 1);
343 foreach my $entry (@output_list) {
344     print CNID2 " ", "00000000", $entry->{dev}, "00000000", $entry->{ino}, "\n";
345     print CNID2 " ", $entry->{cnid}, "\n";
346 }
347 print CNID2 " ", $ri_dev, $ri_ino, "\n";
348 print CNID2 " ", $ri_cnid, "\n";
349 print CNID2 "DATA=END\n";
350
351 output_header(*CNID2, "didname.db", scalar(@output_list) + 1);
352 foreach my $entry (@output_list) {
353     print CNID2 " ", $entry->{did}, $entry->{hexname}, "\n";
354     print CNID2 " ", $entry->{cnid}, "\n";
355 }
356 print CNID2 " ", $ri_did, $ri_hexname, "\n";
357 print CNID2 " ", $ri_cnid, "\n";
358 print CNID2 "DATA=END\n";
359
360 close CNID2;
361
362 exit 0;