[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 4cafdca 02/22: Add lmi_md5sum utility impleme
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 4cafdca 02/22: Add lmi_md5sum utility implementing subset of standard md5sum |
Date: |
Sat, 28 Mar 2020 18:23:35 -0400 (EDT) |
branch: master
commit 4cafdca49f78038e154942cd4bcba00809d688b9
Author: Ilya Sinitsyn <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Add lmi_md5sum utility implementing subset of standard md5sum
Implement a limited functionality variant of GNU 'md5sum' command line
utility.
This avoids dependency on the non-Cygwin MSW md5sum.exe binary as now
lmi_md5sum.exe can be distributed as part of the fardel distribution and
Cygwin md5sum can be used during lmi build.
---
Makefile.am | 11 ++
authenticity_test.cpp | 20 ++--
install_miscellanea.make | 29 +-----
md5sum_cli.cpp | 259 +++++++++++++++++++++++++++++++++++++++++++++++
objects.make | 8 ++
set_toolchain.sh | 2 +-
system_command_test.cpp | 6 +-
workhorse.make | 14 +--
8 files changed, 300 insertions(+), 49 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index b69df63..5620892 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -44,6 +44,7 @@ bin_PROGRAMS = \
lmi_cli \
lmi_wx \
elapsed_time \
+ lmi_md5sum \
generate_passkey \
antediluvian_cli \
ihs_crc_comp \
@@ -456,6 +457,16 @@ libwx_new_la_LIBADD = $(WX_LIBS)
# auxiliary executables
+lmi_md5sum_SOURCES = \
+ getopt.cpp \
+ md5.cpp \
+ md5sum.cpp \
+ md5sum_cli.cpp
+lmi_md5sum_CXXFLAGS = $(AM_CXXFLAGS) $(BOOST_INCLUDE_FLAGS)
+lmi_md5sum_LDADD = \
+ $(BOOST_LIBS) \
+ libmain_auxiliary_common.la
+
generate_passkey_SOURCES = \
authenticity.cpp \
calendar_date.cpp \
diff --git a/authenticity_test.cpp b/authenticity_test.cpp
index 4e51f33..9202864 100644
--- a/authenticity_test.cpp
+++ b/authenticity_test.cpp
@@ -110,15 +110,15 @@ PasskeyTest::~PasskeyTest()
RemoveTestFiles(__FILE__, __LINE__);
}
-/// Regrettably, invoking 'md5sum' through a shell just to confirm its
+/// Regrettably, invoking 'lmi_md5sum' through a shell just to confirm its
/// availability writes its output to stdout; however, without this
/// test, it would be difficult to tell whether downstream errors stem
/// from that program's absence.
void PasskeyTest::EnsureMd5sumBinaryIsFound() const
{
- std::cout << " Result of 'md5sum --version':" << std::endl;
- system_command("md5sum --version");
+ std::cout << " Result of 'lmi_md5sum --version':" << std::endl;
+ system_command("lmi_md5sum --version");
}
void PasskeyTest::RemoveTestFiles(char const* file, int line) const
@@ -169,19 +169,19 @@ void PasskeyTest::InitializeDataFile() const
BOOST_TEST_EQUAL("bf039dbb0e8061971a2c322c8336199c", md5_str(sum));
}
-/// Write a data file to be passed to the 'md5sum' program.
+/// Write a data file to be passed to the 'lmi_md5sum' program.
///
/// For production, a file with md5 sums of crucial files is provided.
/// For this unit test, file 'coleridge' is the sole crucial file.
///
/// This file consists of the md5 sum of the data file followed by two
/// spaces and the name of the data file. Creating that file portably
-/// here by running 'md5sum' would require redirection, which isn't
-/// part of the standard C++ library, so the effect of 'md5sum' is
-/// instead emulated; testing that file here with 'md5sum' validates
-/// that emulation and guards against a bogus 'md5sum' program.
+/// here by running 'lmi_md5sum' would require redirection, which isn't
+/// part of the standard C++ library, so the effect of 'lmi_md5sum' is
+/// instead emulated; testing that file here with 'lmi_md5sum' validates
+/// that emulation and guards against a bogus 'lmi_md5sum' program.
///
-/// Postcondition: the file passes a test with the 'md5sum' program.
+/// Postcondition: the file passes a test with the 'lmi_md5sum' program.
void PasskeyTest::InitializeMd5sumFile() const
{
@@ -197,7 +197,7 @@ void PasskeyTest::InitializeMd5sumFile() const
os << " coleridge\n";
os.close();
- std::string s = "md5sum --check --status " + std::string(md5sum_file());
+ std::string s = "lmi_md5sum --check --status " +
std::string(md5sum_file());
system_command(s);
}
diff --git a/install_miscellanea.make b/install_miscellanea.make
index 556f27e..ba8ae9c 100644
--- a/install_miscellanea.make
+++ b/install_miscellanea.make
@@ -47,7 +47,6 @@ third_party_source_dir := $(dest_dir)/src
boost_archive := boost_1_33_1.tar.bz2
cgicc_archive := cgicc-3.1.4.tar.bz2
jing_archive := jing-20091111.zip
-md5sum_msw_exe := md5sum.exe
sample_archive := lmi-data-20050618T1440Z.tar.bz2
trang_archive := trang-20091111.zip
xmlwrapp_archive := xmlwrapp-0.9.0.tar.gz
@@ -56,14 +55,12 @@ file_list := \
$(boost_archive) \
$(cgicc_archive) \
$(jing_archive) \
- $(md5sum_msw_exe) \
$(sample_archive) \
$(trang_archive) \
$(xmlwrapp_archive) \
boost cgicc xmlwrapp: stem = $(basename $(basename $($@_archive)))
jing trang: stem = $(basename $($@_archive))
-md5sum_msw: stem = $(md5sum_msw_exe)
sample: stem = data
# URLs and archive md5sums
#####################################################
@@ -71,7 +68,6 @@ sample: stem = data
$(boost_archive)-url := $(sf_mirror)/boost/$(boost_archive)
$(cgicc_archive)-url := ftp://ftp.gnu.org/pub/gnu/cgicc/$(cgicc_archive)
$(jing_archive)-url :=
https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jing-trang/$(jing_archive)
-$(md5sum_msw_exe)-url :=
https://github.com/vadz/lmi/releases/download/new-cygwin-makefiles/md5sum.exe
$(sample_archive)-url :=
https://download.savannah.gnu.org/releases/lmi/$(sample_archive)
$(trang_archive)-url :=
https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jing-trang/$(trang_archive)
$(xmlwrapp_archive)-url :=
https://github.com/vslavik/xmlwrapp/releases/download/v0.9.0/$(xmlwrapp_archive)
@@ -79,7 +75,6 @@ $(xmlwrapp_archive)-url :=
https://github.com/vslavik/xmlwrapp/releases/download
$(boost_archive)-md5 := 2b999b2fb7798e1737d1fff8fac602ef
$(cgicc_archive)-md5 := 6cb5153fc9fa64b4e50c7962aa557bbe
$(jing_archive)-md5 := 13eef193921409a1636377d1efbf9843
-$(md5sum_msw_exe)-md5 := eb574b236133e60c989c6f472f07827b
$(sample_archive)-md5 := e7f07133abfc3b9c2252dfa3b61191bc
$(trang_archive)-md5 := 9d31799b948c350850eb9dd14e5b832d
$(xmlwrapp_archive)-md5 := 5e8ac678ab03b7c60ce61ac5424e0849
@@ -120,7 +115,7 @@ ad_hoc_dir_exists = \
# Targets
######################################################################
.PHONY: all
-all: boost cgicc jing md5sum_msw sample trang xmlwrapp
+all: boost cgicc jing sample trang xmlwrapp
# Patches were generated according to this advice:
#
@@ -188,28 +183,6 @@ jing: $(file_list)
$(MV) $(ad_hoc_dir)/$(stem)/bin/$@.jar $(dest_dir)/rng
$(MV) $(ad_hoc_dir)/$(stem)/bin/xercesImpl.jar $(dest_dir)/rng
-# The 'md5sum_msw' binary is redistributed to msw end users for
-# authentication, so the 'fardel' target requires it. On other
-# platforms, it cannot be executed directly, but it is needed for
-# creating a cross 'fardel' and for running cross unit tests.
-#
-# It is placed in lmi's 'third_party/bin/' subdirectory--imperatively
-# not in lmi's 'local/bin/' subdirectory, which is added to $PATH.
-# For cygwin builds, the expressly downloaded 'md5sum.exe' is kept off
-# $PATH to prevent it from shadowing cygwin's own version. However,
-# for cross builds, it cannot shadow the native 'md5sum', yet some
-# cross-built unit tests require an msw binary, so add its directory
-# to $WINEPATH to make those tests work (incidentally, 'wine' doesn't
-# find it if it's simply symlinked).
-#
-# Should the given URL ever become invalid, see:
-# http://www.openoffice.org/dev_docs/using_md5sums.html#links
-# to find another.
-
-.PHONY: md5sum_msw
-md5sum_msw: $(file_list)
- $(CP) --preserve $(cache_dir)/$(stem) $(third_party_bin_dir)
-
# The 'clobber' target doesn't remove $(prefix)/data because that
# directory might contain valuable user-customized files; hence, in
# this case, $(MKDIR) must be allowed to fail.
diff --git a/md5sum_cli.cpp b/md5sum_cli.cpp
new file mode 100644
index 0000000..b558882
--- /dev/null
+++ b/md5sum_cli.cpp
@@ -0,0 +1,259 @@
+// Limited functionality variant of GNU `md5sum` program.
+//
+// Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,
2013, 2014, 2015, 2016, 2017, 2018, 2019 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; ifnot, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "getopt.hpp"
+#include "main_common.hpp"
+#include "md5sum.hpp"
+
+#include <iostream>
+
+int usage(int status)
+{
+ if(status != EXIT_SUCCESS)
+ {
+ std::cerr << "Try 'lmi_md5sum --help' for more information.\n";
+ }
+ else
+ {
+ std::cout
+ <<
+R"(Usage: lmi_md5sum [OPTION]... [FILE]...
+Print or check MD5 (128-bit) checksums.
+
+ -b, --binary read in binary mode (default)
+ -c, --check read MD5 sums from the FILEs and check them
+ -t, --text read in text mode
+
+The following two options are useful only when verifying checksums:
+ --quiet don't print OK for each successfully verified file
+ --status don't output anything, status code shows success
+
+ --help display this help and exit
+ --version output version information and exit
+
+The sums are computed as described in RFC 1321. When checking, the input
+should be a former output of this program. The default mode is to print a
+line with checksum, a space, a character indicating input mode ('*' for binary
+' ' for text or where binary is insignificant), and name for each FILE.
+
+This program is a limited functionality variant of GNU 'md5sum' utility
+and is part of the 'Let Me Illustrate' project.
+)"
+ ;
+ }
+ return status;
+}
+
+void version()
+{
+ std::cout
+ <<
+R"(lmi_md5sum 0.9
+
+This program is a limited functionality variant of GNU 'md5sum' utility
+and is part of the 'Let Me Illustrate' project.
+)"
+ ;
+}
+
+// For long options that have no equivalent short option, use a
+// non-character as a pseudo short option, starting with CHAR_MAX + 1.
+enum
+{
+ STATUS_OPTION = CHAR_MAX + 1,
+ QUIET_OPTION,
+ HELP_OPTION,
+ VERSION_OPTION
+};
+
+int try_main(int argc, char* argv[])
+{
+ bool all_ok = true;
+ int c;
+ int option_index = 0;
+ struct Option long_options[] =
+ {
+ {"binary" ,NO_ARG ,0 ,'b' ,0 ,""},
+ {"check" ,NO_ARG ,0 ,'c' ,0 ,""},
+ {"quiet" ,NO_ARG ,0 ,QUIET_OPTION ,0 ,""},
+ {"status" ,NO_ARG ,0 ,STATUS_OPTION ,0 ,""},
+ {"text" ,NO_ARG ,0 ,'t' ,0 ,""},
+ {"help" ,NO_ARG ,0 ,HELP_OPTION ,0 ,""},
+ {"version" ,NO_ARG ,0 ,VERSION_OPTION ,0 ,""},
+ {0 ,NO_ARG ,0 ,0 ,0 ,""}
+ };
+
+ bool show_help = false;
+ bool show_version = false;
+ bool binary = true;
+ bool have_input_mode_option = false;
+ bool do_check = false;
+ bool command_line_syntax_error = false;
+
+ // With --check, don't generate any output.
+ // The exit code indicates success or failure.
+ bool status_only = false;
+
+ // With --check, suppress the "OK" printed for each verified file.
+ bool quiet = false;
+
+ GetOpt getopt_long
+ (argc
+ ,argv
+ ,"chv"
+ ,long_options
+ ,&option_index
+ ,1
+ );
+
+ while(EOF != (c = getopt_long ()))
+ {
+ switch (c)
+ {
+ case 'b':
+ have_input_mode_option = true;
+ break;
+ case 'c':
+ do_check = true;
+ break;
+ case QUIET_OPTION:
+ quiet = true;
+ break;
+ case STATUS_OPTION:
+ status_only = true;
+ break;
+ case 't':
+ binary = false;
+ have_input_mode_option = true;
+ break;
+ case HELP_OPTION:
+ show_help = true;
+ break;
+ case VERSION_OPTION:
+ show_version = true;
+ break;
+ default:
+ // Error message was already given from getopt() code, so no
need
+ // to output anything else here, but do flush its output so
that it
+ // appears before the usage message.
+ std::fflush(stderr);
+
+ command_line_syntax_error = true;
+ }
+
+ if(command_line_syntax_error)
+ break;
+ }
+
+ if(command_line_syntax_error)
+ {
+ std::cerr << "Try 'lmi_md5sum --help' for more information." <<
std::endl;
+ return EXIT_FAILURE;
+ }
+ if(have_input_mode_option && do_check)
+ {
+ std::cerr
+ << "The --binary and --text options are meaningless when "
+ << "verifying checksums."
+ << std::endl
+ ;
+ return usage(EXIT_FAILURE);
+ }
+ if(status_only && !do_check)
+ {
+ std::cerr
+ << "The --status option is meaningful only when verifying
checksums."
+ << std::endl
+ ;
+ return usage(EXIT_FAILURE);
+ }
+ if(quiet && !do_check)
+ {
+ std::cerr
+ << "The --quiet option is meaningful only when verifying
checksums."
+ << std::endl
+ ;
+ return usage(EXIT_FAILURE);
+ }
+ if(show_help)
+ {
+ return usage(EXIT_SUCCESS);
+ }
+
+ if(show_version)
+ {
+ version();
+ return EXIT_SUCCESS;
+ }
+
+ try
+ {
+ for(int i = getopt_long.optind; i < argc; ++i)
+ {
+ char const* filename = argv[i];
+ std::string md5;
+
+ if(do_check)
+ {
+ auto const sums = md5_read_checksum_file(filename);
+
+ for(auto const& s : sums)
+ {
+ md5 = md5_calculate_file_checksum(s.filename, s.file_mode);
+
+ bool const current_ok = md5 == s.md5sum;
+ if(!status_only)
+ {
+ if(!current_ok || !quiet)
+ {
+ std::cout
+ << s.filename.string()
+ << ": "
+ << (current_ok ? "OK" : "FAILED")
+ << std::endl
+ ;
+ }
+ }
+ all_ok &= current_ok;
+ }
+ }
+ else
+ {
+ md5 = md5_calculate_file_checksum
+ (filename
+ ,binary ? md5_file_mode::binary : md5_file_mode::text
+ );
+
+ std::cout << md5 << " " << (binary ? "*" : " ") << filename <<
std::endl;
+ }
+ }
+ }
+ catch(std::runtime_error const& e)
+ {
+ if(!status_only)
+ std::cerr << "lmi_md5sum: " << e.what() << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ return all_ok ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/objects.make b/objects.make
index 2ac7dca..8b9dc0e 100644
--- a/objects.make
+++ b/objects.make
@@ -1084,6 +1084,14 @@ elapsed_time$(EXEEXT): \
system_command_non_wx.o \
timer.o \
+lmi_md5sum$(EXEEXT): \
+ $(boost_filesystem_objects) \
+ $(main_auxiliary_common_objects) \
+ getopt.o \
+ md5.o \
+ md5sum.o \
+ md5sum_cli.o \
+
generate_passkey$(EXEEXT): \
$(boost_filesystem_objects) \
$(main_auxiliary_common_objects) \
diff --git a/set_toolchain.sh b/set_toolchain.sh
index e2f959c..70ed5b8 100755
--- a/set_toolchain.sh
+++ b/set_toolchain.sh
@@ -90,7 +90,7 @@ local lmi_build_type
local prefix="/opt/lmi"
local localbindir="$prefix/local/${LMI_COMPILER}_${LMI_TRIPLET}/bin"
local locallibdir="$prefix/local/${LMI_COMPILER}_${LMI_TRIPLET}/lib"
-# $winebindir is where 'install_miscellanea.make' places 'md5sum.exe'.
+# $winebindir is where 'make install' places 'lmi_md5sum.exe'.
local winebindir="$prefix"/third_party/bin
# Running a command like this many times:
diff --git a/system_command_test.cpp b/system_command_test.cpp
index 88d4805..cac4984 100644
--- a/system_command_test.cpp
+++ b/system_command_test.cpp
@@ -39,13 +39,13 @@ int test_main(int, char*[])
os1 << "e87dfb7b7c7f87985d3eff4782c172b8 eraseme\n";
os1.close();
- system_command("md5sum --check --status eraseme.md5");
+ system_command("lmi_md5sum --check --status eraseme.md5");
BOOST_TEST_THROW
- (system_command("md5sum --check --status eraseme")
+ (system_command("lmi_md5sum --check --status eraseme")
,std::runtime_error
,lmi_test::what_regex
- ("Exit code [0-9]* from command 'md5sum --check --status
eraseme'.")
+ ("Exit code [0-9]* from command 'lmi_md5sum --check --status
eraseme'.")
);
#if !defined LMI_MSW
diff --git a/workhorse.make b/workhorse.make
index 6995430..2b84580 100644
--- a/workhorse.make
+++ b/workhorse.make
@@ -112,6 +112,7 @@ ifeq (,$(USE_SO_ATTRIBUTES))
bcc_ld$(EXEEXT) \
bcc_rc$(EXEEXT) \
elapsed_time$(EXEEXT) \
+ lmi_md5sum$(EXEEXT) \
generate_passkey$(EXEEXT) \
ihs_crc_comp$(EXEEXT) \
rate_table_tool$(EXEEXT) \
@@ -1058,7 +1059,6 @@ installable_binaries := \
$(default_targets) \
$(wildcard $(localbindir)/*$(SHREXT)) \
$(wildcard $(locallibdir)/*$(SHREXT)) \
- $(wildcard $(prefix)/third_party/bin/*$(EXEEXT)) \
.PHONY: install
install: $(default_targets)
@@ -1076,6 +1076,7 @@ ifeq (,$(USE_SO_ATTRIBUTES))
else
@$(ECHO) "Can't build product_files$(EXEEXT) with USE_SO_ATTRIBUTES."
endif
+ @$(CP) --preserve lmi_md5sum$(EXEEXT) $(prefix)/third_party/bin
################################################################################
@@ -1176,11 +1177,11 @@ fardel_files := \
# Sensitive files are authenticated at run time.
#
-# Binary files other than 'md5sum$(EXEEXT)' are not authenticated
+# Binary files other than 'lmi_md5sum$(EXEEXT)' are not authenticated
# because they aren't easily forged but are sizable enough to make
# authentication too slow. An incorrect version of any such file might
# be distributed by accident, but that problem would not be caught by
-# generating an md5sum for the incorrect file. 'md5sum$(EXEEXT)' is
+# generating an md5sum for the incorrect file. 'lmi_md5sum$(EXEEXT)' is
# however authenticated because replacing it with a program that
# always reports success would circumvent authentication.
#
@@ -1199,7 +1200,7 @@ fardel_checksummed_files = \
$(extra_fardel_checksummed_files) \
*.dat *.database *.funds *.ndx *.policy *.rounding *.strata *.xst \
expiry \
- md5sum$(EXEEXT) \
+ lmi_md5sum$(EXEEXT) \
.PHONY: fardel
fardel: install
@@ -1207,15 +1208,14 @@ fardel: install
@$(MAKE) --file=$(this_makefile) --directory=$(fardel_dir) wrap_fardel
@$(ECHO) "Created '$(fardel_name)' archive in '$(fardel_root)'."
-# A native 'md5sum$(EXEEXT)' must be provided because lmi uses it for
-# run-time authentication.
+# A native 'lmi_md5sum$(EXEEXT)' is provided to be used by end users.
#
# $(CP) is used without '--update' so that custom extra files can
# replace defaults regardless of their datestamps.
.PHONY: wrap_fardel
wrap_fardel:
- @$(CP) $(prefix)/third_party/bin/md5sum$(EXEEXT) .
+ @$(CP) $(prefix)/third_party/bin/lmi_md5sum$(EXEEXT) .
@$(CP) $(datadir)/configurable_settings.xml .
@$(CP) $(datadir)/company_logo.png .
@$(CP) $(datadir)/group_quote_banner.png .
- [lmi-commits] [lmi] master updated (0eafcb0 -> 6425b3b), Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master f340f72 06/22: Explicitly qualify std::size_t, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 2696d31 04/22: Update copyright notices, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 2b4947a 08/22: Write inequality comparisons in number-line order, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 063c376 10/22: Generally avoid "`" where "'" is at least as good, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 0622c32 07/22: Regularize whitespace, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master c029dd3 12/22: Improve documentation, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 4cafdca 02/22: Add lmi_md5sum utility implementing subset of standard md5sum,
Greg Chicares <=
- [lmi-commits] [lmi] master d82ac15 13/22: Split a long line, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 28956d7 14/22: Temporarily allow an "MD5 !!" marker, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 925f946 05/22: Include headers iff appropriate, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master fc09a91 03/22: Add unit tests for MD5-related functionality, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 225cf10 09/22: Avoid a warning by a different means, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 2db19f5 11/22: Remove unnecessary shared-object attributes, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 28f8ffc 15/22: Explicitly qualify std::cout, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master f14ad11 01/22: Implement MD5 checksum files reading in lmi code, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master ec4fe67 18/22: Realphabetize a list, Greg Chicares, 2020/03/28
- [lmi-commits] [lmi] master 8d3e03a 20/22: Expunge a disused makefile variable, Greg Chicares, 2020/03/28