update macos uninstaller
[openafs.git] / src / packaging / MacOS / Uninstall
1 #!/usr/bin/perl -w
2 # real Perl code begins here
3 #
4 # Adapted from Apple's uninstall-devtools.pl (Version 7 for Xcode Tools 1.2)
5 #
6 # BSD License: c.f. <http://www.opensource.org/licenses/bsd-license>
7 #
8
9 use strict;
10 use File::Basename;
11
12 use vars qw ($do_nothing $print_donothing_removals $receipts_dir $verbose $noisy_warnings);
13 use vars qw ($suppress_spin $spin_counter $spin_state $spin_slower_downer);
14 use vars qw (%exception_list $gen_dirs @gen_files @rmfiles @rmdirs @rmpkg);
15
16 #----------------------------------------------------------------------------------------
17
18 $do_nothing = 0;
19 $print_donothing_removals = 1;
20 $verbose = 1;
21 $noisy_warnings = 0;
22
23 # One of rm -rf in this script uses $receipts_dir -- change with care.
24 $receipts_dir = "/Library/Receipts";
25
26 %exception_list = (
27 #  '/usr/bin/aklog', '',
28 );
29
30 $gen_dirs = { };
31 #$gen_dirs->{"/"}->{"Library"}->{"OpenAFS"}->{"Tools"} = {};
32
33 @gen_files = (
34     "/var/db/openafs/etc/cacheinfo",
35     "/var/db/openafs/etc/ThisCell",
36     "/var/db/openafs/etc/config/afsd.options",
37     "/var/db/openafs/etc/CellServDB.save",
38     "/var/db/openafs/etc/CellServDB.master.last",
39     "/var/db/openafs/etc/CellServDB",
40     "/var/db/openafs/etc/config/settings.plist",
41 );
42
43 #----------------------------------------------------------------------------------------
44
45 $| = 1;
46 #if ($do_nothing == 0 && $< != 0)
47 #  {
48 #    die "ERROR: Must be run with root permissions--prefix command with 'sudo'.\n";
49 #  }
50
51 sub main
52   {
53     # commandline args: 
54     #     0: dir of packages to remove
55     #     1: flag indicating whether to keep package receipts
56     #     2: flag indicating whether to supress spin indicator
57
58     if (!@ARGV) {
59         use FindBin qw($Bin);
60         @ARGV = ("$Bin/..", 0, 0);
61     }
62     $suppress_spin = defined ($ARGV[2]) && $ARGV[2];
63
64     $spin_counter = 0;
65     $spin_state = 0;
66     spin_rate_slow ();
67
68     pre_print ();
69     print "Uninstalling OpenAFS package:\n\n";
70
71     remove_generated_files ();
72     remove_main_packages ();
73     remove_generated_directories ();
74
75     if ($do_nothing == 0) {
76     my @rmcmd = ('osascript', '-e', "do shell script \"/bin/rm -f @rmfiles; /bin/rmdir @rmdirs; /bin/rm -rf @rmpkg\" with administrator privileges");
77     system @rmcmd;
78     my $retcode = $? >> 8;
79     if ($retcode != 0) {
80            print_warning ("Warning:  There may have been a problem uninstalling\n");
81     }
82     }
83
84     pre_print ();
85     print "\nFinished uninstalling.\n";
86   }
87
88 sub remove_main_packages
89   {
90 #    opendir (DIR, $ARGV[0]) or die "ERROR: package directory $ARGV[0] not found\n";
91 #    my @pkglist = grep /\.pkg$/, readdir DIR;
92 #    closedir DIR;
93      my @pkglist = ("org.openafs.OpenAFS-debug",
94                     "org.openafs.OpenAFS",
95                     "OpenAFS",
96                     "OpenAFS-debug-extension",
97                     );
98     
99     foreach (@pkglist)
100       {
101         s/\.pkg$//;
102         my $pkgname = $_;
103         my $bompath = undef;
104         my $infopath = undef;
105         my $pkgname_plist_newpath = "/var/db/receipts/$pkgname.pkg.plist";
106             my $pkgname_bom_newpath = "/var/db/receipts/$pkgname.pkg.bom";
107         my $pkgname_plist_fullpath = "/Library/Receipts/$pkgname.pkg/Contents/Info.plist";
108         my $pkgname_bom_fullpath = "/Library/Receipts/$pkgname.pkg/Contents/Archive.bom";
109
110         if (-f $pkgname_plist_newpath && -r $pkgname_plist_newpath && -f $pkgname_bom_newpath && -r $pkgname_bom_newpath) {
111             print_verbose ("Removing package $pkgname.pkg\n");
112             $bompath = $pkgname_bom_newpath;
113             $infopath = $pkgname_plist_newpath;
114         } else {
115             if (-f $pkgname_plist_fullpath && -r $pkgname_plist_fullpath && -f $pkgname_bom_fullpath && -r $pkgname_bom_fullpath) {
116                 print_verbose ("Removing package $pkgname.pkg\n");
117                 $bompath = $pkgname_bom_fullpath;
118                 $infopath = $pkgname_plist_fullpath;
119             } else {
120                 next;
121             }
122         }
123
124         next if (!defined ($bompath));
125         
126         my $bomroot = "";
127         open (INFO, "$infopath") or next;
128         while (<INFO>) {
129             m/IFPkgFlagDefaultLocation/ or next;
130             $bomroot = <INFO>;
131         }
132         close (INFO);
133
134         $bomroot =~ />(.*)</;
135         $bomroot = $1;
136         $bomroot =~ s/^\/$//;
137
138         spin_rate_slow ();
139
140         open (LSBOM, "/usr/bin/lsbom -l -f -p f '$bompath' |") or next;
141         while (<LSBOM>)
142           {
143             chomp;
144             m#^\.(/.*)$#;
145             next if (!defined ($1) || $1 eq "");
146             my $filename = $bomroot . $1;
147
148             remove_a_file ($filename);
149           }
150         close (LSBOM);
151
152         my $rooth = { };
153
154         open (LSBOM, "/usr/bin/lsbom -d -p f '$bompath' |") or next;
155         while (<LSBOM>)
156           {
157             chomp;
158             m#^\.(/.*)$#;
159             next if (!defined ($1) || $1 eq "");
160             my $directory = $bomroot . $1;
161             if (-d $directory)
162               {
163                 $rooth = add_directory_to_tree ($directory, $rooth);
164               }
165             else
166               {
167                 if ($noisy_warnings)
168                   {
169                     print_warning ("Warning: \"$directory\" listed in BOM but not present on system.\n");
170                   }
171               }
172           }
173         close (LSBOM);
174
175         spin_rate_fast ();
176         remove_empty_directories ($rooth, "/");
177
178         if (-d "$receipts_dir/$pkgname.pkg" ) {
179             remove_package_receipts ("$pkgname.pkg") if (!defined ($ARGV[1]) || !$ARGV[1]) ;
180         } else {
181             unshift(@rmfiles, "$bompath");
182             unshift(@rmfiles, "$infopath");
183         }
184       }
185   }
186
187 sub remove_generated_files
188   {
189    foreach (@gen_files)
190      {
191        remove_a_file ($_);
192      }
193   }
194
195 sub remove_generated_directories
196   {
197     remove_empty_directories ($gen_dirs, "/");
198   }
199
200 sub add_directory_to_tree
201   {
202     my $dir = shift;
203     my $rooth = shift;
204     my $p = $rooth;
205
206     my @pathcomp = split /\//, $dir;
207
208     progress_point ();
209     foreach (@pathcomp)
210       {
211         my $cur_name = $_;
212         if ($cur_name eq "" || !defined ($cur_name))
213           {
214             $cur_name = "/";
215           }
216         if (!defined ($p->{"$cur_name"}))
217           {
218             $p->{$cur_name} = { };
219           }
220         $p = $p->{$cur_name};
221       }
222     return $rooth;
223   }
224
225 sub remove_empty_directories
226   {
227     my $rooth = shift;
228     my $path = shift;
229     my $children = (scalar (keys %{$rooth}));
230     my $dirs_remain = 0;
231
232     if ($children > 0)
233       {
234         foreach my $dirname (sort keys %{$rooth})
235           {
236             my $printpath;
237             $printpath = "$path/$dirname";
238             $printpath =~ s#^/*#/#;
239             remove_empty_directories ($rooth->{$dirname}, "$printpath");
240             $dirs_remain = 1 if (-d "$printpath");
241           } 
242       }
243
244      if ($dirs_remain == 0)
245        {
246          maybe_remove_ds_store ("$path");
247        }
248
249      remove_a_dir ("$path");
250   }
251
252 sub remove_a_file
253   {
254     my $fn = shift;
255     my $dirname = dirname ($fn);
256     my $basename = basename ($fn);
257     my $ufs_rsrc_file = "$dirname/._$basename";
258
259     progress_point ();
260     return if (!defined ($fn) || $fn eq "");
261
262     # Leave any files that are shared between packages alone.
263     if (defined($exception_list{$fn}))
264       {
265         if ($noisy_warnings)
266           {
267             print_warning ("Warning: file \"$fn\" intentionally not removed, even though it's in the BOM.\n");
268           }
269         return;
270       }
271
272     if (! -f $fn && ! -l $fn)
273       {
274         if ($noisy_warnings)
275           {
276             print_warning ("Warning: file \"$fn\" present in BOM but not found on disc.\n");
277           }
278         return;
279       }
280
281     if ($do_nothing == 1) 
282       {
283         print_donothing ("rm $fn\n");
284         print_donothing ("rm $ufs_rsrc_file\n") if ( -f $ufs_rsrc_file);
285       }
286     else
287       {
288           unshift(@rmfiles, "$fn");
289           unshift(@rmfiles, "$ufs_rsrc_file") if ( -f $ufs_rsrc_file);
290       }
291   }
292
293 sub remove_a_dir
294   {
295     my $dir = shift;
296
297     progress_point ();
298     return if (!defined ($dir) || $dir eq "" || $dir eq "/" || $dir eq "/usr");
299     if (! -d $dir)
300       {
301         if ($noisy_warnings)
302           {
303             print_warning ("Warning: directory \"$dir\" present in BOM but not found on disc.\n");
304           }
305         return;
306       }
307
308     if ($do_nothing == 1) 
309       {
310         print_donothing ("rmdir $dir\n");
311       }
312     else
313       {
314         push(@rmdirs, "$dir");
315       }
316   }
317
318 sub remove_package_receipts
319   {
320     my $pkgname = shift;
321     $pkgname =~ s#/##g;  # There shouldn't be any path seps in the pkg name...
322     return if (!defined ($pkgname) || $pkgname eq "" 
323                || $pkgname eq "." || $pkgname eq "..");
324
325     my $pkgdir = "$receipts_dir/$pkgname";
326     return if (!defined ($pkgdir) || $pkgdir eq "" || ! -d $pkgdir);
327
328     push(@rmpkg, "$pkgdir");
329   }
330
331
332 sub maybe_remove_ds_store
333   {
334     my $path = shift;
335     my $filecount = 0;
336     return if (!defined ($path) || $path eq "" || $path eq "/" || $path eq "/usr");
337     return if (! -f "$path/.DS_Store");
338
339     open (LS, "/bin/ls -a '$path' |");
340     while (<LS>)
341       {
342         chomp;
343         next if (m#^\.$# || m#^\.\.$#);
344         $filecount++;
345       }
346     close (LS);
347
348     if ($filecount == 1 && -f "$path/.DS_Store")
349       {
350         remove_a_file ("$path/.DS_Store");
351       }
352   }
353
354 sub print_donothing
355   {
356     my $msg = shift;
357     return if ($print_donothing_removals != 1);
358     pre_print ();
359     print $msg;
360   }
361
362 sub print_verbose
363   {
364     my $msg = shift;
365     return if ($verbose != 1);
366     pre_print ();
367     print $msg;
368   }
369
370 sub print_warning
371   {
372     my $msg = shift;
373     pre_print ();
374     print STDERR $msg;
375   }
376
377 sub print_error
378   {
379     my $msg = shift;
380     pre_print ();
381     print STDERR $msg;
382   }
383
384 sub pre_print 
385   {
386     print " \b" unless ($suppress_spin);
387   }
388
389 sub spin_rate_slow
390   {
391     $spin_slower_downer = 150;
392   }
393
394 sub spin_rate_fast
395   {
396     $spin_slower_downer = 75;
397   }
398
399 sub progress_point
400   {
401     return if ($suppress_spin);
402     $spin_counter++;
403     if (($spin_counter % $spin_slower_downer) == 0)
404       {
405         my $spin_chars = "|/-\\";
406         my $c = substr ($spin_chars, $spin_state % 4, 1);
407         $spin_state++;
408         print "\e[7m$c\e[m\b";
409       }
410   }
411
412 main ();
413
414 #----------------------------------------------------------------------------------------