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