summaryrefslogtreecommitdiff
path: root/regress/tinymta/loader.in
blob: b412297c9383ca323a40cce4294d20d44a9dde21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#! @PERL@ -w
#-*- perl -*-
#
# Copyright (C) 2000-2002,2004,2006 Ken'ichi Fukamachi
#          All rights reserved.
#
# $FML: loader.in,v 1.1.1.1 2006/06/10 01:05:10 fukachan Exp $
#
# derived from fml8 loader.in 1.13.
#

eval 'exec @PERL@ -S $0 ${1+"$@"}'
        if $running_under_some_shell;

use vars qw($running_under_some_shell $hints $ERROR_EXIT_CODE);
use strict;
use IO::File;

# reset PATH in the early stage
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

# XXX irregular global variable permitted to handle emergency cases.
# default exit code in error.
$ERROR_EXIT_CODE = 1;

=head1 NAME

loader -- top level wrapper to load and start a real TinyMTA program.

=head1 SYNOPSIS

loader C<[-c main.cf]> [program specific options]

=head1 DESCRIPTION

Perl modules C<fml> uses are dependent on fml version.
C<loader> resolves fml version dependence by
@sysconfdir@/main.cf,
set up proper @INC and load C<FML::Process::Switch>.

=head1 COMMAND LINE OPTIONS

C<-c main.cf>
    main.cf alternative

=head1 METHOD

=head2 Bootstrap( main_cf )

top level loader.

=cut


# Descriptions: top lebel bootstrap program
#               which load a dispather program (process_switch)
#               for process switch. The flow of execution follows:
#               libexec/loader ->
#                  libexec/process_switch ->
#                      FML::Process::Something
#    Arguments: none
#               XXX this program sees $0
#                   (program name, == argv[0] of C language)
# Side Effects: switch to the real process
# Return Value: none
sub init
{
    my ($main_cf_default, $config_cf_file) = @_;

    # 1. main.cf exists and I can open it?
    unless (-f $main_cf_default) {
	__CROAK("cannot find $main_cf_default");
    }
    my $fh = new IO::File $main_cf_default, "r";
    unless (defined $fh) {
	__CROAK("cannot open $main_cf_default");
    }

    # 1.1 parse command line options (preliminary).
    #     we check @ARGV again after by getopt().
    my $main_cf_file = $main_cf_default; # main.cf by default
    for (my $i = 0; $i <= $#ARGV; $i++) {
	# -c main.cf
	if ($ARGV[ $i ] =~ /^\-c$/) {
	    $main_cf_file = $ARGV[$i + 1];
	}
    }

    # 2.1 o.k. try to load main.cf (1st pass) to resolve @INC
    my $main_cf = loader_read_main_cf($main_cf_file);

    # 2.1.1 set up @INC to load FML::Process::Switch
    if (defined $main_cf->{ lib_dir }) {
	push(@INC, split(/\s+/, $main_cf->{ lib_dir }));
    }
    else {
	__CROAK("\$lib_dir not defined in main.cf");
    }

    # 2.1.2 inherit some parameters to change behaviour
    $main_cf->{ _hints } = $hints;

    # arguments to pass off to bootstrap().
    return ($main_cf, $config_cf_file);
}


# Descriptions: dispatch.
#    Arguments: OBJ($main_cf) STR($config_cf_file)
# Side Effects: none
# Return Value: none
sub bootstrap
{
    my ($main_cf, $config_cf_file) = @_;

    # 3. execute
    eval {
      main::dispatch($main_cf, $config_cf_file);
    };
    if ($@) {
	my $reason = $@;
	$reason =~ s/[\n\s]*\s+at\s+.*$//m;
	__CROAK("cannot call main::dispatch()", $reason);
    }
}


=head2 loader_read_main_cf(cf_file)

load "key = value" style configuration file and build a hash.
return HASH REFERENCE.

   my $main_cf = loader_read_main_cf($main_cf_file, $params);

where $param is optional.

=cut


