PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/perllib/mySociety/Config.pm

https://github.com/mysociety/commonlib
Perl | 315 lines | 236 code | 57 blank | 22 comment | 24 complexity | e43772b2af814888d3e3ea5ba02ebbc1 MD5 | raw file
  1. #!/usr/bin/perl
  2. #
  3. # mySociety/Config.pm:
  4. # Very simple config parser. Our config files are sort of cod-PHP.
  5. #
  6. # Copyright (c) 2004 UK Citizens Online Democracy. All rights reserved.
  7. # WWW: http://www.mysociety.org/
  8. package mySociety::Config;
  9. use strict;
  10. use IO::Handle;
  11. use IO::Pipe;
  12. use Error qw(:try);
  13. use Data::Dumper;
  14. use POSIX ();
  15. use YAML ();
  16. =head1 NAME
  17. mySociety::Config
  18. =head1 SYNOPSIS
  19. mySociety::Config::set_file('../conf/general');
  20. my $opt = mySociety::Config::get('CONFIG_VARIABLE', DEFAULT_VALUE);
  21. =head1 DESCRIPTION
  22. Parse config files (written in a sort of cod-php, using
  23. define(OPTION_VALUE_NAME, "value of option");
  24. to define individual elements.
  25. =head1 FUNCTIONS
  26. =over 4
  27. =cut
  28. my $php_path;
  29. # find_php
  30. # Try to locate the PHP binary in various sensible places.
  31. sub find_php () {
  32. $ENV{PATH} ||= '/bin:/usr/bin';
  33. foreach my $dir (split(/:/, $ENV{PATH}),
  34. qw(/usr/local/bin /usr/bin /software/bin /opt/bin /opt/php/bin)) {
  35. foreach my $name (qw(php php-cgi)) {
  36. return "$dir/$name" if (-x "$dir/$name");
  37. }
  38. }
  39. throw Error::Simple "unable to locate PHP binary, needed to read config file";
  40. }
  41. # read_config_from_yaml
  42. # Read configuration data from the named YAML configuration file
  43. sub read_config_from_yaml($) {
  44. my ($f) = @_;
  45. my $conf = YAML::LoadFile($f);
  46. if (ref($conf) ne "HASH") {
  47. throw Error::Simple "$f: The YAML file must represent an object (a.k.a. hash, dict, map)";
  48. }
  49. return $conf;
  50. }
  51. # read_config_from_php
  52. # Read configuration data from the named PHP configuration file
  53. sub read_config_from_php($) {
  54. my ($f) = @_;
  55. if (! -r $f) {
  56. throw Error::Simple "$f: permission denied trying to read config file (maybe you're not running as the correct user?)";
  57. }
  58. my $old_SIGCHLD = $SIG{CHLD};
  59. $SIG{CHLD} = sub { };
  60. # We need to find the PHP binary.
  61. $php_path ||= find_php();
  62. # This is a bit miserable. We ought to use IPC::Open2 or similar, but
  63. # can't because of a nasty interaction with the tied filehandles which
  64. # FCGI.pm uses.
  65. my ($inr, $inw, $outr, $outw);
  66. $inr = new IO::Handle();
  67. $inw = new IO::Handle();
  68. $outr = new IO::Handle();
  69. $outw = new IO::Handle();
  70. my $p1 = new IO::Pipe($outr, $outw);
  71. my $p2 = new IO::Pipe($inr, $inw);
  72. my $pid = fork();
  73. throw Error::Simple "fork: $!" unless (defined($pid));
  74. if ($pid == 0) {
  75. # Delete everything from the environment other than our special
  76. # variable to give PHP the config file name. We don't want PHP to pick
  77. # up other information from our environment and turn into an FCGI
  78. # server or something.
  79. %ENV = ( MYSOCIETY_CONFIG_FILE_PATH => $f );
  80. $inw->close();
  81. $outr->close();
  82. POSIX::close(0);
  83. POSIX::close(1);
  84. POSIX::dup2($inr->fileno(), 0);
  85. POSIX::dup2($outw->fileno(), 1);
  86. $inr->close();
  87. $outw->close();
  88. exec($php_path) or throw Error::Simple "$php_path: exec: $!";
  89. }
  90. $inr->close();
  91. $outw->close();
  92. $inw->print(<<'EOF');
  93. <?php
  94. $b = get_defined_constants();
  95. require(getenv("MYSOCIETY_CONFIG_FILE_PATH"));
  96. $a = array_diff_assoc(get_defined_constants(), $b);
  97. print "start_of_options\n";
  98. foreach ($a as $k => $v) {
  99. print preg_replace("/^OPTION_/", "", $k); /* strip off "OPTION_" if there */
  100. print "\0";
  101. print $v;
  102. print "\0";
  103. }
  104. ?>
  105. EOF
  106. $inw->close();
  107. # skip any header material
  108. my $line;
  109. while (defined($line = $outr->getline())) {
  110. last if ($line eq "start_of_options\n");
  111. }
  112. if (!defined($line)) {
  113. if ($outr->error()) {
  114. throw Error::Simple "$php_path: $f: $!";
  115. } else {
  116. throw Error::Simple "$php_path: $f: no option output from subprocess";
  117. }
  118. }
  119. # read remainder
  120. my $buf = join('', $outr->getlines());
  121. $outr->close();
  122. my @vals = split(/\0/, $buf, -1); # option values may be empty
  123. pop(@vals); # The buffer ends "\0" so there's always a trailing empty value
  124. # at the end of the buffer. I love perl! Perl is my friend!
  125. throw Error::Simple "$php_path: $f: bad option output from subprocess" if (scalar(@vals) % 2);
  126. waitpid($pid, 0);
  127. if ($?) {
  128. if ($? & 127) {
  129. throw Error::Simple "$php_path: killed by signal " . ($? & 127);
  130. } else {
  131. throw Error::Simple "$php_path: exited with failure status " . ($? >> 8);
  132. }
  133. }
  134. # Restore signal handler.
  135. $old_SIGCHLD ||= 'DEFAULT';
  136. $SIG{CHLD} = $old_SIGCHLD;
  137. my %config = @vals;
  138. return \%config;
  139. }
  140. =item read_config FILE [DEFAULTS]
  141. Read configuration from FILE.
  142. If the filename contains .yml, or FILE.yml exists, that file is parsed as
  143. a YAML object which is returned. Otherwise FILE is parsed by PHP, and any defines
  144. are extracted as config values.
  145. For PHP configuration files only, "OPTION_" is removed from any names
  146. beginning with that.
  147. If specified, values from DEFAULTS are merged.
  148. =cut
  149. sub read_config ($;$) {
  150. my ($f, $defaults) = @_;
  151. my $config;
  152. if ($f =~ /\.yml/) {
  153. $config = read_config_from_yaml($f);
  154. } elsif (-f "$f.yml") {
  155. if (-e $f) {
  156. throw Error::Simple "Configuration error: both $f and $f.yml exist (remove one)";
  157. }
  158. $config = read_config_from_yaml("$f.yml");
  159. } else {
  160. $config = read_config_from_php($f);
  161. }
  162. if ($defaults) {
  163. $config->{$_} = $defaults->{$_} foreach (keys %$defaults);
  164. }
  165. $config->{"CONFIG_FILE_NAME"} = $f;
  166. return $config;
  167. }
  168. =item set_file FILENAME
  169. Sets the default configuration file, used by mySociety::Config::get.
  170. =cut
  171. my $main_config_filename;
  172. sub set_file ($) {
  173. ($main_config_filename) = @_;
  174. }
  175. =item load_default
  176. Loads and caches default config file, as set with set_file. This
  177. function is implicitly called by get and get_all.
  178. =cut
  179. my %cached_configs;
  180. sub load_default() {
  181. my $filename = $main_config_filename;
  182. throw Error::Simple "Please call mySociety::Config::set_file to specify config file" if (!defined($filename));
  183. if (!defined($cached_configs{$filename})) {
  184. $cached_configs{$filename} = read_config($filename);
  185. }
  186. return $cached_configs{$filename};
  187. }
  188. =item get KEY [DEFAULT]
  189. Returns the constants set for KEY from the configuration file specified in
  190. set_config_file. The file is automatically loaded and cached. An exception is
  191. thrown if the value isn't present and no DEFAULT is specified.
  192. =cut
  193. sub get ($;$) {
  194. my ($key, $default) = @_;
  195. my $config = load_default();
  196. if (exists($config->{$key})) {
  197. return $config->{$key};
  198. } elsif (@_ == 2) {
  199. return $default;
  200. } else {
  201. throw Error::Simple "No value for '$key' in '" . $config->{'CONFIG_FILE_NAME'} . "', and no default specified";
  202. }
  203. }
  204. sub get_list {
  205. my (%searches) = @_;
  206. # example of usage get_list('startswith' => 'SMS');
  207. # returns a ref to a hash of config values
  208. my $config = load_default();
  209. my $regexp = '';
  210. if ($searches{'startswith'}) {
  211. $regexp = qr/^$searches{'startswith'}/;
  212. }
  213. if ($searches{'endswith'}) {
  214. $regexp = qr/$searches{'endswith'}$/;
  215. }
  216. if ($regexp) {
  217. my $conf_subset = {};
  218. foreach my $key (keys %$config) {
  219. if ($key =~ $regexp) {
  220. $conf_subset->{$key} = $config->{$key};
  221. }
  222. }
  223. return $conf_subset;
  224. } else {
  225. return $config;
  226. }
  227. return {};
  228. }
  229. =item test_run/set
  230. set allows you to change config variables at runtime. As this shouldn't
  231. normally be allowed, and is only for the test suites, you have to call a
  232. special function test_run first, to confirm you want to do this. set
  233. then works as you'd expect, but must come after at least one get.
  234. =cut
  235. my $test_run;
  236. sub test_run() {
  237. $test_run = 1;
  238. }
  239. sub set($$) {
  240. return unless $test_run;
  241. my ($key, $value) = @_;
  242. $cached_configs{$main_config_filename}{$key} = $value;
  243. }
  244. 1;