Files
Bob Beck c9aa42db82 Make most generated files read-only, and write them atomically
Generated files were left writable, so a hand edit looked like it had
worked and was silently lost at the next build. Only the pods and the
generated scripts were protected, by a chmod either side of the
recipe, which cost a shell and two processes for each file.

Route the generators that write to standard output through a helper
that collects the output, sets the mode and renames the result into
place. Renaming needs no permission on the file it replaces, so there
is no unlocking step, and a generator that fails part way now leaves
the previous file rather than a truncated one with a fresh timestamp
that make would believe.

The perlasm modules name their own output rather than writing to
standard output, so they are unchanged and their output stays
writable.

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Sep  1 14:03:12 2026
Merged-from: https://github.com/openssl/openssl/pull/32243
2026-09-01 16:02:42 +02:00

137 lines
4.0 KiB
Perl

#! /usr/bin/env perl
# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
use strict;
use warnings;
use Getopt::Std;
use File::Basename;
use Pod::Man;
# Format man pages without loading Pod::Man once per page. pod2man is a
# perl program that compiles Pod::Man before it reads a single pod, and
# OpenSSL has over nine hundred pages, so running it once per page spends
# most of that time compiling the same module again and again. Here the
# module is loaded once and the pages are then divided between a few
# forked workers, which inherit it already compiled.
# Options. The whole manual is formatted in one run, so the section of
# each page is taken from the directory its pod lives in rather than
# given on the command line: doc/man3/BIO_s_mem.pod is section 3 and
# becomes OUTDIR/man3/BIO_s_mem.3.
our ($opt_o); # -o OUTDIR, the directory holding the man1..man7 dirs
our ($opt_m); # -m MANSUFFIX, appended to the section inside the page
our ($opt_d); # -d DATE
our ($opt_r); # -r RELEASE
our ($opt_j); # -j JOBS
getopts('o:m:d:r:j:');
die "-o flag missing" unless defined $opt_o;
$opt_m = '' unless defined $opt_m;
$opt_d = '' unless defined $opt_d;
$opt_r = '' unless defined $opt_r;
sub cpu_count
{
my $cpus = $ENV{"NUMBER_OF_PROCESSORS"}; # Windows sets this.
if (!defined($cpus) && $^O =~ /linux/) {
my $tmp = qx(nproc 2>/dev/null);
$cpus = $tmp if $? == 0 && $tmp > 0;
}
if (!defined($cpus) && -r "/proc/cpuinfo") {
my $tmp = qx(grep -c ^processor /proc/cpuinfo 2>/dev/null);
$cpus = $tmp if $? == 0 && $tmp > 0;
}
if (!defined($cpus)) {
my $tmp = qx(sysctl -n hw.ncpu 2>/dev/null); # BSDs, macOS
$cpus = $tmp if $? == 0 && $tmp > 0;
}
return defined($cpus) && $cpus > 0 ? int($cpus) : 1;
}
# Turn one pod into one man page.
sub format_page
{
my $pod = shift;
my $name = basename($pod, ".pod");
my $dir = basename(dirname($pod));
my ($section) = $dir =~ m|^man(\d)$|;
die "Can't tell the section of $pod from its directory\n"
unless defined $section;
my $out = "$opt_o/man$section/$name.$section";
# The page is current when it is newer than the pod it comes from.
return if -e $out && -M $out < -M $pod;
unlink $out;
Pod::Man->new(name => uc $name,
section => "$section$opt_m",
center => "OpenSSL",
date => $opt_d,
release => $opt_r)
->parse_from_file($pod, $out);
chmod 0444, $out
or die "Can't set the mode of $out, $!\n";
}
my @pods = @ARGV;
exit 0 unless @pods;
# Only ask how many processors there are when the answer can matter;
# the count is found by running a command, and the per-page rules call
# this script with a single pod.
my $jobs = @pods > 1
? (defined $opt_j && $opt_j > 0 ? int($opt_j) : cpu_count())
: 1;
$jobs = scalar @pods if $jobs > @pods;
# One page, or no reason to fork: do the work here. This is also the path
# taken where fork() is emulated and would cost more than it saves.
if ($jobs <= 1) {
format_page($_) foreach @pods;
exit 0;
}
my @pids;
foreach my $worker (0 .. $jobs - 1) {
my $pid = fork();
die "Can't fork, $!\n" unless defined $pid;
if (!$pid) {
# Deal every $jobs'th page to this worker. The pages differ a lot
# in size, and dealing them out interleaves the large ones instead
# of handing one worker a contiguous run of them.
for (my $i = $worker; $i <= $#pods; $i += $jobs) {
format_page($pods[$i]);
}
exit 0;
}
push @pids, $pid;
}
my $failed = 0;
foreach my $pid (@pids) {
waitpid($pid, 0);
$failed = 1 if $?;
}
die "Failed to format the manual pages\n" if $failed;