# Descriptions: load "key = value" style configuration.
#               It is available to use the following style.
#                    key = value1 value2
#                          value3
#               XXX This file is non-Object Oriented style but
#               XXX this is minimum module used in standalone program.
#    Arguments: $file $params
#               $params is 'key1=value1 key2=value2' syntax.
# Side Effects: $config (hash reference) is allocated on memory here.
# Return Value: hash reference to configuration parameters
sub loader_read_main_cf
{
    my ($file) = @_;
    my $config = {};

    my $fh = new IO::File $file, "r";

    if (defined $fh) {
	my $curkey = '';
	while (<$fh>) {
	    next if /^\#/;
	    chomp;

	    if (/^([A-Za-z]\w+)\s+=\s*(.*)/) {
		my ($key, $value) = ($1, $2);
		$curkey           = $key;
		$config->{$key}   = $value;
	    }
	    if (/^\s+(.*)/) {
		$config->{ $curkey }  .= " ". $1;
	    }
	}
	$fh->close;
    }
    else {
	__CROAK("Error: cannot open $file");
    }

    loader_expand_variables( $config );
    return $config;
}


# Descriptions: expand $var to the value of $var.
#    Arguments: $ref_to_config
# Side Effects: rewrite the given $config
# Return Value: none
sub loader_expand_variables
{
    my ($config) = @_;
    my (@order) = keys %$config;

    # check whether the variable definition is recursive.
    # For example, definition "var_a = $var_a/b/c" causes a loop.
    for my $x ( @order ) {
	if ((defined $x) && defined ($config->{ $x })) {
	    if ($config->{ $x } =~ /\$$x/) {
		__CROAK("loop1: definition of $x is recursive");
	    }
	}
    }

    # main expansion loop
    my $org = '';
    my $max = 0;
  KEY:
    for my $x ( @order ) {
	next KEY unless defined($config->{ $x });
	next KEY if $config->{ $x } !~ /\$/o;

	# we need a loop to expand nested variables, for example,
	# a = $x/y and b = $a/c/0
	#
	$max = 0;
      EXPANSION_LOOP:
	while ($max++ < 16) {
	    $org = $config->{ $x };

	    if ($config->{ $x } =~ /\{/) { # expand ${prefix}/xxx ...
		$config->{ $x } =~ s/\$\{([a-z_]+)\}/$config->{$1}/g;
	    }
	    $config->{ $x } =~ s/\$([a-z_]+)/$config->{$1}/g;

	    last EXPANSION_LOOP if $config->{ $x } !~ /\$/o;
	    last EXPANSION_LOOP if $org eq $config->{ $x };

	    if ($config->{ $x } =~ /\$$x/) {
		__CROAK("loop2: definition of $x is recursive");
	    }
        }

	if ($max >= 16) {
	    __CROAK("variable expansion of $x causes infinite loop");
	}
    }
}


# Descriptions: print error reason
#    Arguments: STR($reason) STR($detail)
# Side Effects: print out error reason and exit here
# Return Value: none
sub __CROAK
{
    my ($reason, $detail) = @_;

    print STDERR "fml loader error: $reason\n";
    print STDERR "  reason(detail): $detail\n" if defined $detail;
    exit($ERROR_EXIT_CODE);
}



#
# MAIN
#

# main routine to boot off
my (@argv) = ();
BEGIN {
    my $prefix      = "@prefix@";
    my $exec_prefix = "@exec_prefix@";
    my $main_cf     = "@fmlconfdir@/main.cf";
    my $config_cf   = "@fmlconfdir@/tinymta.cf";

    (@argv) = init($main_cf, $config_cf);
}

# not use Carp.pm to be quiet if needed
eval q{ bootstrap(@argv); };
if ($@) { print STDERR "Error: ", $@, "\n"; exit($ERROR_EXIT_CODE);}

exit(0);


=head1 SEE ALSO

=head1 AUTHOR

Ken'ichi Fukamachi

=head1 COPYRIGHT

Copyright (C) 2000-2002,2004,2006 Ken'ichi Fukamachi

All rights reserved. This program is free software; you can
redistribute it and/or modify it under the same terms as Perl itself.

=head1 HISTORY

libexec/loader appeared in fml8 mailing list driver package.
See C<http://www.fml.org/> for more details.

=cut


1;