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