1 #!/usr/perl5/bin/perl 2 # 3 # CDDL HEADER START 4 # 5 # The contents of this file are subject to the terms of the 6 # Common Development and Distribution License (the "License"). 7 # You may not use this file except in compliance with the License. 8 # 9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 # or http://www.opensolaris.org/os/licensing. 11 # See the License for the specific language governing permissions 12 # and limitations under the License. 13 # 14 # When distributing Covered Code, include this CDDL HEADER in each 15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 # If applicable, add the following below this CDDL HEADER, with the 17 # fields enclosed by brackets "[]" replaced with your own identifying 18 # information: Portions Copyright [yyyy] [name of copyright owner] 19 # 20 # CDDL HEADER END 21 # 22 # Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 # Use is subject to license terms. 24 # 25 #ident "@(#)pkgtemplate.pl 1.1 08/03/26 SMI" 26 # 27 28 use Getopt::Long; 29 use Cwd; 30 use File::Find; 31 use Fcntl ':mode'; 32 33 *name = *File::Find::name; 34 35 my ($pkg_pkg, $pkg_name, $pkg_description, $pkginfo, $prototype, $basedir) = ( 36 'SUNWxxx', 'package name goes here', 'description goes here', 37 'pkginfo', 'prototype', '/'); 38 my (@proto_paths, %contents, $ignore_contents_file) = (); 39 40 my $proto = $ENV{'ROOT'}; 41 42 $| = 1; 43 44 sub load_contents_file { 45 my ($lineno, $line) = (); 46 47 open(CFD, "</var/sadm/install/contents"); 48 49 print("loading contents file"); 50 51 while ($line = <CFD>) { 52 my ($entry) = (); 53 54 (($lineno++ % 5000) == 0) && print("."); 55 56 if ($line =~ /^#/) { 57 next; 58 } 59 60 chop($line); 61 62 if ($line =~ m{/([^=]+)=([^\s]+) ([ls]) ([^\s]+) (.*)}) { 63 $entry->{path} = $1; 64 $entry->{rpath} = $2; 65 $entry->{type} = $3; 66 $entry->{class} = $4; 67 $entry->{pkgs} = split(/ /, $5); 68 } elsif ($line =~ m{/([^\s]+) ([dx]) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) (.*)}) { 69 $entry->{path} = $1; 70 $entry->{type} = $2; 71 $entry->{class} = $3; 72 $entry->{mode} = oct($4); 73 $entry->{owner} = $5; 74 $entry->{group} = $6; 75 $entry->{pkgs} = split(/ /, $7); 76 } elsif ($line =~ m{/([^\s]+) ([efv]) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) (.*)}) { 77 $entry->{path} = $1; 78 $entry->{type} = $2; 79 $entry->{class} = $3; 80 $entry->{mode} = oct($4); 81 $entry->{owner} = $5; 82 $entry->{group} = $6; 83 $entry->{size} = $7; 84 $entry->{sum} = $8; 85 $entry->{modtime} = $9; 86 $entry->{pkgs} = split(/ /, $10); 87 } elsif ($line =~ m{/([^\s]+) ([bc]) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) (.*)}) { 88 $entry->{path} = $1; 89 $entry->{type} = $2; 90 $entry->{class} = $3; 91 $entry->{major} = $4; 92 $entry->{minor} = $5; 93 $entry->{mode} = oct($6); 94 $entry->{owner} = $7; 95 $entry->{group} = $8; 96 $entry->{pkgs} = split(/ /, $9); 97 } else { 98 print("unrecognized: $line\n"); 99 next; 100 } 101 102 $contents{$entry->{path}} = $entry; 103 } 104 print("loaded\n"); 105 } 106 107 sub find_proto_paths { 108 ($name eq '.') && next; 109 ($name =~ /\.\/(.*)/) && ($name = $1); 110 push(@proto_paths, $name); 111 } 112 113 sub file_header { 114 local ($FH) = @_; 115 116 my $year = (localtime)[5] + 1900; 117 print $FH 118 "# 119 # CDDL HEADER START 120 # 121 # The contents of this file are subject to the terms of the 122 # Common Development and Distribution License (the \"License\"). 123 # You may not use this file except in compliance with the License. 124 # 125 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 126 # or http://www.opensolaris.org/os/licensing. 127 # See the License for the specific language governing permissions 128 # and limitations under the License. 129 # 130 # When distributing Covered Code, include this CDDL HEADER in each 131 # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 132 # If applicable, add the following below this CDDL HEADER, with the 133 # fields enclosed by brackets \"[]\" replaced with your own identifying 134 # information: Portions Copyright [yyyy] [name of copyright owner] 135 # 136 # CDDL HEADER END 137 # 138 139 # 140 # Copyright $year Sun Microsystems, Inc. All rights reserved. 141 # Use is subject to license terms. 142 # 143 # ident \"@(#)pkgtemplate.pl 1.1 08/03/26 SMI\" 144 # 145 146 " ; 147 148 } 149 sub pkginfo_file { 150 local ($FH, $pkg, $name, $description, $type) = @_; 151 152 file_header($FH); 153 my @classes = keys %classes; 154 print $FH 155 " 156 PKG=\"$pkg\" 157 NAME=\"$name\" 158 ARCH=\"ISA\" 159 VERSION=\"SFWVERS,REV=0,0,0\" 160 SUNW_PRODNAME=\"$name\" 161 SUNW_PRODVERS=\"RELEASE/VERSION\" 162 SUNW_PKGTYPE=\"$type\" 163 MAXINST=\"1000\" 164 CATEGORY=\"system\" 165 DESC=\"$description\" 166 VENDOR=\"SunMicrosystems, Inc.\" 167 HOTLINE=\"Please contact your local service provider\" 168 EMAIL=\"\" 169 CLASSES=\"@classes\" 170 BASEDIR=$basedir 171 SUNW_PKGVERS=\"1.0\" 172 SUNW_PKG_ALLZONES=\"false\" 173 SUNW_PKG_HOLLOW=\"false\" 174 SUNW_PKG_THISZONE=\"false\" 175 " ; 176 } 177 178 sub file_type { 179 local ($path) = @_; 180 181 open(FH, "/bin/file $path|"); 182 my $line = <FH>; 183 close(FH); 184 chop $line; 185 186 # if the path matches the pattern, it's a man page. 187 ($path =~ /.*\/man\/man.+\/.*\.[1-9].*/) && ($line = 'roff'); 188 if ($line =~ /XML document/) { 189 open(FH, "<$path"); 190 my $tmp; 191 192 while(defined($tmp = <FH>)) { 193 ($tmp =~ /DOCTYPE (.+) SYSTEM/) && ($line = $1); 194 } 195 close(FH); 196 } 197 198 return($line); 199 } 200 201 sub pathinfo_to_class_type_mode { 202 my ($path, $imode) = @_; 203 my ($class, $type, $mode) = ('none', 'f', $imode); 204 205 if (-f $path) { 206 $mode = $imode & 0555; 207 208 if (($path =~ m{^etc/}) && (-f $path)) { 209 ($class, $type, $mode) = ('preserve', 'e', oct('0644')); 210 } elsif (defined($ftype = file_type($path))) { 211 if ($ftype =~ 'executable') { # strip write bits from executables 212 $mode = $imode & 07555; 213 } elsif ($ftype eq 'service_bundle') { 214 ($class, $type, $mode) = ('manifest', 'f', oct('0444')); 215 } 216 } elsif ($path =~ m{^var/.*$}) { 217 ($type, $mode) = ('v', oct('0644')); 218 } 219 } 220 221 return ($class, $type, $mode); 222 } 223 224 sub filesystem_to_entry { 225 local ($path, %entry) = @_; 226 227 my ($dev, $inode, $mode, $nlink, $uid, $gid) = lstat($path); 228 229 if (defined($mode) && defined($uid) && defined($gid)) { 230 $entry->{path} = $path; 231 $entry->{class} = 'none'; 232 $entry->{owner} = 'root'; # getpwuid($uid); 233 $entry->{group} = 'bin'; # getgrgid($gid); 234 235 if (S_ISDIR($mode)) { 236 $entry->{type} = 'd'; 237 $entry->{mode} = S_IMODE($mode) #& 07555; 238 } elsif (S_ISREG($mode)) { 239 $entry->{type} = 'f'; 240 ($entry->{class}, $entry->{type}, $entry->{mode}) = 241 pathinfo_to_class_type_mode($path, S_IMODE($mode)); 242 } elsif (S_ISLNK($mode)) { 243 $entry->{type} = 's'; 244 $entry->{rpath} = readlink($path); 245 } else { 246 printf("failed: $path: %8.8o\n", $mode); 247 $entry = (); 248 } 249 } else { 250 printf("%s: %s\n", $path, $!); 251 } 252 253 return $entry; 254 } 255 256 sub prototype_entry { 257 local ($FH, $base, $entry) = @_; 258 my $path = $entry->{path}; 259 260 (length($base) > 0) && ($path =~ m{$base/(.*)}) && ($path = $1); 261 ($path eq $base) && return; 262 263 (($path =~ /.*\.a$/) || ($path =~ /.*\.la$/)) && 264 print($FH "# CHECKME: we don't normally ship this type of file\n"); 265 (($path =~ /etc\/init\.d/) || ($path =~ /etc\/rc.*\.d/)) && 266 print($FH "# CHECKME: ship the SMF bits instead\n"); 267 268 (($path =~ /\/i86pc/) || ($path =~ /\/amd64/) || 269 ($path =~ /\/sparc\//) || ($path =~ /\/sun4\//)) && 270 print($FH "# CHECKME: replace with entries in prototype_{arch}"); 271 if ($entry->{type} =~ /[defvx]/) { 272 printf($FH "%s %s %s %4.4o %s %s\n", $entry->{type}, 273 $entry->{class}, $path, $entry->{mode}, 274 $entry->{owner}, $entry->{group}); 275 } elsif ($entry->{type} =~ /[ls]/) { 276 printf($FH "%s %s %s=%s\n", $entry->{type}, $entry->{class}, 277 $path, $entry->{rpath}); 278 } 279 } 280 281 sub prototype_header { 282 local ($FH) = @_; 283 284 print $FH "i pkginfo\n"; 285 print $FH "i copyright\n"; 286 print $FH "i depend\n\n"; 287 } 288 289 sub prototype_file { 290 local ($FH, $base, @paths) = @_; 291 292 my $lineno = 0; 293 294 for $path (@paths) { 295 ($path eq '.') && next; # skip . 296 297 (($lineno++ % 100) == 0) && print("."); 298 299 # use the contents file data first. 300 my ($centry, $fentry, $entry) = 301 ($contents{$path}, filesystem_to_entry($path)); 302 if (defined($centry) && defined($fentry)) { 303 if ($centry->{type} ne $fentry->{type}) { 304 print $FH "# CHECK ME: package db/filesystem conflict\n"; 305 $entry = $fentry; 306 } else { 307 $entry = $centry; 308 } 309 } elsif (defined($centry)) { 310 $entry = $centry; 311 } elsif (defined($fentry)) { 312 $entry = $fentry; 313 } 314 315 if (defined($entry)) { 316 prototype_entry($FH, $base, $entry); 317 $classes{$entry->{class}} = $entry->{class}; 318 } 319 } 320 } 321 322 sub prototype_com_file { 323 local ($FH, @paths) = @_; 324 my $base = $basedir; 325 326 ($base =~ m{/(.*)}) && ($base = $1); 327 328 file_header($FH); 329 print $FH "i pkginfo\n"; 330 print $FH "i copyright\n"; 331 print $FH "i depend\n\n"; 332 prototype_file($FH, $base, @paths); 333 } 334 335 sub prototype_arch_file { 336 local ($FH, @paths) = @_; 337 my $base = $basedir; 338 339 ($base =~ m{/(.*)}) && ($base = $1); 340 341 file_header($FH); 342 print $FH "!include prototype_com\n\n"; 343 prototype_file($FH, $base, @paths); 344 } 345 346 sub generate_package { 347 local ($pkg, $name, $description, $type, @paths) = @_; 348 my $pkgdir = "$ENV{'SRC'}/pkgdefs/$pkg"; 349 350 %classes = (); 351 print("Generating $pkg ($pkgdir)..."); 352 353 mkdir($pkgdir, 0777); 354 (-f $license_file) && 355 copy($license_file, "$pkgdir/copyright"); 356 357 # this should split the paths into architecture specific and independent 358 # file sets. 359 open($FH, ">$pkgdir/prototype_com"); 360 prototype_com_file($FH, sort @paths); 361 close($FH); 362 363 open($FH, ">$pkgdir/prototype_i386"); 364 prototype_arch_file($FH); #, sort @paths); 365 close($FH); 366 367 open($FH, ">$pkgdir/prototype_sparc"); 368 prototype_arch_file($FH); #, sort @paths); 369 close($FH); 370 371 #print("\n"); return; 372 373 open($FH, ">$pkgdir/pkginfo.tmpl"); 374 pkginfo_file($FH, $pkg, $name, $description, $type); 375 close($FH); 376 377 print("\n"); 378 } 379 380 sub root_usr_opt_split { 381 local($root, $usr, $opt, @paths) = @_; 382 my $path; 383 384 for $path (@paths) { 385 if ($path =~ /^opt/) { 386 push(@{$opt}, $path); 387 } elsif ($path =~ /^usr/) { 388 push(@{$usr}, $path); 389 } else { 390 push(@{$root}, $path); 391 } 392 } 393 } 394 395 # 396 # Execution begins here 397 # 398 399 GetOptions('pkg=s' => \$pkg_pkg, 'name=s' => \$pkg_name, 400 'description=s' => \$description, 'basedir=s' => \$basedir, 401 'ignore-contents-file', \$ignore_contents_file, 402 'pkginfo=s' => \$pkginfo, 'prototype=s' => \$prototype, 403 'proto=s' => \$proto); 404 405 # load the contents file 406 (!defined($ignore_contents_file)) && load_contents_file(); 407 408 print("Enumerating files in $proto..."); 409 @proto_paths = (); 410 chdir($proto); 411 File::Find::find({wanted => \&find_proto_paths}, '.'); 412 @paths = @proto_paths; 413 print("done\n"); 414 415 my (@root, @usr, @opt) = (); 416 root_usr_opt_split(\@root, \@usr, \@opt, @paths); 417 418 if (($#root >= 0) && ($#usr >= 0)) { 419 generate_package($pkg_pkg.'r', $pkg_name.' (root)', 420 $pkg_description.' (root)', 'root', @root); 421 generate_package($pkg_pkg.'u', $pkg_name.' (usr)', 422 $pkg_description.' (usr)', 'usr', @usr); 423 } elsif ($#opt >= 0) { 424 # assume that "optional" software delivers all files in /opt 425 generate_package($pkg_pkg, $pkg_name, $pkg_description, 'optional', @opt); 426 } else { 427 generate_package($pkg_pkg, $pkg_name, $pkg_description, 'usr', @paths); 428 } 429 430 exit 0; 431