1# Vend::MakeCat - Routines for Interchange catalog configurator
2#
3# $Id: MakeCat.pm,v 2.17 2007-08-09 13:40:53 pajamian Exp $
4#
5# Copyright (C) 2002-2007 Interchange Development Group
6# Copyright (C) 1996-2002 Red Hat, Inc.
7#
8# This program was originally based on Vend 0.2 and 0.3
9# Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public
22# License along with this program; if not, write to the Free
23# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
24# MA  02110-1301  USA.
25
26package Vend::MakeCat;
27
28use Cwd;
29use File::Find;
30use File::Copy;
31use File::Basename;
32use Sys::Hostname;
33use Vend::Util;
34require Safe;
35$Safe = new Safe;
36
37require Exporter;
38@ISA = qw(Exporter);
39@EXPORT = qw(
40
41%Conf
42%Content
43%Ever
44%History
45%IfRoot
46%Commandline
47%Postprocess
48%Prefix
49%Window
50$Force
51$Safe
52
53add_catalog
54addhistory
55applicable_directive
56can_do_suid
57check_root_execute
58compare_file
59conf_parse_http
60copy_current_to_dir
61copy_dir
62description
63debug
64directory_process
65do_msg
66error_message
67find_inet_info
68findexe
69findfiles
70get_id
71get_ids
72get_rename
73history
74inet_host
75inet_port
76install_file
77label
78prefix
79pretty
80prompt
81read_additional
82readconfig
83sethistory
84strip_na
85strip_trailing_slash
86substitute
87sum_it
88unique_ary
89validate
90
91);
92
93
94use strict;
95
96use vars qw($Safe $Force $Error $History $VERSION);
97
98$Safe->share(qw/%Conf %Ever &debug/);
99
100use vars qw/
101	%Alias
102	%Conf
103	%Content
104	%Commandline
105	%Ever
106	%History
107	%IfRoot
108	%Postprocess
109	%Special_sub
110	%Prefix
111	%Window
112/;
113
114$VERSION = substr(q$Revision: 2.17 $, 10);
115
116$Force = 0;
117$History = 0;
118
119%Alias = (
120	serverconf => {
121						linux => '/etc/httpd/conf/httpd.conf',
122					},
123);
124
125my %Watch = qw/
126		cfg_extramysql 1
127		/;
128
129my %Pretty = (
130	qw/
131	aliases				Aliases
132	basedir				BaseDir
133	catroot				CatRoot
134	catuser				CatUser
135	cgibase				CgiBase
136	cgidir				CgiDir
137	cgiurl				CgiUrl
138	demotype			DemoType
139	documentroot		DocumentRoot
140	imagedir			ImageDir
141	imageurl			ImageUrl
142	interchangegroup	InterchangeGroup
143	interchangeuser		InterchangeUser
144	mailorderto			MailOrderTo
145	samplehtml			SampleHtml
146	sampleurl			SampleUrl
147	serverconf			ServerConf
148	servername			ServerName
149	sharedir			ShareDir
150	shareurl			ShareUrl
151	vendroot			VendRoot
152/,
153	linkmode => 'Link mode',
154
155);
156
157my %Label = (
158	add_catalog		=> 'Add catalog to interchange.cfg',
159    aliases			=> 'Link aliases',
160	basedir			=> 'Base directory for catalogs',
161	catroot			=> 'Catalog directory',
162	catuser			=> 'Catalog user',
163	catalogname		=> 'Catalog name',
164	cgibase			=> 'CGI base URL',
165	cgidir			=> 'CGI Directory',
166	cgiurl			=> 'URL call for catalog',
167	demotype		=> 'Catalog skeleton',
168	documentroot	=> 'Document Root',
169	imagedir		=> 'Image directory',
170	imageurl		=> 'Image base URL',
171	interchangeuser	=> 'Interchange daemon username',
172	interchangegroup	=> 'Interchange daemon groupname',
173	linkhost		=> 'Link host',
174	linkmode		=> 'Link mode',
175	linkport		=> 'Link port',
176	mailorderto		=> 'Email address for orders',
177	permtype		=> 'Permission Type',
178	run_catalog		=> 'Add catalog to running server',
179	samplehtml		=> 'Catalog HTML base directory',
180	servconflist	=> 'Server config files found',
181	serverconf		=> 'Server config file',
182	serverlist		=> 'Servers in httpd.conf',
183	servername		=> 'Server name',
184	sharedir		=> 'Share Directory',
185	shareurl		=> 'Share URL',
186	win_addcatalog	=> 'Add catalog to Interchange',
187	win_catinfo		=> 'Catalog Initialization Information',
188	win_greeting	=> 'Make an Interchange Catalog',
189	win_servername	=> 'HTTP ServerName',
190	win_server		=> 'HTTP Server Information',
191	win_serverconf	=> 'HTTP Server Configuration File',
192	win_linkinfo	=> 'Link Program Information',
193	win_urls		=> 'URL and Directory Information',
194);
195
196my %Desc = (
197	add_catalog => <<EOF,
198# To make the catalog active, you must add it to the
199# interchange.cfg file. If you don't select this, then you will
200# have to manually add it later.
201EOF
202
203	aliases    =>  <<EOF,
204#
205# Additional URL locations for the CGI program, as with CgiUrl.
206# This is used when calling the catalog from more than one place,
207# perhaps because your secure server is not the same name as the
208# non-secure one.
209#
210# http://www.secure.domain/secure-bin/prog
211#                         ^^^^^^^^^^^^^^^^
212#
213# We set it to the name of the catalog by default to enable the
214# internal HTTP server.
215#
216EOF
217
218	basedir    =>  <<EOF,
219#
220# DIRECTORY where the Interchange catalog directories will go.
221# These are the catalog files, such as the ASCII database source,
222# Interchange page files, and catalog.cfg file. Catalogs will be
223# an individual subdirectory of this directory.
224#
225EOF
226
227	catalogname => <<EOF,
228# Select a short, mnemonic name for the catalog. This will be
229# used to set the defaults for naming the catalog, executable,
230# and directory, so you will have to type in this name
231# frequently.
232#
233# NOTE: This will be the name of 'vlink' or 'tlink', the link CGI
234#       program. Depending on your CGI setup, it may also have
235#       the extension .cgi added.
236#
237# Only the characters [-a-zA-Z0-9_] are allowed, and it is
238# strongly suggested that the catalog name be all lower case.
239#
240# If you are doing the demo for the first time, you might use
241# "standard".
242EOF
243
244	catroot   =>  <<EOF,
245# Where the Interchange files for this catalog will go, pages,
246# products, config and all. This should not be in HTML document
247# space! Usually a 'catalogs' directory below your home directory
248# works well. Remember, you will want a test catalog and an
249# online catalog.
250EOF
251
252	catuser    =>  <<EOF,
253#
254# The user name the catalog will be owned by.
255#
256EOF
257
258	cgibase    =>  <<EOF,
259#
260# The URL-style location of the normal CGI directory.
261# Only used to set the default for the CgiUrl setting.
262#
263# http://www.virtual.com/cgi-bin/prog
264#                       ^^^^^^^^
265#
266# If you have no CGI-bin directory, (your CGI programs end
267# in .cgi), leave this blank.
268#
269EOF
270
271	cgidir     =>  <<EOF,
272# The location of the normal CGI directory. This is a
273# file path, not a script alias.
274#
275# If all of your CGI programs must end in .cgi, this is
276# should be the same as your HTML directory.
277#
278EOF
279
280	cgiurl     =>  <<EOF,
281# The URL location of the CGI program, without the http://
282# or server name.
283#
284# http://www.virtual.com/cgi-bin/prog
285#                       ^^^^^^^^^^^^^
286#
287# http://www.virtual.com/program.cgi
288#                       ^^^^^^^^^^^^
289#
290EOF
291
292	demotype   =>  <<EOF,
293# The type of demo catalog to use. The standard one distributed is:
294#
295#    standard
296#
297# If you have defined your own custom template catalog,
298# you can enter its name.
299#
300# If you are new to Interchange, use "standard" to start with.
301EOF
302
303	documentroot    =>  <<EOF,
304# The base directory for HTML for this (possibly virtual) domain.
305# This is a directory path name, not a URL -- it is your HTML
306# directory.
307#
308EOF
309
310	imagedir   =>  <<EOF,
311# Where the image files should be copied. A directory path
312# name, not a URL.
313#
314EOF
315
316	imageurl   =>  <<EOF,
317# The URL base for the sample images. Sets the ImageDir
318# directive in the catalog configuration file. This is a URL
319# fragment, not a directory or file name.
320#
321#         <IMG SRC="/standard/images/icon.gif">
322#                   ^^^^^^^^^^^^^^^^
323#
324EOF
325
326	interchangegroup    =>  <<EOF,
327# The group name the server-owned files should be set to. This is
328# only important if Interchange catalogs will be owned by
329# multiple users and the group to be used is not the default for
330# the catalog user.
331#
332# Normally this is left blank.
333EOF
334
335	interchangeuser  =>  <<EOF,
336# The user name the Interchange server runs under on this
337# machine. This should not be the same as the user that runs the
338# HTTP server (i.e. NOT nobody).
339EOF
340
341	linkhost => <<EOF,
342# If you are using INET mode, you need to set the host the link
343# CGI will talk to.
344#
345# If Interchange is running on the same server as your web
346# server, this should be "localhost" or "127.0.0.1". If the web
347# server is on a different machine, it is the IP address of the
348# machine Interchange is running on.
349EOF
350
351	linkmode => <<EOF,
352# Interchange can use either UNIX- or internet-domain sockets.
353# Most ISPs would prefer UNIX mode, and it is more secure.
354#
355# If you already have a program there, or use mod_interchange,
356# select NONE. You will then need to copy the program by hand or
357# otherwise ensure its presence.
358EOF
359
360	linkport => <<EOF,
361# If you are using INET mode, you need to set the port the
362# link CGI will talk to. The IANA standard for Interchange is
363# port 7786.
364EOF
365
366	mailorderto  =>  <<EOF,
367# The email address where orders for this catalog should go. To
368# have a secure catalog, either this should be a local user name
369# and not go over the Internet -- or use the PGP option.
370#
371EOF
372
373	permtype  =>  <<EOF,
374# The type of permission structure for multiple user catalogs.
375#
376# Select:
377#    M for each user in own group (with interchange user in group)
378#    G for all users in group of interchange user
379#    U for all catalogs owned by interchange user
380#      (should be catuser as well)
381#
382#    M is recommended, G works for most installations.
383EOF
384
385	run_catalog  =>  <<EOF,
386# You can add this catalog to the running Interchange server. You
387# may not want to do this if you are using a SQL database, as you
388# will not be able to monitor the database creation activity.
389#
390# If you don't do it, then you can restart Interchange to
391# activate the new catalog.
392EOF
393
394	samplehtml =>  <<EOF,
395# Where the sample HTML files (not Interchange pages) should be
396# installed. There is a difference. Usually a subdirectory of
397# your HTML directory.
398#
399EOF
400
401	sampleurl  =>  <<EOF,
402# Our guess as to the URL to run this catalog, used for the
403# client-pull screens and an informational message, not prompted for.
404#
405EOF
406
407	servconflist =>  <<EOF,
408# A list of server configuration files automatically found.
409# When you use history to change this, it will be reflected
410# in the next field to save you entering the file name.
411#
412EOF
413
414	serverconf =>  <<EOF,
415# The server configuration file, if you are running
416# Apache or NCSA. Often:
417#                          /etc/httpd/conf/httpd.conf
418#                          /usr/local/apache/conf/httpd.conf
419#                          /usr/local/etc/httpd/conf/httpd.conf
420#
421EOF
422
423	servername =>  <<EOF,
424# The server name, something like: www.company.com
425#                                  www.company.com:8000
426#                                  www.company.com/~yourname
427#
428EOF
429
430	sharedir => <<EOF,
431# This is a directory path name (not a URL) where the administration user
432# interface images from share/ should be copied to. These will normally be
433# shared by all catalogs. Often this is the same as your DocumentRoot.
434#
435EOF
436
437	shareurl => <<EOF,
438# The URL base for the administration user interface images.
439# This is a URL fragment, not an entire URL.
440#
441#         <IMG SRC="/interchange-5/en_US/bg.gif">
442#                   (leave blank)
443#
444#         <IMG SRC="/~yourname/interchange-5/en_US/bg.gif">
445#                   ^^^^^^^^^^
446#
447EOF
448
449	vendroot  =>  <<EOF,
450# The directory where the Interchange software is installed.
451#
452EOF
453
454	win_addcatalog		=> <<EOF,
455# You should add the catalog callout to interchange.cfg, and
456# optionally can add it into the running server.
457EOF
458
459	win_catinfo		=> <<EOF,
460# We need to set base template type and directory for your catalog.
461EOF
462
463	win_greeting		=> <<EOF,
464# Welcome to Interchange!
465#
466# You can now configure a working catalog.
467#
468# You can exit by selecting the "Cancel" button below, but your
469# catalog will not be built until you complete the configuration.
470EOF
471
472	win_linkinfo		=> <<EOF,
473# We need to get information necessary for compiling the link
474# program(s).
475EOF
476
477	win_server		=> <<EOF,
478# We need to know some basic HTTP Server configuration information.
479EOF
480
481	win_serverconf		=> <<EOF,
482# If you are using Apache or another HTTP server with the same
483# type of configuration file, we can read it and set some
484# defaults based on the server name you are using.
485EOF
486
487	win_servername		=> <<EOF,
488# Since you are running Apache, we can give you a choice of the
489# server names defined in the httpd.conf file you selected. This
490# will be used to pre-set items like DocumentRoot, ScriptAlias
491# (cgi-bin), etc.
492#
493# If you don't see your server, pick the empty option and go to
494# the next screen.
495EOF
496
497	win_urls		=> <<EOF,
498# We need to set the HTML, image, and executable paths for your
499# catalog.
500EOF
501
502);
503
504my %Validate = (
505	demotype => <<EOF,
506The demotype skeleton directory must exist. In addition, if you
507are root the files must be owned by root and not be group-
508or world-writable.
509EOF
510);
511
512my %Build_error = (
513	demotype => <<EOF,
514There were errors in copying the demo files.  Cannot
515continue.  Check to see if permissions are correct.
516EOF
517
518);
519
520my $Wname = 'content00';
521
522sub readconfig {
523	my ($file, $ref) = @_;
524	return undef unless $file;
525	return undef unless -f $file;
526	$ref = {} unless ref $ref;
527	open (INICONF, "< $file")
528		or die "open $file: $!\n";
529	local($/);
530	my $data = <INICONF>;
531	close INICONF;
532	my $novirt;
533
534	my %virtual;
535
536	$data =~ s/^\s*#.*//mg;
537	$data =~ m{(\[<)}
538		or $novirt = 1;
539	my $first = $1;
540	if($first eq '<') {
541		$data =~ s!
542				<catalog\s+
543
544					([^>\n]+)
545				\s*>\s+
546					([\000-\377]*?)
547				</catalog>!
548				$virtual{$1} = $2; ''!xieg;
549
550		$virtual{'_base'} = $data;
551	}
552	else {
553		my %recognize = ( base => '_base' );
554		my @lines = grep /\S/, split /\n/, $data;
555		my $handle;
556		for(@lines) {
557			if(/^\[(.*?)\]\s*$/) {
558				my $hh = $1;
559				if($hh =~ /^catalog\s+(\S+)/) {
560					$handle = $1;
561				}
562				elsif($recognize{$hh}) {
563					$handle = $recognize{$hh};
564				}
565				else {
566					undef $handle;
567				}
568				$virtual{$handle} = '' if ! $virtual{$handle};
569				next;
570			}
571			next unless $handle;
572			next unless /\S/;
573			$virtual{$handle} .= $_;
574		}
575	}
576
577	my $out = {};
578	foreach my $hk (keys %virtual) {
579		my $ref = $out->{$hk} = {};
580		my @lines = grep /\S/, split /\n/, $virtual{$hk};
581		for(@lines) {
582			s/^\s+//;
583			s/\s+$//;
584			my ($k, $v) = split /\s*=\s*/, $_, 2;
585			$ref->{$k} = $v;
586		}
587	}
588	return $out;
589}
590
591sub read_additional {
592	my ($file) = @_;
593
594	if (! $file) {
595		$file = "$Conf{vendroot}/$Conf{demotype}/config/additional_fields";
596		return undef unless -f $file;
597	}
598
599	my $help = $file;
600	$help =~ s/_fields$/_help/ or undef $help;
601	my $data;
602
603	SPLIT: {
604		local ($/);
605		open ADDLFIELDS, "< $file"
606			or return undef;
607		$data = <ADDLFIELDS>;
608		close ADDLFIELDS;
609	}
610
611	HELP: {
612		local($/) = "";
613		last HELP unless open  ADDLHELP, "< $help";
614		while(<ADDLHELP>) {
615			s/^[.\t ]+$//mg;
616			my ($k, $v) = split /\n/, $_, 2;
617			$Desc{lc $k} = $v;
618		}
619		close ADDLHELP;
620	}
621
622	my @chunks;
623	if($data =~ /^\s*</) {
624		@chunks = read_common_config($data);
625		return read_additional_new(@chunks);
626	}
627	else {
628		@chunks = split /\n\n+/, $data;
629		return read_additional_old(@chunks);
630	}
631}
632
633sub read_additional_old {
634	my (@chunks) = @_;
635
636	my @addl_windows;
637	my %label;
638
639	my $winref;
640
641	for(@chunks) {
642		my $noprompt = '';
643		my $grp;
644		my $realgrp;
645		my $wid;
646		my $cref = {};
647		s/\s+$//;
648		my ($var, $prompt, $default) = split /\n/, $_, 3;
649
650		($var, $realgrp, $wid) = split /\t/, $var;
651
652		$cref->{widget} = $wid if $wid;
653
654		my $label;
655		($prompt, $label) = split /\t/, $prompt, 2;
656
657		my $subcode;
658		my $mainparam;
659
660		if($var =~ s/{\s*([A-Z0-9]+)(\s*\S.*?)?\s*}\s*//) {
661			$mainparam = lc $1;
662			my $test = $2;
663			$test =~ s/'?__MVC_([A-Z0-9]+)__'?/\$Conf{\L$1}/g;
664			$subcode = <<EOF;
665sub {
666	my \$status;
667	if(\$Conf{$mainparam} $test) {
668		\$status = 1;
669	}
670	else {
671		\$status = 0;
672	}
673	return \$status;
674}
675EOF
676
677			my $sub = eval $subcode;
678			if($@) {
679				undef $sub;
680			}
681			$cref->{conditional} = $sub;
682		}
683		$var =~ s/\s+//g;
684		$var =~ s:^!::
685			and $noprompt = 1;
686		$var =~ s/\W+//g;
687		$var = lc $var;
688debug("conditional code: $subcode") if $Watch{$var};
689
690		$cref->{help} = description($var);
691		$cref->{group} ||= $realgrp;
692		$grp = $cref->{group} || $var;
693
694		if(! $var and $cref->{group}) {
695			$Window{$cref->{group}} ||= { };
696			$Window{$cref->{group}}->{banner} = $label || $prompt;
697			$Window{$cref->{group}}->{help}    =
698			$Window{$cref->{group}}->{message} = description($cref->{group});
699			push @addl_windows, $cref->{group};
700			next;
701		}
702		elsif($grp ne $var) {
703			if (! $Window{$grp}) {
704				$Window{$grp} = { };
705				push @addl_windows, $grp;
706			}
707			elsif($mainparam) {
708				$Window{$grp}{conditional} = $cref->{conditional}
709					if !  $Window{$grp}{conditional};
710			}
711			$Window{$grp}{contents} = [] if ! $Window{$grp}{contents};
712			push @{$Window{$grp}{contents}}, $var;
713		}
714		else {
715			push @addl_windows, $var;
716		}
717
718		my (@history)  = split /\t/, $default;
719		$default = $Conf{$var} || $history[0] || '';
720
721		if($label =~ /\S/) {
722			$cref->{banner} = $prompt;
723			$cref->{label} = $label;
724		}
725		else {
726			$cref->{label} = $prompt;
727		}
728
729		my $presubcode;
730		if($default =~ s/__MVC_([A-Z0-9]+)__/\$Conf{\L$1}/g) {
731			$default =~ s/\@/\\\@/g;
732			my $presubcode = qq{
733				sub {
734					return qq[$default]
735				}
736			};
737			my $presub = eval $presubcode;
738			if($@) {
739debug("error evaling prefix sub for $var: $presubcode");
740			}
741			$cref->{prefix} = $presub;
742			$cref->{prefix_source} = $presubcode;
743		}
744		else {
745			$cref->{prefix} = $default;
746		}
747		$cref->{options} = \@history;
748		if ($noprompt) {
749			if($cref->{conditional}) {
750				my $snippet = <<EOF;
751
752	\$Conf{$var} = q{$default} if \$status;
753	return \$status;
754}
755EOF
756				# Appease vi {
757				$subcode =~ s/\s*return .*\s+}\s*$/$snippet/;
758				$cref->{conditional} = eval $subcode;
759				$cref->{conditional_source} = $subcode;
760			}
761			else {
762				$Conf{$var} = substitute($default);
763				$cref->{conditional} = sub { 0 };
764			}
765		}
766		if($mainparam || $cref->{group}) {
767			my $winref;
768			if($cref->{group}) {
769				$winref = $Window{$cref->{group}};
770			}
771			else {
772				$winref = $Content{$mainparam};
773				$winref->{additional} ||= [];
774				push @{$winref->{additional}}, $var;
775			}
776			$winref->{override} ||= {};
777			$winref->{override}{$var} = $cref;
778			$Content{$var} = $cref unless $Content{$var};
779		}
780		elsif ($Content{$var}) {
781die("generated duplicate param for $var with no group or mainparam.\n");
782debug("generated duplicate param for $var with no group or mainparam.\n");
783		}
784		else {
785			$Content{$var} = $cref;
786		}
787#debug( "ref for $var: " . ::uneval($cref));
788	}
789	close ADDLFIELDS;
790#debug("read_additional: returning: " . join ",", @addl_windows);
791	my %seen;
792
793	# Multiple conditions may define them more than once
794	@addl_windows = grep !$seen{$_}++, @addl_windows;;
795	return @addl_windows;
796}
797
798sub read_additional_new {
799	my ($help, @chunks) = @_;
800	my $winref;
801	my @addl_windows;
802
803	foreach my $cref (@chunks) {
804		my $grp;
805		my $subcode;
806		my $mainparam;
807		my $var;
808		my $default;
809		my $cond_code;
810
811		if(! ref $cref) {
812			# Bad chunk
813			next;
814		}
815
816		$cref->{_additional} = 1;
817
818		if($cond_code = $cref->{conditional}) {
819			delete $cref->{conditional};
820			if($cond_code =~ /^sub\s+{.*}\s*$/s) {
821				$subcode = $cond_code;
822			}
823			else {
824				$cond_code =~ m{^[A-Z][A-Z0-9]+$}
825					and $cond_code = "\U__MVC_${cond_code}__";
826				$cond_code =~ m{__MVC_([A-Z0-9]+)__}
827					and $mainparam = lc $1;
828				$cond_code =~ s{(['"]?)__MVC_([A-Z0-9]+)__\1}
829							   {'$Conf{' . lc $2 . '}'   }eg;
830
831				# Appease vi }
832				$subcode = <<EOF;
833		sub {
834			my \$status;
835			if($cond_code) {
836				\$status = 1;
837			}
838			else {
839				\$status = 0;
840			}
841			return \$status;
842		}
843EOF
844			}
845		}
846
847		if($subcode) {
848			my $sub = eval $subcode;
849			if($@) {
850debug("Problem evaluating sub: $subcode");
851				undef $sub;
852			}
853			$cref->{conditional} = $sub;
854		}
855
856		for my $code (qw/callback/) {
857			my $cb = $cref->{$code}
858				or next;
859			if($cb =~ /^\s*sub\s+{/) {
860				# Appease vi }
861				local($SIG{__DIE__});
862				$cref->{$code} = eval $cb;
863				$cref->{"${code}_source"} = $cb;
864			}
865			elsif($cb =~ /^\s*\[.*\]\s*$/s) {
866				$cref->{$code} = eval($cb);
867				$cref->{"${code}_source"} = $cb;
868			}
869			elsif($cb =~ /[a-z]/) {
870				my @items = Text::ParseWords::shellwords($cref->{$code});
871				@items = map { lc $_ } @items;
872				$cref->{$code} = \@items;
873				$cref->{"${code}_source"} = $cb;
874			}
875		}
876
877		for my $code (qw/options history/) {
878			my $cb = $cref->{$code}
879				or next;
880			if($cb =~ /^\s*sub\s+{.*}\s*$/s) {
881				local($SIG{__DIE__});
882				$cref->{$code} = eval $cb;
883				$cref->{"${code}_source"} = $cb;
884			}
885			elsif($cb =~ /^\s*\[.*\]\s*$/s) {
886				$cref->{$code} = eval($cb);
887				$cref->{"${code}_source"} = $cb;
888			}
889			elsif($cb =~ /[a-z]/) {
890				my @items = Text::ParseWords::shellwords($cref->{$code});
891				$cref->{$code} = \@items;
892				$cref->{"${code}_source"} = $cb;
893			}
894		}
895
896		for my $code (qw/check_routine/) {
897			my $cb = $cref->{$code}
898				or next;
899			if($cb =~ /^\s*sub\s+{/) {
900				# Appease vi }
901				$cref->{$code} = eval $cb;
902				$cref->{"${code}_source"} = $cb;
903			}
904			else {
905				undef $cref->{$code};
906			}
907		}
908
909		$var = $cref->{name} || $Wname++;
910		$var =~ s/\s+//g;
911		$var =~ s/\W+//g;
912		$cref->{name} = $var = lc $var;
913$::Subcode{$var} = $subcode;
914debug("conditional code: $subcode") if $Watch{$var};
915
916		$grp = $cref->{group} || $var;
917
918		if($cref->{_window}) {
919			if(my $wref = $Window{$var}) {
920				for(keys %$wref) {
921					$cref->{$_} = $wref->{$_}
922						unless defined $cref->{$_};
923				}
924			}
925			$cref->{help} ||= description($var);
926			$cref->{message} ||= $cref->{help};
927			$cref->{banner}  ||= $cref->{label};
928			push @addl_windows, $var;
929			$Window{$var} = $cref;
930			next;
931		}
932		elsif($grp ne $var) {
933			if (! $Window{$grp}) {
934				$Window{$grp} = { };
935				push @addl_windows, $grp;
936			}
937			elsif($mainparam) {
938				$Window{$grp}{conditional} = $cref->{conditional}
939					if !  $Window{$grp}{conditional};
940			}
941			$Window{$grp}{contents} = [] if ! $Window{$grp}{contents};
942			push @{$Window{$grp}{contents}}, $var;
943		}
944		else {
945			push @addl_windows, $var;
946		}
947
948		if(! $cref->{default} and ref $cref->{history} eq 'ARRAY') {
949			$cref->{default} = $cref->{history}[0];
950		}
951
952		$cref->{help}    ||= description($var);
953		$cref->{message} ||= $cref->{help};
954
955		# Set default one of three ways
956		if($cref->{default}) {
957			$default = $cref->{default};
958		}
959
960		if($default =~ /\t/ and ! $cref->{history}) {
961			$cref->{history} = [ split /\t/, $default ];
962			$default =~ s/\t.*//;
963			$cref->{default} = $default;
964		}
965		$cref->{label} = $cref->{prompt} if ! $cref->{label};
966
967		my $presubcode;
968		if($cref->{default} =~ s/__MVC_([A-Z0-9]+)__/\$Conf{\L$1}/g) {
969			$cref->{default} =~ s/\@/\\\@/g;
970			my $presubcode = qq{
971				sub {
972					return qq[$cref->{default}]
973				}
974			};
975			my $presub = eval $presubcode;
976			if($@) {
977debug("error evaling prefix sub for $var: $presubcode");
978			}
979			$cref->{default} = $cref->{prefix} = $presub;
980			$cref->{default_source} = $presubcode;
981		}
982		else {
983			$cref->{default} = $cref->{prefix} = $default;
984		}
985
986		$cref->{options} = $cref->{history} if ! $cref->{options};
987		if ($cref->{noprompt}) {
988			if($cref->{conditional}) {
989				# Appease vi {
990				my $snippet = <<EOF;
991
992	\$Conf{$var} = q{$default} if \$status;
993	return \$status;
994}
995EOF
996				# Appease vi {
997				$subcode =~ s/\s*return .*\s+}\s*$/$snippet/;
998				$cref->{conditional} = eval $subcode;
999				if($@) {
1000debug("Problem evaluating sub: $subcode");
1001				}
1002				$cref->{conditional_source} = $presubcode;
1003			}
1004			else {
1005				$Conf{$var} = $default;
1006				$cref->{conditional} = sub { 0 };
1007			}
1008		}
1009		if($cref->{always_set}) {
1010			$Conf{$var} = substitute($default);
1011		}
1012
1013		if($mainparam || $cref->{group}) {
1014			my $winref;
1015			if($cref->{group}) {
1016				$winref = $Window{$cref->{group}};
1017			}
1018			else {
1019				$winref = $Content{$mainparam};
1020				$winref->{additional} ||= [];
1021				push @{$winref->{additional}}, $var;
1022			}
1023			$winref->{override} ||= {};
1024			$winref->{override}{$var} = $cref;
1025		}
1026		elsif ($Content{$var}) {
1027debug("generated duplicate param for $var with no group or mainparam.");
1028die("generated duplicate param for $var with no group or mainparam.");
1029		}
1030		else {
1031			$Content{$var} = $cref;
1032			push @addl_windows, $var;
1033		}
1034#debug( "ref for $var: " . ::uneval($cref));
1035	}
1036	my %seen;
1037
1038	# Multiple conditions may define them more than once
1039	@addl_windows = grep !$seen{$_}++, @addl_windows;;
1040debug("read_additional returning windows: " . join ",", @addl_windows);
1041#debug("Here is the whole shebang:\n" . uneval(\%Window) . "\ncontent:\n" . uneval(\%Content));
1042	return @addl_windows;
1043}
1044
1045sub read_common_config {
1046	my $data = shift;
1047#debug("read_common_config called with data=$data");
1048	my @lines = split /\n/, $data;
1049	my $prev = '';
1050	my $waiting;
1051
1052	my @out;
1053	my $out = \@out;
1054	my $wref;
1055	my $cref;
1056
1057	my $type;
1058	for(@lines) {
1059		# Strip CR, we hope
1060		s/\s+$//;
1061
1062		# Handle continued lines
1063		if(s/\\$//) {
1064			$prev .= $_;
1065			next;
1066		}
1067		elsif($waiting) {
1068			if($_ eq $waiting) {
1069				undef $waiting;
1070				$_ = $prev;
1071				$prev = '';
1072				s/\s+$//;
1073			}
1074			else {
1075				$prev .= "$_\n";
1076				next;
1077			}
1078		}
1079		elsif($prev) {
1080			$_ = "$prev$_";
1081			$prev = '';
1082		}
1083
1084		if (s/<<(\w+)$//) {
1085			$waiting = $1;
1086			$prev .= $_;
1087			next;
1088		}
1089
1090		next unless /\S/;
1091		next if /^\s*#/;
1092		if(m{
1093				^ \s* <
1094						(\w+)
1095						(?:\s+(\w[-\w]*\w))?
1096					\s*>\s*
1097			}x)
1098		{
1099			$type = lc $1;
1100			my $name = $2 || undef;
1101			if($name) {
1102				$name = lc $name;
1103				$name =~ tr/-/_/;
1104			}
1105			if(defined $cref and $cref->{_window} and $type ne 'window') {
1106				$wref = $cref;
1107				$out = $wref->{content_array} ||= [];
1108			}
1109			else {
1110				push @$out, $cref if $cref;
1111			}
1112			$cref = { "_$type" => 1, name => $name };
1113			next;
1114		}
1115		elsif (m{^\s*</(\w[-\w]+\w)\s*>\s*}) {
1116			my $ender = lc $1;
1117			$ender =~ tr/-/_/;
1118			if(! $cref) {
1119				push @out, $wref if $wref;
1120				$out = \@out;
1121				undef $type;
1122				undef $wref;
1123				undef $cref;
1124			}
1125			elsif($ender eq $type) {
1126				if($type eq 'window') {
1127					push @out, ($wref || $cref);
1128					undef $cref;
1129					undef $wref;
1130					$out = \@out;
1131				}
1132				else {
1133					push @$out, $cref;
1134					undef $cref;
1135				}
1136			}
1137			else {
1138				die errmsg("Syntax error in config input: %s", $_);
1139			}
1140			next;
1141		}
1142
1143		s/^\s*(\w[-\w]*\w)(\s+|$)//
1144			or do {
1145				die "Problem reading config reference type=$type: $_\n";
1146			};
1147		my $parm = lc $1;
1148		$cref ||= {};
1149		$parm =~ tr/-/_/;
1150		$cref->{$parm} = $_;
1151	}
1152	push @out, $cref if $cref;
1153	my @extra;
1154	for my $ref (@out) {
1155		if($ref->{content_array} and $ref->{_window}) {
1156			for (@{delete $ref->{content_array}}) {
1157				$_->{name} ||= $Wname++;
1158debug("popping $_->{name} from $ref->{name} content array");
1159				$_->{group} = $ref->{name};
1160				push @extra, $_;
1161			}
1162		}
1163	}
1164	push @out, @extra;
1165#debug("read_common_config: " . uneval(\@out) );
1166	return @out;
1167}
1168
1169sub read_commands {
1170	my ($file, $wref) = @_;
1171
1172	my @data;
1173	my @files;
1174	my $pre_post;
1175	if(! $file) {
1176		@files = (
1177			"$Conf{vendroot}/$Conf{demotype}/config/precopy_commands",
1178		    "$Conf{vendroot}/$Conf{demotype}/config/postcopy_commands",
1179		);
1180		$pre_post = 1;
1181	}
1182	else {
1183		@files = ($file);
1184		$wref = {} unless $wref;
1185	}
1186
1187	for (my $i = 0; $i < @files; $i++) {
1188		my $fn = $files[$i];
1189		next if ! $fn;
1190		next if ! -f $fn;
1191		open CMDFILE, "< $fn"
1192			or do {
1193				my $msg = errmsg(
1194							"Cannot %s commands file %s: %s",
1195							errmsg('open'),
1196							$fn,
1197							$!,
1198						  );
1199				die "$msg\n";
1200			};
1201		local ($/);
1202		$data[$i] = <CMDFILE>;
1203		close CMDFILE
1204			or do {
1205				my $msg = errmsg(
1206							"Cannot %s commands file %s: %s",
1207							errmsg('close'),
1208							$fn,
1209							$!,
1210						  );
1211				die "$msg\n";
1212			};
1213	}
1214
1215	my $cmd_num = "cmd000";
1216
1217	return undef unless @data;
1218	foreach my $block (@data) {
1219		my $root_msg = $> == 0 ? <<EOF : '';
1220
1221Because you are root, you should be very careful
1222what commands you run.  If you are unsure about the
1223ownership of any files, or of what the effects might
1224be, please uncheck the box next to the command.
1225EOF
1226		if($pre_post eq '1') {
1227			if(! $Window{precopy_commands}) {
1228				$Window{precopy_commands} = {
1229					contents => [],
1230					conditional => 0,
1231					message => 'Resolving catalog initialization commands',
1232				};
1233			}
1234			$wref = $Window{precopy_commands};
1235			$pre_post = 2;
1236		}
1237		elsif ($pre_post == 2) {
1238			if(! $Window{postcopy_commands}) {
1239				$Window{postcopy_commands} = {
1240					contents => [],
1241					banner => 'Resolving catalog finalization commands',
1242				};
1243			}
1244			$wref = $Window{postcopy_commands};
1245			$pre_post = 3;
1246		}
1247		next unless $block;
1248
1249		my @cmds;
1250		if($block =~ /^\s*</) {
1251			@cmds = read_common_config($block);
1252		}
1253		else {
1254			@cmds = split /\n\n+/, $block;
1255		}
1256		foreach my $cmd (@cmds) {
1257			my $cref;
1258			my $unprompted;
1259			my $subcode;
1260			my $mainparam;
1261			my ($command, $prompt);
1262			if(ref $cmd) {
1263				$cref = $cmd;
1264				$cmd = '';
1265			}
1266			else {
1267				$cmd = substitute($cmd);
1268				$cmd =~ s/\\\n//g;
1269				$cref = {};
1270				my $prompt;
1271				($command, $prompt) = split /\n/, $cmd, 2;
1272				if($prompt =~ s/^\s*(\w+\s*=[^\n]*|{\s*\w+\s*=.*})\s*\n//s) {
1273					my $extra = $1;
1274#debug("Found command mods: $extra");
1275					my $ref = get_option_hash($extra);
1276#debug("Command mods: " . uneval($ref));
1277					if (ref $ref) {
1278						for (keys %$ref) {
1279							$cref->{$_} = $ref->{$_};
1280						}
1281					}
1282					else {
1283						warn "Unsuccessful command option parse: $extra\n";
1284					}
1285				}
1286				$cref->{help}   = $prompt if ! $cref->{help};
1287			}
1288			if($cref->{window_indicator}) {
1289				$wref ||= $Window{$cref->{name}};
1290				for(keys %$cref) {
1291					$wref->{$_} = $cref->{$_};
1292				}
1293				next;
1294			}
1295			$cref->{widget} = 'yesno' if ! $cref->{widget};
1296			$command = $cref->{command} if ! $command;
1297			$command =~ s/^\s+//;
1298			$command =~ s/\s+$//;
1299			$command =~ s/^!// and $> != 0 and $cref->{unprompted} = 1;
1300			delete $wref->{conditional}
1301				if  $wref->{conditional} eq '0'
1302				and $pre_post;
1303
1304			if($command =~ s/{\s*([A-Z0-9]+)(\s*\S.*?)?\s*}\s*//) {
1305				$mainparam = lc $1;
1306				my $test = $2;
1307				$test =~	s{(['"]?)__MVC_([A-Z0-9]+)__\1}
1308							 {'\$Conf{' . lc $2 . '}'   }eg;
1309				$subcode = <<EOF;
1310 sub {
1311	my \$status;
1312#debug("conditional checking param=$mainparam testing=$test Value=\$Conf{$mainparam}");
1313	if(\$Conf{$mainparam} $test) {
1314		\$status = 1;
1315	}
1316	else {
1317		\$status = 0;
1318	}
1319#debug("conditional routine returning \$status");
1320	return \$status;
1321 }
1322EOF
1323			}
1324			elsif ( $cref->{conditional} ) {
1325				$cref->{conditional} =~ s/^[A-Z0-9a-z]+$/__MVC_\U${1}__/;
1326				$cref->{conditional} =~ s{(['"]?)__MVC_([A-Z0-9]+)__\1}
1327										 {'\$Conf{' . lc $2 . '}'   }eg;
1328				# Make vi happy: } }
1329				$subcode = delete $cref->{conditional};
1330			}
1331
1332#debug("read_commands:  sub=$subcode");
1333			if($subcode) {
1334				$subcode = "sub {\n" . $cref->{conditional} . "}"
1335					unless $subcode =~ /^\s*sub\s+{/;
1336				my $sub = eval $subcode;
1337				if($@) {
1338debug("read_commands: Problem evaluating sub: $subcode");
1339					undef $sub;
1340				}
1341				$cref->{conditional} = $sub;
1342			}
1343
1344			$cref->{command} = $command;
1345			$cref->{label}   = $command if ! $cref->{label};
1346			$cref->{name}    = $cmd_num++ unless $cref->{name};
1347			my $name = $cref->{name};
1348			$Content{$name} = $cref;
1349			if(my $gname = $cref->{group}) {
1350				$Window{$gname}->{contents} ||= [];
1351				push @{$Window{$gname}->{contents}}, $name;
1352			}
1353			else {
1354				push @{$wref->{contents}}, $name;
1355			}
1356		}
1357	}
1358	if($pre_post) {
1359		return ($Window{precopy_commands}, $Window{postcopy_commands});
1360	}
1361	else {
1362		return $wref;
1363	}
1364}
1365
1366
1367# Validate a field against:
1368#
1369#   check_regex -- a regular expression which must succeeed
1370#   check_blank -- Just needs a non-blank value
1371#   check_routine -- a subroutine which can return -1, 0, 1
1372#   check_message -- a template (from errmsg) which can be used
1373#
1374# If the return value is -1, then the error message is assumed
1375# to have been handled by the check_routine and is not returned.
1376#
1377sub validate {
1378	my ($val, $parm) = @_;
1379	my $thing = $Content{$parm};
1380	if(! $parm or ! $thing) {
1381		return (0, errmsg('blank'));
1382	}
1383
1384	my $status;
1385	my $message = $thing->{check_message};
1386	my $errmsg;
1387
1388	if($thing->{check_regex}) {
1389		$errmsg = errmsg('blank');
1390		$status = length($val) ? 1 : 0;
1391	}
1392	elsif($thing->{check_regex}) {
1393		my $regex = qr/$thing->{check_regex}/;
1394		$status = $val =~ $regex;
1395	}
1396	elsif($thing->{check_routine}) {
1397		($status, $errmsg) = $thing->{check_routine}->($val, $parm);
1398	}
1399	else {
1400		$status = 1;
1401	}
1402
1403	## This allows directly returning error and no confirm screen
1404	return $status if abs($status);
1405	$message = "%s (value '%s'): failed validation"
1406		if ! $message;
1407	my $lab = label($parm) || $parm;
1408	$message = errmsg($message, $lab, $val, $parm);
1409	return($status, $message);
1410}
1411
1412sub prefix {
1413	my ($parm, $nodefault, $override) = @_;
1414	$parm = lc $parm;
1415	if($Alias{$parm} and $Conf{$parm}) {
1416		$Conf{$parm} = $Alias{$parm}{$Conf{$parm}};
1417	}
1418	return $Conf{$parm} if $Conf{$parm};
1419	return $ENV{"MVC_\U$parm"} if $ENV{"MVC_\U$parm"};
1420	return undef if $nodefault;
1421	my $thing = $Content{$parm}{prefix} || $Prefix{$parm};
1422	if(ref $thing eq 'CODE') {
1423		return $thing->();
1424	}
1425	elsif(ref $thing eq 'ARRAY') {
1426		return $thing->[0];
1427	}
1428	else {
1429		return $thing;
1430	}
1431}
1432
1433%Special_sub= (
1434	cryptpw => sub {
1435		my $pw = shift;
1436		return $pw if $Conf{alreadycrypt};
1437		my @letters = ('A' .. 'Z', 'a' .. 'z');
1438		my $salt = $letters[ int rand(scalar @letters) ];
1439		$salt .= $letters[ int rand(scalar @letters) ];
1440		return crypt($pw, $salt);
1441	},
1442);
1443
1444sub substitute {
1445	my($parm) = @_;
1446	if($parm !~ /^\w+$/) {
1447		$parm =~ s/__MVC_([A-Z0-9]+)__/$Conf{lc $1}/eg;
1448	}
1449	elsif (defined $ENV{"MVC_$parm"}) {
1450		$parm = $ENV{"MVC_$parm"};
1451	}
1452	elsif (my $sub = $Special_sub{lc $parm}) {
1453		if(ref $sub) {
1454			$parm = $sub->($Conf{lc $parm});
1455		}
1456		else {
1457			$parm = $sub;
1458			$parm =~ s/__MVC_([A-Z0-9]+)__/$Conf{lc $1}/eg;
1459		}
1460	}
1461	else {
1462		$parm = $Conf{lc $parm};
1463	}
1464	$parm = '' unless defined $parm;
1465	return $parm;
1466}
1467
1468sub sum_it {
1469	my ($file) = @_;
1470	open(IT, "<$file")
1471		or return undef;
1472	my $data = '';
1473	$data .= $_ while (<IT>);
1474	close IT;
1475	return unpack("%32c*", $data);
1476}
1477
1478sub strip_na {
1479	my $val = shift;
1480	return '' if lc($val) eq 'n/a';
1481	return $val;
1482}
1483
1484
1485sub directory_process {
1486    my $dir = shift;
1487    $dir =~ s:[/\s]+$::;
1488    if($Conf{catuser} and $dir =~ /^~/) {
1489        my $userdir = ( getpwnam( $Conf{catuser} ) )[7];
1490		$dir =~ s/^~/$userdir/ if $userdir;
1491    }
1492    return $dir;
1493}
1494
1495sub strip_trailing_slash {
1496	my $url = shift;
1497	$url =~ s:[/\s]+$::;
1498	return $url;
1499}
1500
1501
1502sub inet_host {
1503	return scalar find_inet_info('h');
1504}
1505
1506sub inet_port {
1507	return scalar find_inet_info('p');
1508}
1509
1510sub find_inet_info {
1511	my $type = shift;
1512	my (@hosts);
1513	my (@ports);
1514	my $prog = "$Conf{relocate}$Conf{vendroot}/src/tlink";
1515	my $the_one = sum_it($prog);
1516	my $defport = '7786';
1517	my $defhost = '127.0.0.1';
1518
1519	my @poss = glob("$Conf{relocate}$Conf{vendroot}/src/tlink.*.*");
1520	for (@poss) {
1521		my $name = $_;
1522		/tlink\.(.*)\.(\d+)$/
1523			or next;
1524		my ($h, $p) = ($1, $2);
1525		push @hosts, $h;
1526		push @ports, $p;
1527		my $one = sum_it($_);
1528		next unless $one eq $the_one;
1529		$defhost = $h;
1530		$defport = $p;
1531	}
1532
1533	if(! $type) {
1534		my %seen;
1535		@ports = grep !$seen{$_}++, @ports;
1536		%seen = ();
1537		@hosts = grep !$seen{$_}++, @hosts;
1538		return (\@hosts, \@ports);
1539	}
1540	elsif ($type =~ /^h/i) {
1541		return $defhost;
1542	}
1543	elsif ($type =~ /^p/i) {
1544		return $defport;
1545	}
1546}
1547
1548sub applicable_directive {
1549	my ($direc, $routine) = @_;
1550	$direc = lc($direc);
1551	if($routine) {
1552		return undef if ! $routine->($direc);
1553	}
1554	return $direc if ! defined $IfRoot{$direc};
1555	return undef if $Conf{asroot} xor $IfRoot{$direc};
1556	return $direc;
1557}
1558
1559
1560sub findexe {
1561	my($exe) = @_;
1562	my($dir,$path) = ('', $ENV{PATH});
1563	$path =~ s/\(\)//g;
1564	$path =~ s/\s+/ /g;
1565	my(@dirs) = split /[\s:]+/, $path;
1566	foreach $dir (@dirs) {
1567		return "$dir/$exe" if -x "$dir/$exe";
1568	}
1569	return '';
1570}
1571
1572sub findfiles {
1573	my($file) = @_;
1574	return undef if $^O =~ /win32/i;
1575	my $cmd;
1576	my @files;
1577	if($cmd = findexe('locate')) {
1578		@files = `locate \\*/$file`;
1579	}
1580	else {
1581		@files = `find / -name $file -print 2>/dev/null`;
1582	}
1583	return undef unless @files;
1584	chomp @files;
1585	return @files;
1586}
1587
1588sub pretty {
1589	my($parm) = @_;
1590	return defined $Pretty{lc $parm} ? $Pretty{lc $parm} : $parm;
1591}
1592
1593sub history {
1594	my $parm = shift;
1595	$parm = lc $parm;
1596	return unless defined $History{$parm};
1597	my @things = $History{$parm}->(@_);
1598	return wantarray ? @things : \@things;
1599}
1600
1601sub error_message {
1602	my($parm) = @_;
1603	$parm = lc $parm;
1604	return defined $Validate{$parm} ? $Validate{$parm} : '';
1605}
1606
1607sub label {
1608	my($parm) = @_;
1609	return defined $Label{lc $parm} ? $Label{lc $parm} : '';
1610}
1611
1612sub description {
1613	my($parm) = @_;
1614	return defined $Desc{lc $parm} ? $Desc{lc $parm} : '';
1615}
1616
1617sub can_do_suid {
1618	return 0 if $^O =~ /win32/i;
1619	my $file = "tmp$$.fil";
1620	my $status;
1621
1622	open(TEMPFILE,">$file");
1623	close TEMPFILE;
1624	eval { chmod 04755, $file; $@ = ''};
1625	$status = $@ ? 0 : 1;
1626	unlink $file;
1627	return $status;
1628}
1629
1630sub get_id {
1631	return 'everybody' if $^O =~ /win32/i;
1632	my $file = -f "$Global::VendRoot/error.log"
1633				? "$Global::VendRoot/error.log" : '';
1634	return '' unless $file;
1635	my ($name);
1636
1637	my($uid) = (stat($file))[4];
1638	$name = (getpwuid($uid))[0];
1639	return $name;
1640}
1641
1642sub get_ids {
1643	return ('everybody', 'nogroup') if $^O =~ /win32/i;
1644	my $file = "tmp$$.fil";
1645	my ($name, $group);
1646
1647	open(TEMPFILE,">$file");
1648	close TEMPFILE;
1649	my($uid,$gid) = (stat($file))[4,5];
1650	unlink $file;
1651	$name = (getpwuid($uid))[0];
1652	$group = (getgrgid($gid))[0];
1653	return ($name,$group);
1654}
1655
1656sub get_rename {
1657	my ($bn, $extra) = @_;
1658	$extra = '~' unless $extra;
1659	$bn =~ s:(.*/)::;
1660	my $dn = $1;
1661	return $dn . "/.$extra." . $bn;
1662}
1663
1664sub compare_file {
1665	my($first,$second) = @_;
1666	return 0 unless -f $first && -f $second;
1667	return 0 unless -s $first == -s $second;
1668	local $/;
1669	open(FIRST, "< $first") or return undef;
1670	open(SECOND, "< $second") or (close FIRST and return undef);
1671	binmode(FIRST);
1672	binmode(SECOND);
1673	$first = '';
1674	$second = '';
1675	while($first eq $second) {
1676		read(FIRST, $first, 1024);
1677		read(SECOND, $second, 1024);
1678		last if length($first) < 1024;
1679	}
1680	close FIRST;
1681	close SECOND;
1682	$first eq $second;
1683}
1684
1685sub set_owner {
1686	return unless $> == 0;
1687	my($file) = @_;
1688	resolve_owner()
1689		unless $Conf{interchangeuid};
1690
1691	my ($user, $group) = ($Conf{interchangeuid}, $Conf{interchangegid});
1692	die errmsg("Can't find info: %s", 'interchangeuid')
1693		unless $Conf{interchangeuid};
1694
1695	if($Conf{permtype} =~ /^m/i) {
1696		$user = $Conf{catuseruid};
1697		$group = $Conf{catusergid};
1698	}
1699	elsif($Conf{permtype} =~ /^g/i) {
1700		$group = $Conf{catusergid};
1701	}
1702	chown($user, $group, $file)
1703		or die errmsg(
1704				"Couldn't set ownership to UID=%s GID=%s for %s: %s",
1705				$user,
1706				$group,
1707				$file,
1708				$!,
1709			);
1710}
1711
1712sub install_file {
1713	my ($srcdir, $targdir, $filename, $opt) = @_;
1714	$opt = {} unless $opt;
1715	my $save_umask;
1716	if($opt->{umask} ) {
1717		$save_umask = umask $opt->{umask};
1718		local($SIG{__DIE__}) = sub { umask $save_umask; warn @_; exit 1 };
1719	}
1720
1721	my $scale;
1722	if($scale = $opt->{scale_call}) {
1723		$scale->( 'start', $opt->{scale}, $opt->{message});
1724	}
1725
1726	if (ref $srcdir) {
1727		$opt = $srcdir;
1728		$srcdir  = $opt->{Source} || die "Source dir for install_file not set.\n";
1729		$targdir = $opt->{Target} || die "Target dir for install_file not set.\n";
1730		$filename = $opt->{Filename} || die "File name for install_file not set.\n";
1731	}
1732	my $srcfile  = $srcdir . '/' . $filename;
1733	my $targfile = $targdir . '/' . $filename;
1734	my $mkdir = File::Basename::dirname($targfile);
1735	my $extra;
1736	my $perms;
1737
1738
1739	if(! -d $mkdir) {
1740		File::Path::mkpath($mkdir, undef, $opt->{dmode} || 0777)
1741			or die "Couldn't make directory $mkdir: $!\n";
1742		chmod($opt->{dmode}, $mkdir) if $opt->{dmode};
1743		set_owner($mkdir);
1744	}
1745
1746	if (! -f $srcfile) {
1747		die "Source file $srcfile missing.\n";
1748	}
1749	elsif (
1750		$opt->{perm_hash}
1751			and $opt->{perm_hash}->{$filename}
1752		)
1753	{
1754		$perms = $opt->{perm_hash}->{$filename};
1755	}
1756	elsif ($opt->{fmode}) {
1757		$perms = $opt->{fmode};
1758	}
1759	elsif ( $opt->{Perms} =~ /^(m|g)/i ) {
1760		$perms = (stat(_))[2] | 0660;
1761	}
1762	elsif ( $opt->{Perms} =~ /^u/i ) {
1763		$perms = (stat(_))[2] | 0600;
1764	}
1765	else {
1766		$perms = (stat(_))[2] & 0777;
1767	}
1768
1769	if( ! $Global::Win32 and -f $targfile and ! compare_file($srcfile, $targfile) ) {
1770		open (GETVER, "< $targfile")
1771			or die "Couldn't read $targfile for version update: $!\n";
1772		while(<GETVER>) {
1773			/VERSION\s+=.*?\s+([\d.]+)/ or next;
1774			$extra = $1;
1775			$extra =~ tr/0-9//cd;
1776			last;
1777		}
1778		$extra = '~' unless $extra;
1779		my $rename = get_rename($targfile, $extra);
1780		while (-f $rename ) {
1781			$extra .= '~';
1782			$rename = get_rename($targfile, $extra);
1783		}
1784		rename $targfile, $rename
1785			or die "Couldn't rename $targfile to $rename: $!\n";
1786	}
1787
1788	File::Copy::copy($srcfile, $targfile)
1789		or die "Copy of $srcfile to $targfile failed: $!\n";
1790	if($opt->{Substitute}) {
1791			my $bak = "$targfile.mv";
1792			rename $targfile, $bak;
1793			open(SOURCE, "< $bak")
1794				or die errmsg("%s %s: %s\n", errmsg("open"), $bak, $!);
1795			open(TARGET, ">$targfile")
1796				or die errmsg("%s %s: %s\n", errmsg("create"), $bak, $!);
1797			local($/) = undef;
1798			my $page = <SOURCE>; close SOURCE;
1799
1800			$page =~ s/^#>>(.*)(__MVR_(\w+)__.*)\n\1.*/#>>$1$2/mg;
1801			$page =~ s/^#>>(.*__MVR_(\w+)__.*)/#>>$1\n$1/mg;
1802			1 while $page =~ s/^([^#].*)__MVR_(.*)/$1__MVC_$2/mg;
1803			$page =~ s/__MV[CS]_([A-Z0-9]+)__/$opt->{Substitute}{lc $1}/g;
1804
1805			print TARGET $page				or die "print $targfile: $!\n";
1806			close TARGET					or die "close $targfile: $!\n";
1807			unlink $bak						or die "unlink $bak: $!\n";
1808	}
1809
1810	chmod $perms, $targfile;
1811	$scale->('end') if $scale;
1812	umask $save_umask if $save_umask;
1813	return 1;
1814}
1815
1816sub debug {
1817	for(@_) {
1818		print DEBUG "$_\n";
1819	}
1820	return;
1821}
1822
1823sub copy_current_to_dir {
1824	my($target_dir, $exclude_pattern) = @_;
1825	return copy_dir('.', $target_dir, $exclude_pattern);
1826}
1827
1828sub copy_dir {
1829	my($source_dir, $target_dir, $exclude_pattern, $opt) = @_;
1830	return undef unless -d $source_dir;
1831	$opt = {} unless $opt;
1832	my $scale;
1833	if($scale = $opt->{scale_call}) {
1834		$scale->('start', $opt->{scale}, $opt->{message});
1835	}
1836	my $orig_dir;
1837	if($source_dir ne '.') {
1838		$orig_dir = cwd();
1839		chdir $source_dir or die "chdir: $!\n";
1840	}
1841	my @files;
1842	my $wanted = sub {
1843		return unless -f $_;
1844		my $name = $File::Find::name;
1845		$name =~ s:^\./::;
1846		return if $exclude_pattern and $name =~ m{$exclude_pattern}o;
1847		push (@files, $name);
1848	};
1849	File::Find::find($wanted, '.');
1850
1851	# also exclude directories that match $exclude_pattern
1852	@files = grep !m{$exclude_pattern}o, @files if $exclude_pattern;
1853	eval {
1854		for(@files) {
1855			install_file('.', $target_dir, $_, $opt);
1856		}
1857	};
1858	my $msg = $@;
1859	chdir $orig_dir if $orig_dir;
1860	die "$msg" if $msg;
1861	return 1;
1862}
1863
1864use vars q!$Prompt_sub!;
1865my $History_add;
1866my $History_set;
1867my $term;
1868eval {
1869	require Term::ReadLine;
1870	import Term::ReadLine;
1871
1872	$term = new Term::ReadLine 'Interchange Configuration';
1873	die "No Term::ReadLine" unless defined $term;
1874
1875	readline::rl_set('CompleteAddsuffix', 'Off');
1876	readline::rl_set('TcshCompleteMode', 'On');
1877	$Prompt_sub = sub {
1878		my ($prompt, $default) = @_;
1879		if($Force) {
1880			print "$prompt SET TO --> $default\n";
1881			return $default;
1882		}
1883		$prompt =~ s/^\s*(\n+)/print $1/ge;
1884		$prompt =~ s/\n+//g;
1885		readline::rl_bind('C-x', 'catch-cancel');
1886		readline::rl_bind('C-b', 'catch-backward');
1887		readline::rl_bind('C-y', 'catch-help');
1888		readline::rl_bind('C-f', 'catch-forward');
1889		if(! $Conf{vi_edit_mode}) {
1890			readline::rl_bind('"\M-\OP"', 'catch-help');
1891			readline::rl_bind('"\M-[20"', 'catch-backward');
1892			readline::rl_bind('"\M-[21"', 'catch-forward');
1893			#readline::rl_bind('"\M-[1"', 'catch-cancel');
1894			readline::rl_bind('"\M-[5"', 'catch-backward');
1895			readline::rl_bind('"\M-[6"', 'catch-forward');
1896		}
1897		my $out = $term->readline($prompt, $default);
1898		return "\cB" if ! defined $out;
1899		return $out;
1900	};
1901	$History_add = sub {
1902		my ($line) = @_;
1903		$term->addhistory($line)
1904			if $line =~ /\S/;
1905	};
1906	$History_set = sub {
1907		$term->SetHistory(@_);
1908	};
1909	$History = 1;
1910
1911};
1912
1913sub prompt {
1914	return &$Prompt_sub(@_)
1915		if defined $Prompt_sub;
1916	my($prompt) = shift || '? ';
1917	my($default) = shift;
1918	if($Force) {
1919		print "$prompt SET TO --> $default\n";
1920		return $default;
1921	}
1922	my($ans);
1923
1924	print $prompt;
1925	print "[$default] " if $default;
1926	local ($/) = "\n";
1927	chomp($ans = <STDIN>);
1928	length($ans) ? $ans : $default;
1929}
1930
1931sub addhistory {
1932	return '' unless defined $History_add;
1933	return $History_add->(@_);
1934}
1935
1936sub sethistory {
1937	return '' unless defined $History_set;
1938	return $History_set->(@_);
1939}
1940
1941sub do_msg {
1942	my ($msg, $size) = @_;
1943	$size = 60 unless defined $size;
1944	my $len = length $msg;
1945
1946	return "$msg.." if ($len + 2) >= $size;
1947	$msg .= '.' x ($size - $len);
1948	return $msg;
1949}
1950
1951sub add_catalog {
1952	my ($file, $directive, $configname, $value) = @_;
1953	if(! $file) {
1954		$file = "$Conf{relocate}$Global::ConfigFile";
1955	}
1956	$configname = $Conf{catalogname} if ! $configname;
1957	$directive  = 'Catalog'          if ! $directive;
1958	if (! $value) {
1959		$value = "$Conf{catalogname} $Conf{catroot} $Conf{cgiurl}";
1960		$value .= " $Conf{aliases}" if $Conf{aliases};
1961	}
1962	my ($newcfgline, $mark, @out);
1963	my ($tmpfile) = "$file.$$";
1964	if (-f $file) {
1965		rename ($file, $tmpfile)
1966			or die "Couldn't rename $file: $!\n";
1967	}
1968	else {
1969		File::Copy::copy("$file.dist", $tmpfile)
1970			or die errmsg("Couldn't find interchange.cfg");
1971	}
1972	open(CFG, "< $tmpfile")
1973		or die "Couldn't open $tmpfile: $!\n";
1974	$newcfgline = sprintf "%-19s %s\n", $directive, $value;
1975	while(<CFG>) {
1976		$mark = $. if /^#?\s*catalog\s+/i;
1977debug("\nDeleting old configuration $configname.\n") if s/^(\s*$directive\s+$configname\s+)/#$1/io;
1978		push @out, $_;
1979	}
1980	close CFG;
1981	open(NEWCFG, ">$file")
1982		or die "\nCouldn't write $file: $!\n";
1983	if (defined $mark) {
1984		print NEWCFG @out[0..$mark-1];
1985		print NEWCFG $newcfgline;
1986		print NEWCFG @out[$mark..$#out];
1987	}
1988	else {
1989		warn "\nNo $directive previously defined. Adding $configname at top.\n";
1990		print NEWCFG $newcfgline;
1991		print NEWCFG @out;
1992	}
1993	close NEWCFG || die errmsg("%s %s: %s\n", 'close', $file, $!);
1994	unlink $tmpfile;
1995}
1996
1997sub server_running {
1998	local ($/);
1999debug("in server_running, pid file=$Global::PIDfile");
2000	open(PID, "+< $Global::PIDfile")
2001		or return undef;
2002debug("opened PID file");
2003	if(Vend::Util::lockfile(\*PID, 1, 0)) {
2004debug("PID file not locked");
2005		## Daemon not running;
2006		close PID;
2007		return undef;
2008	}
2009	my $pid = <PID>;
2010debug("PID=$pid");
2011	$pid =~ /(\d+)/;
2012	$pid = $1;
2013	return $pid;
2014}
2015
2016sub run_catalog {
2017	my ($file, $directive, $configname, $value) = @_;
2018	$Conf{relocate}
2019		and die errmsg("Can't add catalog to running server when relocating.");
2020
2021	if(! $file) {
2022		my $fn = 'restart';
2023		$file  = "$Global::RunDir/$fn";
2024	}
2025
2026	$configname = $Conf{catalogname} if ! $configname;
2027	$directive  = 'Catalog'          if ! $directive;
2028	if (! $value) {
2029		$value = "$Conf{catalogname} $Conf{catroot} $Conf{cgiurl}";
2030		$value .= " $Conf{aliases}" if $Conf{aliases};
2031	}
2032	my $pid = server_running();
2033	if(! defined $pid) {
2034		die errmsg("Can't add %s to server: not running", $configname);
2035	}
2036
2037	open(RESTART, "<+$file")
2038		or open(RESTART, ">>$file")
2039			or die errmsg("%s %s: %s\n", errmsg("write"), $file, $!);
2040	Vend::Util::lockfile(\*RESTART, 1, 1)
2041			or die errmsg("%s %s: %s\n", errmsg("lock"), $file, $!);
2042	printf RESTART "%-19s %s\n", $directive, $value;
2043	Vend::Util::unlockfile(\*RESTART)
2044		or die errmsg("%s %s: %s\n", errmsg("unlock"), $file, $!);
2045	close RESTART;
2046	set_owner($file);
2047	kill 'HUP', $pid;
2048}
2049
2050my %Http_hash = (
2051					qw(
2052						scriptalias		1
2053						addhandler		1
2054						alias			1
2055					)
2056				);
2057
2058my %Http_process = (
2059						scriptalias		=> sub {
2060												my ($junk, $val) = @_;
2061												$val =~ s!/+$!!;
2062												return $val;
2063											},
2064				);
2065
2066my %Http_scalar = (
2067					qw(
2068						user			1
2069						group			1
2070						serveradmin		1
2071						resourceconfig	1
2072						documentroot	1
2073					)
2074				);
2075
2076
2077sub conf_parse_http {
2078	my ($file) = @_;
2079
2080	my $virtual = {};
2081	my $servers = {};
2082	my $newfile;
2083
2084	open(HTTPDCONF, "< $file")
2085		or do { $Error = "Can't open $file: $!"; return undef};
2086	local($/) = undef;
2087	my $data = <HTTPDCONF>;
2088	close(HTTPDCONF);
2089
2090
2091	if($data =~ s/^\s*resourceconfig\s+("?)(.*)\1//i) {
2092		$newfile = $2;
2093	}
2094
2095	unless(defined $newfile) {
2096		$newfile = $file;
2097		$newfile =~ s:[^/]+$::;
2098		$newfile .= 'srm.conf';
2099	}
2100
2101	SRMCONF: {
2102		if (-f $newfile) {
2103			open(HTTPDCONF, "< $newfile")
2104				or last SRMCONF;
2105			$data .= <HTTPDCONF>;
2106			close(HTTPDCONF);
2107		}
2108	}
2109
2110	$data =~ s!
2111				<virtualhost
2112				\s+
2113					([^>\n]+)
2114				\s*>\s+
2115					([\000-\377]*?)
2116				</virtualhost>!
2117				$virtual->{$1} = $2; ''!xieg;
2118
2119	$virtual->{' '} = $data;
2120
2121	my @data;
2122	my $servname;
2123	my $handle;
2124	my $main;
2125	foreach $handle (sort keys %$virtual) {
2126
2127		undef $servname;
2128		@data = split /[\r\n]+/, $virtual->{$handle};
2129		my $port = $handle;
2130		$port =~ s/.*:(\d+).*/$1/ or $port = '';
2131		@data = grep /^\s*[^#]/, @data;
2132		for(@data) {
2133			next unless /^\s*servername\s+(.*)/i;
2134			$servname = $1;
2135			$servname =~ s/\s+$//;
2136			if(defined $servers->{$servname} and $port) {
2137				$servname .= ":$port";
2138			}
2139			elsif(defined $servers->{$servname} and $port) {
2140				$Error = "Server $servname defined twice.";
2141				return undef;
2142			}
2143			$servers->{$servname} = {};
2144		}
2145
2146		if($handle eq ' ') {
2147			$servname = Sys::Hostname::hostname() unless $servname;
2148			$servname =~ s/\s+$//;
2149			$main = $servname;
2150			$servers->{$servname} = {} if ! $servers->{$servname};
2151			$servers->{$servname}{Master} = 1;
2152		}
2153		next unless $servname;
2154
2155		my $ref = $servers->{$servname};
2156
2157		$ref->{servername} = $servname;
2158
2159		foreach my $line (@data) {
2160			$line =~ s/^\s+//;
2161			$line =~ s/\s+$//;
2162			my ($key, $val);
2163			my ($directive,$param) = split /\s+/, $line, 2;
2164			$directive = lc $directive;
2165			if(defined $Http_hash{$directive}) {
2166				$ref->{$directive} = {}
2167					unless defined $ref->{$directive};
2168				my ($key,$val) = split /\s+/, $param, 2;
2169				$val =~ s/^\s*"// and $val =~ s/"\s*$//;
2170				if (defined $Http_process{$directive}) {
2171					$key = $Http_process{$directive}->('key', $key);
2172					$val = $Http_process{$directive}->('value', $val);
2173				}
2174				$ref->{$directive}{$key} = $val;
2175			}
2176			elsif(defined $Http_scalar{$directive}) {
2177				$param =~ s/^"// and $param =~ s/"\s*$//;
2178				if (defined $ref->{$directive}) {
2179					undef $ref;
2180					$Error = "$directive defined twice in $servname, only allowed once.";
2181					return undef;
2182				}
2183				if (defined $Http_process{$directive}) {
2184					$param = $Http_process{$directive}->($param);
2185				}
2186				$ref->{$directive} = $param;
2187			}
2188		}
2189	}
2190
2191	return $servers;
2192}
2193
2194sub substitute_cryptpw {
2195	my $pw = $Conf{cryptpw};
2196	return unless $pw;
2197	return if $Conf{alreadycrypt}++;
2198	my @letters = ('A' .. 'Z', 'a' .. 'z');
2199	my $salt = $letters[ int rand(scalar @letters) ];
2200	$salt .= $letters[ int rand(scalar @letters) ];
2201	$Conf{cryptpw} = crypt($pw, $salt);
2202}
2203
2204sub unique_ary {
2205	my %seen;
2206	%seen = ();
2207	return ( grep !$seen{$_}++, @_ );
2208}
2209
2210sub resolve_owner {
2211	my $cref = shift || \%Conf;
2212	die errmsg("Usage: %s", "resolve_owner({ })")
2213		unless ref $cref eq 'HASH';
2214	return unless $> == 0 || $cref->{asroot};
2215	my @things = qw/interchangeuser interchangegroup catuser catgroup/;
2216	my ($icu, $icg, $catu, $catg) = @$cref{@things};
2217
2218	$catu = $icu if ! $catu;
2219
2220	# Default groups
2221	my $icd;
2222	my $catd;
2223
2224	my($icu_uid, $catu_uid, $icg_gid, $catg_gid);
2225	$icu_uid = getpwnam($icu)
2226		or die errmsg("User does not exist: %s\n", $icu);
2227	$catu_uid = getpwnam($catu)
2228		or die errmsg("User does not exist: %s\n", $catu);
2229
2230	if($cref->{permtype} =~ /^\s*m/i) {
2231		$icg_gid = (getpwnam($catu))[3] if ! $icg;
2232		$catg_gid = (getpwnam($catu))[3];
2233	}
2234	elsif($cref->{permtype} =~ /^\s*g/i) {
2235		$icg_gid = (getpwnam($icu))[3] if ! $icg;
2236		$catg_gid = (getpwnam($icu))[3];
2237	}
2238	else {
2239		$icg_gid = (getpwnam($catu))[3] if ! $icg;
2240		$catg_gid = (getpwnam($catu))[3];
2241	}
2242	$icg_gid = (getpwnam($icu))[3] if ! $icg_gid;
2243	$catg_gid = (getpwnam($catu))[3] if ! $catg_gid;
2244
2245	@$cref{qw/
2246			interchangeuid
2247			interchangegid
2248			catuid
2249			catgid
2250			/} = ($icu_uid, $icg_gid, $catu_uid, $catg_gid);
2251	return $cref;
2252}
2253
2254sub hammer_symlinks {
2255	my $dir = shift;
2256	File::Find::find(
2257					sub {
2258						return if ! -l $_;
2259						unlink $_
2260							or die "couldn't unlink $File::Find::name: $!\n";
2261					},
2262					$dir,
2263	 );
2264	 return 1;
2265}
2266
2267
2268
2269sub check_root_execute {
2270	my $dir = shift;
2271	return undef if ! -d $dir;
2272	my @disc;
2273	my $wanted = sub {
2274		my @stat = stat($_);
2275		my $type = -d _ ? 'directory' : 'file';
2276		push @disc, [ $type, $File::Find::name, l('not owned by root')]
2277			if $stat[4] != 0;
2278		push @disc, [ $type, $File::Find::name, l('world writable')   ]
2279			if (07777 & $stat[2] & 02);
2280		push @disc, [ $type, $File::Find::name, l('group writable')   ]
2281			if (07777 & $stat[2] & 020);
2282	};
2283
2284	File::Find::find($wanted, $dir);
2285	return 1 if ! @disc;
2286	my $out = "";
2287	for (@disc) {
2288		$_->[1] =~ s!^$dir/!!;
2289		$out .= errmsg("  %s %s is %s\n", @$_);
2290	}
2291	return $out;
2292}
2293
2294sub compile_link {
2295	my $cref = shift || \%Conf;
2296	for( qw/linkmode cgiurl vendroot cgidir cgiurl/) {
2297		die errmsg("improper reference passed, missing: %s", $_)
2298			if ! $cref->{$_};
2299	}
2300	return 1 if $cref->{linkmode} =~ /^\s*n/i;
2301	my @args;
2302	my $cginame = $cref->{cgiurl};
2303	$cginame =~ s:.*/::;
2304	$cref->{cgifile} = $cginame = "$cref->{relocate}$cref->{cgidir}/$cginame";
2305	die errmsg("%s %s: %s", 'target file', $cref->{cgifile}, 'is a directory')
2306		if -e $cref->{cgifile};
2307	my $exec = "$cref->{relocate}$cref->{vendroot}/bin/compile_link";
2308	die errmsg("%s %s: %s", 'executable file', $exec, 'not executable')
2309		if ! -x $exec;
2310	push @args, (
2311			$cref->{linkmode} =~ /^\s*u/i
2312			? '--unixmode'
2313			: '--inetmode'
2314		);
2315	push @args, "--source=$cref->{relocate}$cref->{vendroot}/src";
2316	push @args, "--outputfile=$cref->{relocate}$cref->{cgifile}";
2317	push @args, "--port=$cref->{linkport}"
2318		if $cref->{linkport};
2319	push @args, "--host=$cref->{linkhost}"
2320		if $cref->{linkhost};
2321	push @args, "--nosuid"
2322		if $cref->{cgiwrap};
2323	push @args, "--nosuid"
2324		if $cref->{cgiwrap};
2325	for (@args) {
2326		die errmsg("Improper argument: %s", $_)
2327			if /"/;
2328		$_ = qq{"$_"};
2329	}
2330	my $dir = $ENV{TMP} || '/tmp';
2331	my $bdir    = "$dir/compile_link.$$";
2332	my $outfile = "$bdir/build.out";
2333	my $errfile = "$bdir/build.err";
2334	File::Path::mkpath($bdir);
2335	push @args, "--build=$bdir";
2336
2337	system join " ",
2338			   $exec,
2339			   @args,
2340			   "2>$errfile",
2341			   ">$outfile";
2342
2343	if($?) {
2344		my $msg = `cat $errfile`;
2345		die errmsg("Failed to compile and copy link:\n\n%s", $msg);
2346	}
2347	File::Path::rmtree($bdir);
2348	unlink $errfile;
2349	unlink $outfile;
2350	return 1;
2351}
2352
2353my @Action;
2354
2355sub evaluate_action {
2356	my $act = shift;
2357	ref($act) eq 'HASH' or die "usage: evaluate_action(\%action)";
2358	my $orig_dir;
2359	my $error;
2360	eval {
2361		if($act->{chdir}) {
2362			$orig_dir = cwd();
2363			my $dir = $act->{chdir};
2364			$dir = substitute($dir) if $dir =~ /__MVC_/;
2365			chdir $dir
2366				or die errmsg("Unable to change directory to %s.", $dir) . "\n";
2367		}
2368		if($act->{from_dir} and $act->{to_dir}) {
2369			if($Conf{relocate}) {
2370				$act->{to_dir} = "$Conf{relocate}$act->{to_dir}";
2371				$act->{from_dir} = "$Conf{relocate}$act->{from_dir}";
2372			}
2373			copy_dir($act->{from_dir}, $act->{to_dir}, undef, $act);
2374			if($act->{delete_from}) {
2375				File::Path::rmtree($act->{from_dir});
2376			}
2377		}
2378		if(my $sub = $act->{sub}) {
2379			my $args = $act->{args} || [];
2380			$sub->(@$args);
2381		}
2382		if(my $cmd = $act->{command}) {
2383			$cmd = substitute($cmd) if $cmd =~ /__MVC_/;
2384			system $cmd;
2385			if($?) {
2386				my $status = $? >> 8;
2387				die errmsg(
2388						"Command %s returned status %s: %s",
2389						$cmd,
2390						$status,
2391						$!,
2392					) . "\n";
2393			}
2394		}
2395	};
2396	$error = $@ if $@;
2397	chdir $orig_dir if $orig_dir;
2398	die $error if $error;
2399	return;
2400}
2401
2402sub build_cat {
2403	my ($scale, $die, $warn, $opt) = @_;
2404debug("build_cat called scalesub=$scale");
2405
2406	$opt ||= {};
2407	(
2408		$scale  && ! ref $scale eq 'CODE'
2409			or
2410		$die    && ! ref $die   eq 'CODE'
2411			or
2412		$warn   && ! ref $warn  eq 'CODE'
2413			or
2414		$opt    && ! ref $opt   eq 'HASH'
2415	) and die errmsg("usage: %s", 'build_cat(\&scale,\&die,\&warn,$hashref)');
2416
2417	$die  = sub { die  errmsg(@_) . "\n"; } if ! $die;
2418	$warn = sub { die  errmsg(@_) . "\n"; } if ! $die;
2419
2420	my $cref = $opt->{configuration} || \%Conf;
2421
2422	my @action;
2423
2424#	Here we create an array of hashes. The elements:
2425#	structure is:
2426#
2427#		from_dir       =>  directory to copy from (done before sub)
2428#		to_dir         =>  directory to copy to
2429#		delete_from    =>  delete from_dir when finished
2430#		sub		       =>  subroutine to run
2431#		args           =>  subroutine args
2432#		message        =>  message for scale routine
2433#		scale          =>  value to be added to scale when done
2434#		error          =>  Error message if fails
2435#		error_ok       =>  Ignore error if it occurs
2436#		error_warn     =>  Issue conditional warning if error
2437#                          (dies if in batch mode)
2438#
2439#   If "action_ref" option key is provided, it is used instead. (Unlikely
2440#   ever to be used, obviously.)
2441
2442	CREATEACTION: {
2443		if($opt->{action_ref}) {
2444			@action = @{$opt->{action_ref}};
2445			last CREATEACTION;
2446		}
2447
2448		push @action, {
2449					sub => \&substitute_cryptpw,
2450					message => errmsg('Encrypting passwords'),
2451					scale => 1,
2452				};
2453
2454		push @action, {
2455					sub => \&compile_link,
2456					args => [ $cref ],
2457					message => errmsg('Compiling link programs'),
2458					scale => 4,
2459				};
2460
2461		push @action, {
2462				sub => sub {
2463					hammer_symlinks("$Conf{relocate}$Conf{catroot}"),
2464				},
2465				message => errmsg("Cleaning up catalog directory"),
2466				scale => 1,
2467		} if -d "$Conf{relocate}$Conf{catroot}";
2468
2469		if(my $wref = $Window{precopy_commands}) {
2470			$wref->{contents} ||= [];
2471			for(@{$wref->{contents}}) {
2472				my $cref = $Content{$_};
2473				$cref->{scale} = 1 unless defined $cref->{scale};
2474				$cref->{message} = "Running $cref->{command}"
2475					unless $cref->{message};
2476				$cref->{error_warn} = 1
2477					unless $cref->{error_ok};
2478				if(! $cref->{conditional} or $cref->{conditional}->()) {
2479					push @action, $cref;
2480				}
2481			}
2482		}
2483		push @action, {
2484					from_dir	=> "$Conf{vendroot}/$Conf{demotype}",
2485					to_dir		=>  $Conf{catroot},
2486					dmode		=>  02770,
2487					fmode		=>  0660,
2488					Substitute  =>  \%Conf,
2489					error 		=>  $Build_error{demotype},
2490					message		=>  errmsg("Copying base demo skeleton"),
2491					scale		=>  3,
2492				};
2493
2494		push @action, {
2495					delete_from =>  1,
2496					dmode		=>  0775,
2497					error 		=>  $Build_error{demotype},
2498					fmode		=>  0664,
2499					from_dir	=>  "$Conf{catroot}/html",
2500					message		=>  errmsg("Copying public HTML files"),
2501					scale		=>  1,
2502					to_dir		=>  $Conf{samplehtml},
2503				};
2504
2505		push @action, {
2506					delete_from =>  1,
2507					dmode		=>  0775,
2508					error 		=>  $Build_error{demotype},
2509					fmode		=>  0664,
2510					from_dir	=>  "$Conf{catroot}/images",
2511					message		=>  errmsg("Copying image files"),
2512					scale		=>  2,
2513					symlink_to  =>  1,
2514					to_dir		=>  $Conf{imagedir},
2515				};
2516
2517		if(my $wref = $Window{postcopy_commands}) {
2518			$wref->{contents} ||= [];
2519			for(@{$wref->{contents}}) {
2520				my $cref = $Content{$_};
2521				$cref->{scale} = 1 unless defined $cref->{scale};
2522				$cref->{chdir} = $Conf{catroot} unless $cref->{chdir};
2523				$cref->{message} = "Running $cref->{command}"
2524					unless $cref->{message};
2525				$cref->{error_warn} = 1
2526					unless $cref->{error_ok};
2527				if(! $cref->{conditional} or $cref->{conditional}->()) {
2528					push @action, $cref;
2529				}
2530			}
2531		}
2532
2533		push @action, {
2534				sub => \&add_catalog,
2535				message		=>  errmsg("Adding catalog to interchange.cfg"),
2536				scale => 1,
2537		} if $cref->{add_catalog};
2538debug("run_catalog=$cref->{run_catalog} server_running=" . server_running());
2539		push @action, {
2540				sub => \&run_catalog,
2541				message		=>  errmsg("Running catalog"),
2542				scale => 1,
2543		} if $cref->{run_catalog} and server_running();
2544	}
2545	my $total_scale = 0;
2546	foreach my $act (@action) {
2547		$total_scale += $act->{scale};
2548	}
2549
2550debug("total scale amount=$total_scale scalesub=$scale");
2551	## install_scale returns a closure implementing whatever scale
2552	## there is....
2553	my $msg = errmsg("Installing catalog: %s", $Conf{catalogname});
2554	my $scale_call;
2555debug("scale_call=$scale_call");
2556	if(! $opt->{event_driven}) {
2557		$scale_call = $scale->($total_scale, $msg)
2558			if $scale;
2559		foreach my $act (@action) {
2560debug("action: " . uneval($act));
2561			$scale_call->('start', $act->{scale}, errmsg($act->{message}))
2562				if $scale_call;
2563			#select(undef,undef,undef, .75);
2564			my $orig_dir;
2565			eval {
2566				evaluate_action($act);
2567			};
2568			if(! $@) {
2569				$scale_call->('end')
2570					if $scale_call;
2571			}
2572			elsif($act->{error_ok}) {
2573debug("action error_ok: $@");
2574				my $msg = errmsg($act->{message}) . "..." . errmsg('failed') . ".";
2575				$scale_call->('end', undef, $msg)
2576					if $scale_call;;
2577			}
2578			elsif($act->{error_warn}) {
2579debug("action error_warn: $@");
2580				my $msg = $@;
2581				$warn->($msg)
2582					or do {
2583						$die->($msg);
2584						return undef;
2585					};
2586			}
2587			else {
2588debug("action fatal_error: $@");
2589				$die->( errmsg("Error installing catalog %s: %s"));
2590				return undef;
2591			}
2592			chdir $orig_dir if $orig_dir;
2593		}
2594		$scale_call->('finish')
2595			if $scale_call;
2596	}
2597	elsif($scale) {
2598		eval {
2599			$scale->($total_scale, $msg, \&evaluate_action, @action);
2600		};
2601		if($@) {
2602debug("action fatal_error: $@");
2603			$die->( errmsg("Error installing catalog %s: %s"));
2604			return undef;
2605		}
2606	}
2607	else {
2608		die "Must have scale subroutine call if event-driven\n";
2609	}
2610
2611}
2612
2613package readline;
2614
2615use vars qw/$AcceptLine/;
2616
2617sub discard_ReadKey {
2618	return unless $Term::ReadKey::VERSION;
2619	my $timeout = shift || '-1';
2620	local($^W);
2621	eval {
2622			Term::ReadKey::ReadKey(-1, $readline::term_IN);
2623	};
2624}
2625
2626sub F_CatchHelp {
2627		$AcceptLine = "\cY";
2628}
2629
2630sub F_CatchCancel {
2631		$AcceptLine = "\cX";
2632		discard_ReadKey(1);
2633}
2634
2635sub F_CatchBackward {
2636		$AcceptLine = "\cB";
2637		discard_ReadKey(1);
2638}
2639
2640sub F_CatchForward {
2641		$AcceptLine = "\cF";
2642		discard_ReadKey(1);
2643}
2644
26451;
2646__END__
2647