Util.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. # ------------------------------------------------------------------------------
  2. # NAME
  3. # Fcm::Util
  4. #
  5. # DESCRIPTION
  6. # This is a package of misc utilities used by the FCM command.
  7. #
  8. # COPYRIGHT
  9. # (C) Crown copyright Met Office. All rights reserved.
  10. # For further details please refer to the file COPYRIGHT.txt
  11. # which you should have received as part of this distribution.
  12. # ------------------------------------------------------------------------------
  13. use warnings;
  14. use strict;
  15. package Fcm::Util;
  16. require Exporter;
  17. our @ISA = qw{Exporter};
  18. sub expand_tilde;
  19. sub e_report;
  20. sub find_file_in_path;
  21. sub get_command_string;
  22. sub get_rev_of_wc;
  23. sub get_url_of_wc;
  24. sub get_url_peg_of_wc;
  25. sub get_wct;
  26. sub is_url;
  27. sub is_wc;
  28. sub print_command;
  29. sub run_command;
  30. sub svn_date;
  31. sub tidy_url;
  32. sub touch_file;
  33. sub w_report;
  34. our @EXPORT = qw{
  35. expand_tilde
  36. e_report
  37. find_file_in_path
  38. get_command_string
  39. get_rev_of_wc
  40. get_url_of_wc
  41. get_url_peg_of_wc
  42. get_wct
  43. is_url
  44. is_wc
  45. print_command
  46. run_command
  47. svn_date
  48. tidy_url
  49. touch_file
  50. w_report
  51. };
  52. # Standard modules
  53. use Carp;
  54. use Cwd;
  55. use File::Basename;
  56. use File::Find;
  57. use File::Path;
  58. use File::Spec;
  59. use POSIX qw{strftime SIGINT SIGKILL SIGTERM WEXITSTATUS WIFSIGNALED WTERMSIG};
  60. # FCM component modules
  61. use Fcm::Timer;
  62. # ------------------------------------------------------------------------------
  63. # Module level variables
  64. my %svn_info = (); # "svn info" log, (key1 = path,
  65. # key2 = URL, Revision, Last Changed Rev)
  66. # ------------------------------------------------------------------------------
  67. # SYNOPSIS
  68. # %srcdir = &Fcm::Util::find_file_in_path ($file, \@path);
  69. #
  70. # DESCRIPTION
  71. # Search $file in @path. Returns the full path of the $file if it is found
  72. # in @path. Returns "undef" if $file is not found in @path.
  73. # ------------------------------------------------------------------------------
  74. sub find_file_in_path {
  75. my ($file, $path) = @_;
  76. for my $dir (@$path) {
  77. my $full_file = File::Spec->catfile ($dir, $file);
  78. return $full_file if -e $full_file;
  79. }
  80. return undef;
  81. }
  82. # ------------------------------------------------------------------------------
  83. # SYNOPSIS
  84. # $expanded_path = &Fcm::Util::expand_tilde ($path);
  85. #
  86. # DESCRIPTION
  87. # Returns an expanded path if $path is a path that begins with a tilde (~).
  88. # ------------------------------------------------------------------------------
  89. sub expand_tilde {
  90. my $file = $_[0];
  91. $file =~ s#^~([^/]*)#$1 ? (getpwnam $1)[7] : ($ENV{HOME} || $ENV{LOGDIR})#ex;
  92. # Expand . and ..
  93. while ($file =~ s#/+\.(?:/+|$)#/#g) {next}
  94. while ($file =~ s#/+[^/]+/+\.\.(?:/+|$)#/#g) {next}
  95. # Remove trailing /
  96. $file =~ s#/*$##;
  97. return $file;
  98. }
  99. # ------------------------------------------------------------------------------
  100. # SYNOPSIS
  101. # $rc = &Fcm::Util::touch_file ($file);
  102. #
  103. # DESCRIPTION
  104. # Touch $file if it exists. Create $file if it does not exist. Return 1 for
  105. # success or 0 otherwise.
  106. # ------------------------------------------------------------------------------
  107. sub touch_file {
  108. my $file = $_[0];
  109. my $rc = 1;
  110. if (-e $file) {
  111. my $now = time;
  112. $rc = utime $now, $now, $file;
  113. } else {
  114. mkpath dirname ($file) unless -d dirname ($file);
  115. $rc = open FILE, '>', $file;
  116. $rc = close FILE if $rc;
  117. }
  118. return $rc;
  119. }
  120. # ------------------------------------------------------------------------------
  121. # SYNOPSIS
  122. # $flag = &is_wc ([$path]);
  123. #
  124. # DESCRIPTION
  125. # Returns true if current working directory (or $path) is a Subversion
  126. # working copy.
  127. # ------------------------------------------------------------------------------
  128. sub is_wc {
  129. my $path = @_ ? $_[0] : cwd ();
  130. if (-d $path) {
  131. return (-e File::Spec->catfile ($path, qw/.svn format/)) ? 1 : 0;
  132. } elsif (-f $path) {
  133. return (-e File::Spec->catfile (dirname ($path), qw/.svn format/)) ? 1 : 0;
  134. } else {
  135. return 0;
  136. }
  137. }
  138. # ------------------------------------------------------------------------------
  139. # SYNOPSIS
  140. # $flag = &is_url ($url);
  141. #
  142. # DESCRIPTION
  143. # Returns true if $url is a URL.
  144. # ------------------------------------------------------------------------------
  145. sub is_url {
  146. # This should handle URL beginning with svn://, http:// and svn+ssh://
  147. return ($_[0] =~ m#^[\+\w]+://#);
  148. }
  149. # ------------------------------------------------------------------------------
  150. # SYNOPSIS
  151. # $url = tidy_url($url);
  152. #
  153. # DESCRIPTION
  154. # Returns a tidied version of $url by removing . and .. in the path.
  155. # ------------------------------------------------------------------------------
  156. sub tidy_url {
  157. my ($url) = @_;
  158. if (!is_url($url)) {
  159. return $url;
  160. }
  161. my $DOT_PATTERN = qr{/+ \. (?:/+|(@|\z))}xms;
  162. my $DOT_DOT_PATTERN = qr{/+ [^/]+ /+ \.\. (?:/+|(@|\z))}xms;
  163. my $TRAILING_SLASH_PATTERN = qr{([^/]+) /* (@|\z)}xms;
  164. my $RIGHT_EVAL = q{'/' . ($1 ? $1 : '')};
  165. DOT:
  166. while ($url =~ s{$DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
  167. next DOT;
  168. }
  169. DOT_DOT:
  170. while ($url =~ s{$DOT_DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
  171. next DOT_DOT;
  172. }
  173. $url =~ s{$TRAILING_SLASH_PATTERN}{$1$2}xms;
  174. return $url;
  175. }
  176. # ------------------------------------------------------------------------------
  177. # SYNOPSIS
  178. # $string = &get_wct ([$dir]);
  179. #
  180. # DESCRIPTION
  181. # If current working directory (or $dir) is a Subversion working copy,
  182. # returns the top directory of this working copy; otherwise returns an empty
  183. # string.
  184. # ------------------------------------------------------------------------------
  185. sub get_wct {
  186. my $dir = @_ ? $_[0] : cwd ();
  187. return '' if not &is_wc ($dir);
  188. my $updir = dirname $dir;
  189. while (&is_wc ($updir)) {
  190. $dir = $updir;
  191. $updir = dirname $dir;
  192. last if $updir eq $dir;
  193. }
  194. return $dir;
  195. }
  196. # ------------------------------------------------------------------------------
  197. # SYNOPSIS
  198. # $string = &get_url_of_wc ([$path[, $refresh]]);
  199. #
  200. # DESCRIPTION
  201. # If current working directory (or $path) is a Subversion working copy,
  202. # returns the URL of the associated Subversion repository; otherwise returns
  203. # an empty string. If $refresh is specified, do not use the cached
  204. # information.
  205. # ------------------------------------------------------------------------------
  206. sub get_url_of_wc {
  207. my $path = @_ ? $_[0] : cwd ();
  208. my $refresh = exists $_[1] ? $_[1] : 0;
  209. my $url = '';
  210. if (&is_wc ($path)) {
  211. delete $svn_info{$path} if $refresh;
  212. &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
  213. $url = $svn_info{$path}{URL};
  214. }
  215. return $url;
  216. }
  217. # ------------------------------------------------------------------------------
  218. # SYNOPSIS
  219. # $string = &get_url_peg_of_wc ([$path[, $refresh]]);
  220. #
  221. # DESCRIPTION
  222. # If current working directory (or $path) is a Subversion working copy,
  223. # returns the URL@REV of the associated Subversion repository; otherwise
  224. # returns an empty string. If $refresh is specified, do not use the cached
  225. # information.
  226. # ------------------------------------------------------------------------------
  227. sub get_url_peg_of_wc {
  228. my $path = @_ ? $_[0] : cwd ();
  229. my $refresh = exists $_[1] ? $_[1] : 0;
  230. my $url = '';
  231. if (&is_wc ($path)) {
  232. delete $svn_info{$path} if $refresh;
  233. &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
  234. $url = $svn_info{$path}{URL} . '@' . $svn_info{$path}{Revision};
  235. }
  236. return $url;
  237. }
  238. # ------------------------------------------------------------------------------
  239. # SYNOPSIS
  240. # &_invoke_svn_info (PATH => $path);
  241. #
  242. # DESCRIPTION
  243. # The function is internal to this module. It invokes "svn info" on $path to
  244. # gather information on URL, Revision and Last Changed Rev. The information
  245. # is stored in a hash table at the module level, so that the information can
  246. # be re-used.
  247. # ------------------------------------------------------------------------------
  248. sub _invoke_svn_info {
  249. my %args = @_;
  250. my $path = $args{PATH};
  251. my $cfg = Fcm::Config->instance();
  252. return if exists $svn_info{$path};
  253. # Invoke "svn info" command
  254. my @info = &run_command (
  255. [qw/svn info/, $path],
  256. PRINT => $cfg->verbose > 2, METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore',
  257. );
  258. for (@info) {
  259. chomp;
  260. if (/^(URL|Revision|Last Changed Rev):\s*(.+)$/) {
  261. $svn_info{$path}{$1} = $2;
  262. }
  263. }
  264. return;
  265. }
  266. # ------------------------------------------------------------------------------
  267. # SYNOPSIS
  268. # $string = &get_command_string ($cmd);
  269. # $string = &get_command_string (\@cmd);
  270. #
  271. # DESCRIPTION
  272. # The function returns a string by converting the list in @cmd or the scalar
  273. # $cmd to a form, where it can be executed as a shell command.
  274. # ------------------------------------------------------------------------------
  275. sub get_command_string {
  276. my $cmd = $_[0];
  277. my $return = '';
  278. if (ref ($cmd) and ref ($cmd) eq 'ARRAY') {
  279. # $cmd is a reference to an array
  280. # Print each argument
  281. for my $i (0 .. @{ $cmd } - 1) {
  282. my $arg = $cmd->[$i];
  283. $arg =~ s/./*/g if $i > 0 and $cmd->[$i - 1] eq '--password';
  284. if ($arg =~ /[\s'"*?]/) {
  285. # Argument contains a space, quote it
  286. if (index ($arg, "'") >= 0) {
  287. # Argument contains an apostrophe, quote it with double quotes
  288. $return .= ($i > 0 ? ' ' : '') . '"' . $arg . '"';
  289. } else {
  290. # Otherwise, quote argument with apostrophes
  291. $return .= ($i > 0 ? ' ' : '') . "'" . $arg . "'";
  292. }
  293. } else {
  294. # Argument does not contain a space, just print it
  295. $return .= ($i > 0 ? ' ' : '') . ($arg eq '' ? "''" : $arg);
  296. }
  297. }
  298. } else {
  299. # $cmd is a scalar, just print it "as is"
  300. $return = $cmd;
  301. }
  302. return $return;
  303. }
  304. # ------------------------------------------------------------------------------
  305. # SYNOPSIS
  306. # &print_command ($cmd);
  307. # &print_command (\@cmd);
  308. #
  309. # DESCRIPTION
  310. # The function prints the list in @cmd or the scalar $cmd, as it would be
  311. # executed by the shell.
  312. # ------------------------------------------------------------------------------
  313. sub print_command {
  314. my $cmd = $_[0];
  315. print '=> ', &get_command_string ($cmd) , "\n";
  316. }
  317. # ------------------------------------------------------------------------------
  318. # SYNOPSIS
  319. # @return = &run_command (\@cmd, <OPTIONS>);
  320. # @return = &run_command ($cmd , <OPTIONS>);
  321. #
  322. # DESCRIPTION
  323. # This function executes the command in the list @cmd or in the scalar $cmd.
  324. # The remaining are optional arguments in a hash table. Valid options are
  325. # listed below. If the command is run using "qx", the function returns the
  326. # standard output from the command. If the command is run using "system", the
  327. # function returns true on success. By default, the function dies on failure.
  328. #
  329. # OPTIONS
  330. # METHOD => $method - this can be "system", "exec" or "qx". This determines
  331. # how the command will be executed. If not set, the
  332. # default is to run the command with "system".
  333. # PRINT => 1 - if set, print the command before executing it.
  334. # ERROR => $flag - this should only be set if METHOD is set to "system"
  335. # or "qx". The $flag can be "die" (default), "warn" or
  336. # "ignore". If set to "die", the function dies on error.
  337. # If set to "warn", the function issues a warning on
  338. # error, and the function returns false. If set to
  339. # "ignore", the function returns false on error.
  340. # RC => 1 - if set, must be a reference to a scalar, which will be
  341. # set to the return code of the command.
  342. # DEVNULL => 1 - if set, re-direct STDERR to /dev/null before running
  343. # the command.
  344. # TIME => 1 - if set, print the command with a timestamp before
  345. # executing it, and print the time taken when it
  346. # completes. This option supersedes the PRINT option.
  347. # ------------------------------------------------------------------------------
  348. sub run_command {
  349. my ($cmd, %input_opt_of) = @_;
  350. my %opt_of = (
  351. DEVNULL => undef,
  352. ERROR => 'die',
  353. METHOD => 'system',
  354. PRINT => undef,
  355. RC => undef,
  356. TIME => undef,
  357. %input_opt_of,
  358. );
  359. local($|) = 1; # Make sure STDOUT is flushed before running command
  360. # Print the command before execution, if necessary
  361. if ($opt_of{TIME}) {
  362. print(timestamp_command(get_command_string($cmd)));
  363. }
  364. elsif ($opt_of{PRINT}) {
  365. print_command($cmd);
  366. }
  367. # Re-direct STDERR to /dev/null if necessary
  368. if ($opt_of{DEVNULL}) {
  369. no warnings;
  370. open(OLDERR, ">&STDERR") || croak("Cannot dup STDERR ($!), abort");
  371. use warnings;
  372. open(STDERR, '>', File::Spec->devnull())
  373. || croak("Cannot redirect STDERR ($!), abort");
  374. # Make sure the channels are unbuffered
  375. my $select = select();
  376. select(STDERR); local($|) = 1;
  377. select($select);
  378. }
  379. my @return = ();
  380. if (ref($cmd) && ref($cmd) eq 'ARRAY') {
  381. # $cmd is an array
  382. my @command = @{$cmd};
  383. if ($opt_of{METHOD} eq 'qx') {
  384. @return = qx(@command);
  385. }
  386. elsif ($opt_of{METHOD} eq 'exec') {
  387. exec(@command);
  388. }
  389. else {
  390. system(@command);
  391. @return = $? ? () : (1);
  392. }
  393. }
  394. else {
  395. # $cmd is an scalar
  396. if ($opt_of{METHOD} eq 'qx') {
  397. @return = qx($cmd);
  398. }
  399. elsif ($opt_of{METHOD} eq 'exec') {
  400. exec($cmd);
  401. }
  402. else {
  403. system($cmd);
  404. @return = $? ? () : (1);
  405. }
  406. }
  407. my $rc = $?;
  408. # Put STDERR back to normal, if redirected previously
  409. if ($opt_of{DEVNULL}) {
  410. close(STDERR);
  411. open(STDERR, ">&OLDERR") || croak("Cannot dup STDERR ($!), abort");
  412. }
  413. # Print the time taken for command after execution, if necessary
  414. if ($opt_of{TIME}) {
  415. print(timestamp_command(get_command_string($cmd), 'end'));
  416. }
  417. # Signal and return code
  418. my ($signal, $status) = (WTERMSIG($rc), WEXITSTATUS($rc));
  419. if (exists($opt_of{RC})) {
  420. ${$opt_of{RC}} = $status;
  421. }
  422. if (WIFSIGNALED($rc) && grep {$signal == $_} (SIGINT, SIGKILL, SIGTERM)) {
  423. croak(sprintf('%s terminated (%d)', get_command_string($cmd), $signal));
  424. }
  425. if ($status && $opt_of{ERROR} ne 'ignore') {
  426. my $func_ref = $opt_of{ERROR} eq 'warn' ? \&carp : \&croak;
  427. $func_ref->(sprintf('%s failed (%d)', get_command_string($cmd), $status));
  428. }
  429. return @return;
  430. }
  431. # ------------------------------------------------------------------------------
  432. # SYNOPSIS
  433. # &e_report (@message);
  434. #
  435. # DESCRIPTION
  436. # The function prints @message to STDERR and aborts with a error.
  437. # ------------------------------------------------------------------------------
  438. sub e_report {
  439. print STDERR @_, "\n" if @_;
  440. exit 1;
  441. }
  442. # ------------------------------------------------------------------------------
  443. # SYNOPSIS
  444. # &w_report (@message);
  445. #
  446. # DESCRIPTION
  447. # The function prints @message to STDERR and returns.
  448. # ------------------------------------------------------------------------------
  449. sub w_report {
  450. print STDERR @_, "\n" if @_;
  451. return;
  452. }
  453. # ------------------------------------------------------------------------------
  454. # SYNOPSIS
  455. # $date = &svn_date ($time);
  456. #
  457. # DESCRIPTION
  458. # The function returns a date, formatted as by Subversion. The argument $time
  459. # is the number of seconds since epoch.
  460. # ------------------------------------------------------------------------------
  461. sub svn_date {
  462. my $time = shift;
  463. return strftime ('%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)', localtime ($time));
  464. }
  465. # ------------------------------------------------------------------------------
  466. 1;
  467. __END__