commit: 38794d2599aea207de65b4d1b40f726eba5e16ba
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Tue, 26 Sep 2023 17:18:19 +0200
Import from utils
Diffstat:
46 files changed, 2674 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+/config.mk
+/target_filter
+/cmd/*
+!/cmd/*.c
+!/cmd/*.ha
+!/cmd/*.1
+*.t.err
+*.o
+
+# Kyua
+/html/
diff --git a/.reuse/dep5 b/.reuse/dep5
@@ -0,0 +1,6 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: https://hacktivis.me/git/utils
+
+Files: test-cmd/inputs/* test-cmd/outputs/*
+Copyright: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+License: CC0-1.0
diff --git a/Kyuafile b/Kyuafile
@@ -0,0 +1,7 @@
+-- SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+-- SPDX-License-Identifier: MPL-2.0
+syntax(2)
+
+test_suite("utils-extra")
+
+include("test-cmd/Kyuafile")
diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,61 @@
+# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+include config.mk
+
+all: $(EXE)
+
+.c:
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+.c.o:
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -c -o $@ $<
+
+.PHONY: check
+check: all
+ MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 kyua test || (kyua report --verbose --results-filter=broken,failed; false)
+
+.PHONY: lint
+lint:
+ $(SHELLCHECK) ./configure test_all.sh test_functions.sh ./sh/*
+ SHELLCHECK=${SHELLCHECK} ./test-cmd/shellcheck
+ ${FLAWFINDER} --error-level=4 .
+ $(MANDOC) -Tlint -Wunsupp,error,warning $(MAN1)
+ $(REUSE) lint
+
+clean:
+ rm -fr $(EXE)
+ rm -fr ${EXE:=.c.gcov} ${EXE:=.gcda} ${EXE:=.gcno}
+
+install: script-install cmd-install
+
+script-install:
+ mkdir -p $(DESTDIR)$(SHELLDIR)/ $(DESTDIR)$(PERLDIR)/
+ cp -r sh/* $(DESTDIR)$(SHELLDIR)/
+ cp -r perl/* $(DESTDIR)$(PERLDIR)/
+
+cmd-install:
+ mkdir -p ${DESTDIR}${BINDIR}/
+ cp -p ${EXE} ${DESTDIR}${BINDIR}/
+ chown 0:0 ${DESTDIR}${BINDIR}/memsys
+ chmod 4755 ${DESTDIR}${BINDIR}/memsys
+ mkdir -p ${DESTDIR}${MANDIR}/man1
+ cp -p ${MAN1} ${DESTDIR}${MANDIR}/man1
+
+.PHONY: coverage
+coverage:
+ $(GCOV) -b $(EXE)
+
+C_SOURCES = cmd/*.c lib/*.h lib/*.c
+format: $(C_SOURCES)
+ clang-format -style=file -assume-filename=.clang-format -i $(C_SOURCES)
+
+cmd/lolcat: cmd/lolcat.c Makefile
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -o $@ cmd/lolcat.c -lm $(LDFLAGS)
+
+cmd/xcd: cmd/xcd.c Makefile
+ rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno}
+ $(CC) -std=c99 $(CFLAGS) -o $@ cmd/xcd.c -lm $(LDFLAGS)
diff --git a/cmd/args.1 b/cmd/args.1
@@ -0,0 +1,20 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2022-02-13
+.Dt ARGS 1
+.Os
+.Sh NAME
+.Nm args
+.Nd dump all passed arguments
+.Sh SYNOPSIS
+.Nm
+.Op Ar arguments
+.Sh DESCRIPTION
+.Nm
+will print out all the arguments passed to it.
+This is intended as a tool for shell debugging.
+.Sh EXIT STATUS
+.Ex -std
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/args.c b/cmd/args.c
@@ -0,0 +1,20 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+// printf()
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+ printf("argc: %i\n", argc);
+
+ for(int i = 0; i < argc; i++)
+ {
+ printf("argv[%d]: \"%s\"\n", i, argv[i]);
+ }
+
+ return 0;
+}
diff --git a/cmd/del.1 b/cmd/del.1
@@ -0,0 +1,22 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2021-05-13
+.Dt DEL 1
+.Os
+.Sh NAME
+.Nm del
+.Nd delete any file
+.Sh SYNOPSIS
+.Nm
+.Ar
+.Sh DESCRIPTION
+Simpler replacement to
+.Xr rm 1 ,
+notably treating directories like any other file.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr remove 3
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/del.c b/cmd/del.c
@@ -0,0 +1,24 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h> /* errno */
+#include <stdio.h> /* remove() */
+#include <string.h> /* strerror() */
+
+int
+main(int argc, char *argv[])
+{
+ for(int i = 1; i < argc; i++)
+ {
+ if(remove(argv[i]) < 0)
+ {
+ // TODO: interact on write protection to force deletion
+ fprintf(stderr, "del: remove(%s) error: %s\n", argv[i], strerror(errno));
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/cmd/errno.1 b/cmd/errno.1
@@ -0,0 +1,22 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2022-04-19
+.Dt ENV 1
+.Os
+.Sh NAME
+.Nm errno
+.Nd print errno message for a value
+.Sh SYNOPSIS
+.Nm
+.Ar value
+.Sh DESCRIPTION
+.Nm
+shall print the errno message associated with
+.Ar value .
+.Sh EXIT STATUS
+.Ex -std
+.Sh STANDARDS
+No applicable one known.
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/errno.c b/cmd/errno.c
@@ -0,0 +1,42 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#include <errno.h> // errno
+#include <stdio.h> // puts, perror
+#include <stdlib.h> // strtol
+#include <string.h> // strerror
+
+int
+main(int argc, char *argv[])
+{
+ if(argc != 2)
+ {
+ puts("usage: errno <number>");
+ return 1;
+ }
+
+ errno = 0;
+
+ int err = (int)(strtol(argv[1], NULL, 10));
+ if(errno != 0)
+ {
+ perror("errno: strtol");
+ return 1;
+ }
+
+ errno = 0;
+ char *msg = strerror(err);
+ if(errno != 0)
+ {
+ perror("errno: strerror");
+ return 1;
+ }
+
+ if(puts(msg) < 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/cmd/humanize.1 b/cmd/humanize.1
@@ -0,0 +1,31 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2023-06-05
+.Dt HUMANIZE 1
+.Os
+.Sh NAME
+.Nm humanize
+.Nd format numbers into human readable form
+.Sh SYNOPSIS
+.Nm
+.Op Fl dbt
+.Ar number ...
+.Sh DESCRIPTION
+Takes each
+.Ar number
+and format it in a human readable form, one per line.
+It supports the following options:
+.Bl -tag -width Ds
+.It Fl b
+Divide by 1024, typically used for bytes.
+.It Fl d
+Divide by 1000, this is the default but made explicit in case of changes without breaking scripts.
+.It Fl t
+.Ar number
+represents time duration in seconds.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/humanize.c b/cmd/humanize.c
@@ -0,0 +1,144 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+
+#include <err.h> // errx
+#include <errno.h> // EINVAL, ERANGE
+#include <limits.h> // LLONG_MIN, LLONG_MAX
+#include <stdbool.h> // bool
+#include <stdio.h> // fprintf, perror, sscanf
+#include <stdlib.h> // strtonum
+#include <unistd.h> // opt*, getopt
+
+void
+dtosi(double num, char *buf, size_t bufsize, bool iec)
+{
+#define PFX 11
+ char *si_prefixes[PFX] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"};
+ char *iec_prefixes[PFX] = {
+ "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"};
+
+ int div = iec ? 1024 : 1000;
+ char **prefixes = iec ? iec_prefixes : si_prefixes;
+
+ unsigned quotient = 0;
+
+ while(num > div && quotient < PFX)
+ {
+ num /= div;
+ quotient += 1;
+ }
+
+ snprintf(buf, bufsize, "%g %s", num, prefixes[quotient]);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: humanize [-bdt] number\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ // default to -d
+ bool iec = false, time = false;
+
+ int c = -1;
+ while((c = getopt(argc, argv, ":bdt")) != -1)
+ {
+ switch(c)
+ {
+ case 'b':
+ iec = true;
+ break;
+ case 'd':
+ iec = false;
+ break;
+ case 't':
+ time = true;
+ break;
+ case ':':
+ fprintf(stderr, "humanize: Error: Missing operand for option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ case '?':
+ fprintf(stderr, "humanize: Error: Unrecognised option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc < 1)
+ {
+ usage();
+ return 1;
+ }
+
+ for(int argi = 0; argi < argc; argi++)
+ {
+ const char *errstr = NULL;
+ char buf[32] = "";
+
+ if(time)
+ {
+ int year = 0, month = 0, mday = 0, hour = 0, min = 0, sec = 0;
+ long int epoch = 0;
+
+ if(sscanf(argv[argi], "%ld", &epoch) < 1)
+ {
+ perror("humanize: sscanf");
+ return 1;
+ }
+
+ year = epoch / 31556926; // round(year)
+ epoch %= 31556926;
+ month = epoch / 2629743; // year/12
+ epoch %= 2629743;
+ mday = epoch / 86400;
+ epoch %= 86400;
+
+ hour = epoch / 3600;
+ epoch %= 3600;
+ min = epoch / 60;
+ epoch %= 60;
+ sec = epoch;
+
+ if(year > 0) printf("%d年 ", year);
+ if(month > 0) printf("%d月 ", month);
+ if(mday > 0) printf("%d日 ", mday);
+ if(hour > 0) printf("%dh ", hour);
+ if(min > 0) printf("%dm ", min);
+ if(sec > 0) printf("%ds", sec);
+ printf("\n");
+
+ continue;
+ }
+
+ errno = 0;
+ long long n = strtoll(argv[argi], NULL, 10);
+ if(n == LLONG_MIN)
+ {
+ errx(1, "%s is too small", argv[argi]);
+ }
+ if(n == LLONG_MAX)
+ {
+ errx(1, "%s is too large", argv[argi]);
+ }
+ if(errno != 0)
+ {
+ perror("humanize: strtoll");
+ }
+
+ dtosi(n, buf, 32, iec);
+
+ printf("%s\n", buf);
+ }
+
+ return 0;
+}
diff --git a/cmd/lolcat.1 b/cmd/lolcat.1
@@ -0,0 +1,36 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2021-05-15
+.Dt LOLCAT 1
+.Os
+.Sh NAME
+.Nm lolcat
+.Nd a truecolor rainbow filter
+.Sh SYNOPSIS
+.Nm
+.Ar
+.Sh DESCRIPTION
+.Nm
+takes each character from it's standard input and prints it with a truecolor escape sequence, making the text output rainbow colored given enough characters.
+.Pp
+.Nm
+also replaces escape to
+.Sq ^[
+in bold, this allows to strip escape codes easily.
+.Sh KNOWN ISSUES & LIMITATIONS
+.Bl -bullet
+.It
+Frequence is not adjustable yet
+.It
+.Nm
+puts escape codes even when stdout isn't a tty, this is expected, do not call
+.Nm
+otherwise
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Lk https://github.com/busyloop/lolcat "Original lolcat program, in ruby"
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/lolcat.c b/cmd/lolcat.c
@@ -0,0 +1,113 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h> /* errno */
+#include <math.h> /* sin() */
+#include <stdio.h> /* fgetc(), fputc(), printf(), fopen(), fclose() */
+#include <string.h> /* strerror() */
+
+void
+rainbow(double freq, int i)
+{
+ double red, green, blue;
+ double pi = 3.14159;
+
+ red = sin(freq * i + 0) * 127 + 128;
+ green = sin(freq * i + 2 * pi / 3) * 127 + 128;
+ blue = sin(freq * i + 4 * pi / 3) * 127 + 128;
+
+ printf("[38;2;%02d;%02d;%02dm", (int)red, (int)green, (int)blue);
+}
+
+int
+concat(FILE *stream)
+{
+ double freq = 0.1;
+ int i = 0;
+
+ int c;
+ errno = 0;
+ while((c = fgetc(stream)) != EOF)
+ {
+ rainbow(freq, i);
+ i++;
+ if(c == '')
+ {
+ printf("[1m^[[0m");
+ continue;
+ }
+
+ if(fputc(c, stdout) == EOF)
+ {
+ fprintf(stderr, "\n[0mlolcat: Write error: %s\n", strerror(errno));
+ return 1;
+ }
+ }
+
+ if(c == EOF && errno != 0)
+ {
+ fprintf(stderr, "\n[0mlolcat: Read error: %s\n", strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret = 0;
+
+ if(argc <= 1)
+ {
+ ret = concat(stdin);
+ goto end;
+ }
+
+ for(int argi = 1; argi < argc; argi++)
+ {
+ if(strncmp(argv[argi], "-", 2) == 0)
+ {
+ ret = concat(stdin);
+ if(ret != 0)
+ {
+ goto end;
+ }
+ }
+ else if(strncmp(argv[argi], "--", 3) == 0)
+ {
+ continue;
+ }
+ else
+ {
+ FILE *file = fopen(argv[argi], "r");
+
+ if(!file)
+ {
+ fprintf(stderr, "\n[0mlolcat: Error opening ‘%s’: %s\n", argv[argi], strerror(errno));
+ ret = 1;
+ goto end;
+ }
+ else
+ {
+ ret = concat(file);
+ if(ret != 0)
+ {
+ goto end;
+ }
+
+ ret = fclose(file);
+ if(ret != 0)
+ {
+ goto end;
+ }
+ }
+ }
+ }
+
+end:
+ printf("[0m");
+ return ret;
+}
diff --git a/cmd/mdate.c b/cmd/mdate.c
@@ -0,0 +1,22 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h> /* printf */
+#include <time.h> /* time */
+
+// 3600*24.5
+#define cycle 88200
+
+int
+main(void)
+{
+ time_t now = time(NULL);
+ time_t date_now = now / cycle;
+ time_t time_now = now % cycle;
+
+ printf("%lX,%05lX\n", date_now, time_now);
+
+ return 0;
+}
diff --git a/cmd/memsys.c b/cmd/memsys.c
@@ -0,0 +1,37 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <fcntl.h> /* open() */
+#include <stdio.h> /* perror() */
+#include <unistd.h> /* close(), write() */
+
+#define SYS_POWER_STATE "/sys/power/state"
+
+int
+main(void)
+{
+ int fd, err = 0;
+ char *entry = "mem";
+ size_t entry_size = 3;
+
+ fd = open(SYS_POWER_STATE, O_WRONLY);
+
+ if(fd == -1)
+ {
+ perror("memsys: open(\"" SYS_POWER_STATE "\")");
+ err++;
+ }
+ else
+ {
+ if(write(fd, entry, entry_size) < (ssize_t)entry_size)
+ {
+ perror("memsys: write(fd, \"mem\", 3)");
+ err++;
+ }
+ close(fd);
+ }
+
+ return err;
+}
diff --git a/cmd/pat.1 b/cmd/pat.1
@@ -0,0 +1,29 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2023-02-22
+.Dt PAT 1
+.Os
+.Sh NAME
+.Nm pat
+.Nd print concatenated files
+.Sh SYNOPSIS
+.Nm
+.Op Ar files ...
+.Sh DESCRIPTION
+.Nm
+writes the number of files to be read, then in sequence:
+prints their filename; then reads the
+.Ar file
+and writes it on the standard output.
+If no
+.Ar file
+is given,
+.Nm
+uses standard input as one.
+.Sh EXIT STATUS
+.Ex -std
+.Sh STANDARDS
+None applicable.
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/pat.c b/cmd/pat.c
@@ -0,0 +1,89 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h> /* errno */
+#include <fcntl.h> /* open(), O_RDONLY */
+#include <stdio.h> /* fprintf(), fwrite(), BUFSIZ */
+#include <string.h> /* strerror(), strncmp() */
+#include <unistd.h> /* read(), close() */
+
+int files = 1;
+
+int
+concat(int fd, const char *fdname)
+{
+ ssize_t c;
+ char buf[BUFSIZ];
+
+ // File Number as an esccape/containment solution
+ printf("\n### File %d << %s >> ###\n", files++, fdname);
+
+ while((c = read(fd, buf, sizeof(buf))) > 0)
+ {
+ if(fwrite(buf, (size_t)c, 1, stdout) < 0)
+ {
+ fprintf(stderr, "pat: Error writing: %s\n", strerror(errno));
+ return 1;
+ }
+ }
+
+ if(c < 0)
+ {
+ fprintf(stderr, "pat: Error reading ‘%s’: %s\n", fdname, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ if(argc <= 1)
+ {
+ printf("### 1 Files ###");
+ return concat(0, "<stdin>");
+ }
+
+ // no \n, concat starts with one
+ printf("### %d Files ###", argc - 1);
+
+ for(int argi = 1; argi < argc; argi++)
+ {
+ if(strncmp(argv[argi], "-", 2) == 0)
+ {
+ if(concat(0, "<stdin>") != 0)
+ {
+ return 1;
+ }
+ }
+ else if(strncmp(argv[argi], "--", 3) == 0)
+ {
+ continue;
+ }
+ else
+ {
+ int fd = open(argv[argi], O_RDONLY);
+ if(fd < 0)
+ {
+ fprintf(stderr, "pat: Error opening ‘%s’: %s\n", argv[argi], strerror(errno));
+ return 1;
+ }
+
+ if(concat(fd, argv[argi]) != 0)
+ {
+ return 1;
+ }
+
+ if(close(fd) < 0)
+ {
+ fprintf(stderr, "pat: Error closing ‘%s’: %s\n", argv[argi], strerror(errno));
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/cmd/sizeof.c b/cmd/sizeof.c
@@ -0,0 +1,64 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define FORMAT_M "sizeof(%s) == %d bytes; %d bits; (MIN:MAX) == (%d:%d)\n"
+
+static void
+print_size(size_t size, char *type)
+{
+ int ret = printf("sizeof(%s) == %zd bytes; %zd bits\n", type, size, size * CHAR_BIT);
+ if(ret < 0)
+ {
+ exit(1);
+ }
+}
+
+int
+main(void)
+{
+ int c, ret;
+
+ ret = printf("CHAR_BIT == %d\n", CHAR_BIT);
+ if(ret < 0)
+ {
+ exit(1);
+ }
+
+ c = sizeof(int);
+ /* flawfinder: ignore. Not given by user but by a macro */
+ ret = printf(FORMAT_M, "int", c, c * CHAR_BIT, INT_MIN, INT_MAX);
+ if(ret < 0)
+ {
+ exit(1);
+ }
+ c = sizeof(char);
+ /* flawfinder: ignore. Not given by user but by a macro */
+ ret = printf(FORMAT_M, "char", c, c * CHAR_BIT, CHAR_MIN, CHAR_MAX);
+ if(ret < 0)
+ {
+ exit(1);
+ }
+
+ print_size(sizeof(uint8_t), "uint8_t");
+ print_size(sizeof(short), "short");
+ print_size(sizeof(long), "long");
+ print_size(sizeof(long long), "long long");
+ print_size(sizeof(float), "float");
+ print_size(sizeof(double), "double");
+ print_size(sizeof(long double), "long double");
+ print_size(sizeof(double long), "double long");
+ print_size(sizeof(char[BUFSIZ]), "char[BUFSIZ]");
+ print_size(sizeof(char[256]), "char[256]");
+ print_size(sizeof(char[32]), "char[32]");
+ print_size(sizeof(char[2]), "char[2]");
+ print_size(sizeof('a'), "'a'");
+
+ return 0;
+}
diff --git a/cmd/sname.1 b/cmd/sname.1
@@ -0,0 +1,25 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2021-04-05
+.Dt INAME 1
+.Os
+.Sh NAME
+.Nm sname
+.Nd Structured output of system name and info
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+is equivalent to
+.Xr uname 1
+except the output is a sorted tab-separated key-value list, which allows to be extracted and extended.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr look 1 ,
+.Xr uname 3
+.Sh STANDARDS
+No applicable one known.
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/sname.c b/cmd/sname.c
@@ -0,0 +1,38 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+// uname(2) isn't in POSIX
+#define _GNU_SOURCE
+#include <stdio.h> // printf(), perror()
+#include <stdlib.h> // exit()
+#include <sys/utsname.h> // utsname, uname()
+
+void
+print_kv(char *key, char *value)
+{
+ if(printf("%s %s\n", key, value) < 0)
+ {
+ perror("sname: printf");
+ exit(1);
+ }
+}
+
+int
+main()
+{
+ struct utsname name;
+ if(uname(&name) != 0)
+ {
+ perror("uname");
+ return 1;
+ }
+
+ print_kv("machine", name.machine);
+ print_kv("nodename", name.nodename);
+ print_kv("release", name.release);
+ print_kv("sysname", name.sysname);
+ print_kv("version", name.version);
+
+ return 0;
+}
diff --git a/cmd/xcd.c b/cmd/xcd.c
@@ -0,0 +1,259 @@
+// Collection of Unix tools, comparable to coreutils
+// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+#include <ctype.h> /* isprint() */
+#include <errno.h> /* errno */
+#include <math.h> /* sin() */
+#include <stdint.h> /* uint8_t */
+#include <stdio.h> /* printf(), fread(), fopen(), fclose() */
+#include <string.h> /* memset(), strerror() */
+
+struct rgb
+{
+ double red, green, blue;
+};
+
+static struct rgb
+rgb_char(unsigned char i)
+{
+ double freq = 0.018;
+
+ if(i == 0)
+ {
+ return (struct rgb){64, 64, 64};
+ }
+ else
+ {
+ struct rgb color;
+ double pi = 3.14159;
+
+ color.red = sin(freq * i + 0 * pi / 3) * 127 + 128;
+ color.green = sin(freq * i + 2 * pi / 3) * 127 + 128;
+ color.blue = sin(freq * i + 4 * pi / 3) * 127 + 128;
+
+ return color;
+ }
+}
+
+static int
+print_hex_rgb(unsigned char c)
+{
+ struct rgb color = rgb_char(c);
+
+ int ret = printf("[38;2;%d;%d;%dm%02hhx ", (int)color.red, (int)color.green, (int)color.blue, c);
+
+ return (ret <= 0) ? 1 : 0;
+}
+
+static int
+print_xcd_reset()
+{
+ int ret = printf("[0m[48;2;0;0;0m");
+
+ return (ret <= 0) ? 1 : 0;
+}
+
+static int
+print_plain_rgb(char *line, size_t len)
+{
+ if(print_xcd_reset() != 0)
+ {
+ return 1;
+ }
+
+ if(printf(" >") <= 0)
+ {
+ return 1;
+ }
+
+ for(size_t i = 0; i < len; i++)
+ {
+ struct rgb color = rgb_char((unsigned char)line[i]);
+ int ret = 0;
+
+ ret = printf("[38;2;%d;%d;%dm%c",
+ (int)color.red,
+ (int)color.green,
+ (int)color.blue,
+ isprint(line[i]) ? line[i] : '.');
+
+ if(ret <= 0)
+ {
+ return 1;
+ }
+ }
+
+ if(print_xcd_reset() != 0)
+ {
+ return 1;
+ }
+
+ if(printf("<") <= 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+#define WIDTH 16
+
+int
+concat(FILE *stream)
+{
+ int cols = 0;
+ char line[WIDTH];
+ char c;
+ unsigned int bytes = 0;
+ struct rgb pos_rgb;
+ int ret = 0;
+ errno = 0;
+
+ memset(&line, 0, WIDTH);
+
+ if(print_xcd_reset() != 0)
+ {
+ goto werr;
+ }
+
+ pos_rgb = rgb_char((unsigned char)bytes);
+
+ ret = printf(
+ "[38;2;%d;%d;%dm0x%06x ", (int)pos_rgb.red, (int)pos_rgb.green, (int)pos_rgb.blue, bytes);
+ if(ret <= 0)
+ {
+ goto werr;
+ }
+
+ while(fread(&c, 1, 1, stream) > 0)
+ {
+ if(cols >= WIDTH)
+ {
+ print_plain_rgb(line, (size_t)cols);
+ memset(&line, 0, WIDTH);
+
+ pos_rgb = rgb_char((unsigned char)bytes);
+ ret = printf("\n[38;2;%d;%d;%dm0x%06x ",
+ (int)pos_rgb.red,
+ (int)pos_rgb.green,
+ (int)pos_rgb.blue,
+ bytes);
+ if (ret <= 0)
+ {
+ goto werr;
+ }
+
+ cols = 0;
+ }
+
+ ret = print_hex_rgb((unsigned char)c);
+ if(ret != 0)
+ {
+ goto werr;
+ }
+
+ line[cols] = c;
+
+ cols++;
+ bytes++;
+ }
+
+ // Fill the rest of the hex space with spaces
+ for(; cols < WIDTH; cols++)
+ {
+ ret = printf(" ");
+
+ if(ret <= 0)
+ {
+ goto werr;
+ }
+ }
+
+ if(print_xcd_reset() != 0)
+ {
+ goto werr;
+ }
+
+ ret = print_plain_rgb(line, (size_t)cols);
+ if(ret != 0)
+ {
+ goto werr;
+ }
+
+ pos_rgb = rgb_char((unsigned char)bytes);
+ ret = printf(
+ "\n[38;2;%d;%d;%dm0x%06x\n", (int)pos_rgb.red, (int)pos_rgb.green, (int)pos_rgb.blue, bytes);
+ if(ret <= 0)
+ {
+ goto werr;
+ }
+
+ if(print_xcd_reset() != 0)
+ {
+ goto werr;
+ }
+
+ return 0;
+
+werr:
+ fprintf(stderr, "\n[0mxcd: Write error: %s\n", strerror(errno));
+ return 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int err = 0;
+
+ if(argc <= 1)
+ {
+ if(concat(stdin) != 0)
+ {
+ err = 1;
+ goto cleanup;
+ }
+ }
+ else
+ {
+ for(int argi = 1; (err == 0) && (argi < argc); argi++)
+ {
+ if(strncmp(argv[argi], "-", 2) == 0)
+ {
+ if(concat(stdin) != 0)
+ {
+ err = 1;
+ goto cleanup;
+ }
+ }
+ else
+ {
+ FILE *file = fopen(argv[argi], "r");
+ if(!file)
+ {
+ fprintf(stderr, "\n[0mxcd: Error opening ‘%s’: %s\n", argv[argi], strerror(errno));
+ err = 1;
+ goto cleanup;
+ }
+ else
+ {
+ err += concat(file);
+ err += fclose(file);
+
+ if(err != 0)
+ {
+ fprintf(stderr, "\n[0mxcd: Error closing ‘%s’: %s\n", argv[argi], strerror(errno));
+ err = 1;
+ goto cleanup;
+ }
+ }
+ }
+ }
+ }
+
+cleanup:
+ printf("[0m");
+
+ return err;
+}
diff --git a/configure b/configure
@@ -0,0 +1,223 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+DEPS=""
+
+usage() {
+cat <<END
+Usage: [variables] configure [variables]
+
+Variables:
+ PREFIX=DIR
+ BINDIR=DIR
+ SHELLDIR=DIR
+ PERLDIR=DIR
+ MANDIR=DIR
+
+ PKGCONFIG=BIN
+ MSGFMT=BIN
+ CC=BIN
+ MAKE=BIN
+ MANDOC=BIN
+ SHELLCHECK=BIN
+ FLAWFINDER=BIN
+ GCOV=BIN
+ REUSE=BIN
+
+ CFLAGS=OPTIONS
+ LDFLAGS=OPTIONS
+ EXTRA_CFLAGS=OPTIONS
+
+Variables are set in the following order: Default, Environment, Arguments
+
+Dependencies: See the README.md file
+END
+}
+
+is_ok() {
+ status="$?"
+
+ if test $status -eq 0; then
+ printf " OK\n"
+ else
+ printf " FAIL\n"
+ fi
+
+ return $status
+}
+
+or_die() {
+ is_ok || exit 1
+}
+
+pkg_config_check() {
+ printf 'Checking: %s %s ...' "${PKGCONFIG}" "$*"
+ "${PKGCONFIG}" "$@"
+ is_ok
+}
+
+gen_targets() {
+ printf 'EXE = '
+ printf '%s\n ' cmd/*.c | sed 's;.c$;;' | tr -d '\n'
+ echo
+
+ printf 'MAN1 = '
+ printf '%s\n ' cmd/*.1 | tr -d '\n'
+ echo
+}
+
+check_cmd() {
+ var="$1"
+ full_cmd="$2"
+
+ # shellcheck disable=SC2086
+ set -- $full_cmd
+ cmd="$1"
+
+ printf 'Checking $%s = %s command existance ...' "$var" "$full_cmd"
+ command -v "$cmd" >/dev/null ; is_ok
+}
+
+## User configuration
+
+# defaults
+PREFIX="${PREFIX:-/usr/local}"
+
+PKGCONFIG="${PKGCONFIG:-pkg-config}"
+MSGFMT="${MSGFMT:-msgfmt}"
+CC="${CC:-cc}"
+MAKE="${MAKE:-make}"
+GCOV="${GCOV:-gcov}"
+# -DDEBUG: Otherwise assert() does nothing, fine to be removed in production
+CFLAGS="${CFLAGS:--g -O2 -DDEBUG}"
+MANDOC="${MANDOC:-mandoc}"
+SHELLCHECK="${SHELLCHECK:-shellcheck}"
+FLAWFINDER="${FLAWFINDER:-flawfinder}"
+REUSE="${REUSE:-reuse}"
+
+# Also allow variables through arguments
+for i; do
+ case "$i" in
+ -h|--help)
+ usage
+ exit 1
+ ;;
+ -*)
+ printf "Unknown argument ‘%s’\n" "${i}"
+ usage
+ exit 1
+ ;;
+ *=*)
+ # shellcheck disable=SC2163
+ export "$i"
+ shift
+ ;;
+ *)
+ printf "Unknown argument ‘%s’\n" "${i}"
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+# Fallback definitions for dirs, based on $PREFIX
+BINDIR="${BINDIR:-${PREFIX}/bin}"
+SHELLDIR="${SHELLDIR:-${BINDIR}}"
+PERLDIR="${PERLDIR:-${BINDIR}}"
+MANDIR="${MANDIR:-${PREFIX}/share/man}"
+
+# Add some extra CFLAGS
+CFLAGS="${CFLAGS} ${EXTRA_CFLAGS}"
+
+printf 'Pruning old configurations ...'
+rm -f config.mk ; or_die
+
+## System checks
+# commands
+check_cmd PKGCONFIG "$PKGCONFIG" || exit 1
+check_cmd CC "$CC" || exit 1
+check_cmd MAKE "$MAKE" || exit 1
+
+if check_cmd GCOV "$GCOV"
+then
+ :
+else
+ echo 'Notice: "coverage" target will fail'
+ GCOV="false"
+fi
+
+if check_cmd MANDOC "$MANDOC"
+then
+ :
+else
+ echo 'Notice: Linting depending on mandoc disabled'
+ MANDOC="true"
+fi
+
+if check_cmd SHELLCHECK "$SHELLCHECK"
+then
+ :
+else
+ echo 'Notice: Linting depending on shellcheck disabled'
+ SHELLCHECK="true"
+fi
+
+if check_cmd FLAWFINDER "$FLAWFINDER"
+then
+ :
+else
+ echo 'Notice: Linting depending on flawfinder disabled'
+ FLAWFINDER="true"
+fi
+
+if check_cmd REUSE "$REUSE"
+then
+ :
+else
+ echo 'Notice: Copyright linting depending on reuse disabled'
+ REUSE="true"
+fi
+
+echo
+
+# pkg-config
+for dep in ${DEPS}
+do
+ pkg_config_check --exists "$dep" || exit 1
+done
+
+echo
+
+## Configuration write
+
+printf 'Writing to config.mk ...'
+cat >config.mk <<EOF
+# Autogenerated by ./configure
+PREFIX = ${PREFIX}
+BINDIR = ${BINDIR}
+SHELLDIR = ${SHELLDIR}
+PERLDIR = ${PERLDIR}
+MANDIR = ${MANDIR}
+
+PKGCONFIG = ${PKGCONFIG}
+CC = ${CC}
+MAKE = ${MAKE}
+MANDOC = ${MANDOC}
+SHELLCHECK = ${SHELLCHECK}
+FLAWFINDER = ${FLAWFINDER}
+MSGFMT = ${MSGFMT}
+DBG = ${DBG}
+GCOV = ${GCOV}
+REUSE = ${REUSE}
+
+CFLAGS = ${CFLAGS}
+LDFLAGS = ${LDFLAGS}
+EOF
+is_ok
+
+gen_targets >> config.mk ; or_die
+
+echo
+
+echo 'Done, you can now run make'
diff --git a/test-cmd/Kyuafile b/test-cmd/Kyuafile
@@ -0,0 +1,20 @@
+-- SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+-- SPDX-License-Identifier: MPL-2.0
+syntax(2)
+
+test_suite("utils")
+
+basedir = fs.dirname(fs.dirname(current_kyuafile()))
+
+-- 9,$|LC_ALL=C.UTF-8 sort
+-- atf_test_program{name="pat", required_files=basedir.."/cmd/pat", timeout=1}
+atf_test_program{name="args", required_files=basedir.."/cmd/args", timeout=1}
+atf_test_program{name="del", required_files=basedir.."/cmd/del", timeout=1}
+atf_test_program{name="errno", required_files=basedir.."/cmd/errno", timeout=1}
+atf_test_program{name="humanize", required_files=basedir.."/cmd/humanize", timeout=1}
+atf_test_program{name="lolcat", required_files=basedir.."/cmd/lolcat", timeout=1}
+atf_test_program{name="mdate", required_files=basedir.."/cmd/mdate", timeout=1}
+atf_test_program{name="memsys", required_files=basedir.."/cmd/memsys", timeout=1}
+atf_test_program{name="sizeof", required_files=basedir.."/cmd/sizeof", timeout=1}
+atf_test_program{name="sname", required_files=basedir.."/cmd/sname", timeout=1}
+atf_test_program{name="xcd", required_files=basedir.."/cmd/xcd", timeout=1}
diff --git a/test-cmd/args b/test-cmd/args
@@ -0,0 +1,47 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+
+atf_test_case noargs
+noargs_body() {
+ atf_check -o 'inline:argc: 1
+argv[0]: "../cmd/args"
+' ../cmd/args
+}
+
+atf_test_case onearg
+onearg_body() {
+ atf_check -o 'inline:argc: 2
+argv[0]: "../cmd/args"
+argv[1]: "a"
+' ../cmd/args a
+}
+
+atf_test_case twoargs
+twoargs_body() {
+ atf_check -o 'inline:argc: 3
+argv[0]: "../cmd/args"
+argv[1]: "a"
+argv[2]: "b c"
+' ../cmd/args a 'b c'
+}
+
+atf_test_case options
+options_body() {
+ atf_check -o 'inline:argc: 5
+argv[0]: "../cmd/args"
+argv[1]: "-1"
+argv[2]: "+2"
+argv[3]: "--3"
+argv[4]: "/4"
+' ../cmd/args -1 +2 --3 /4
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+ atf_add_test_case noargs
+ atf_add_test_case onearg
+ atf_add_test_case twoargs
+ atf_add_test_case options
+}
diff --git a/test-cmd/date b/test-cmd/date
@@ -0,0 +1,93 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case noargs
+noargs_body() {
+ atf_check -o not-empty ../cmd/date
+}
+
+atf_test_case badarg
+badarg_body() {
+ atf_check -s 'exit:1' -e "inline:date: Error: Unrecognised option: '-x'\ndate [-uR][-d datetime] [+format]\n" ../cmd/date -x
+}
+
+atf_test_case epoch
+epoch_body() {
+ atf_check -o "match:^[0-9]+$" ../cmd/date '+%s'
+ atf_check -o "inline:1155544496\n" ../cmd/date -uR -d @1155544496 '+%s'
+}
+
+atf_test_case rfc3339
+rfc3339_body() {
+ atf_check -o "match:^[0-9]{4}\-[0-9]{2}\-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\+[0-9]{4}$" ../cmd/date '+%FT%T%z'
+ atf_check -o "match:^2006\-08\-14T08:34:56[+\-]00:?00$" ../cmd/date -uR -d @1155544496 '+%FT%T%z'
+}
+
+atf_test_case rfc5322
+rfc5322_body() {
+ atf_check -o "match:^Mon, 14 Aug 2006 08:34:56 [+\-]00:?00$" ../cmd/date -uR -d @1155544496
+}
+
+atf_test_case empty
+empty_body() {
+ atf_check -o 'inline:\n' ../cmd/date '+'
+}
+
+atf_test_case echolike
+echolike_body() {
+ atf_check -o 'inline:hello world\n' ../cmd/date '+hello world'
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for puts()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for puts()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for puts()"
+
+ atf_check -s exit:1 -e 'inline:date: puts: No space left on device\n' sh -c '../cmd/date >/dev/full'
+}
+
+atf_test_case utc
+utc_body() {
+ atf_check -o "match:^[0-9]+$" ../cmd/date -u '+%s'
+}
+
+atf_test_case timestamp
+timestamp_body() {
+ atf_check -o "inline:1970-01-01T00:00:00\n" ../cmd/date -u -d @0 '+%FT%T'
+ atf_check -o "inline:1970-01-01T00:01:09\n" ../cmd/date -u -d @69 '+%FT%T'
+ atf_check -o "inline:1969-12-31T23:58:51\n" ../cmd/date -u -d @-69 '+%FT%T'
+
+ atf_check -s 'exit:1' -e "inline:date: Error: Missing operand for option: '-d'\ndate [-uR][-d datetime] [+format]\n" ../cmd/date -u -d
+
+ # 36893488147419103232 = 2^65
+ atf_check -s 'exit:1' -e not-empty ../cmd/date -u -d @36893488147419103232
+}
+
+atf_test_case isodate
+isodate_body() {
+ atf_check -o "inline:0\n" ../cmd/date -u -d "1970-01-01T00:00:00Z" '+%s'
+ atf_check -o "inline:69\n" ../cmd/date -u -d "1970-01-01T00:01:09Z" '+%s'
+ atf_check -o "inline:-69\n" ../cmd/date -u -d "1969-12-31T23:58:51Z" '+%s'
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case noargs
+ atf_add_test_case badarg
+ atf_add_test_case empty
+ atf_add_test_case echolike
+ atf_add_test_case devfull
+
+ atf_add_test_case epoch
+ atf_add_test_case rfc3339
+ atf_add_test_case rfc5322
+ atf_add_test_case utc
+
+ atf_add_test_case timestamp
+ atf_add_test_case isodate
+}
diff --git a/test-cmd/del b/test-cmd/del
@@ -0,0 +1,57 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case basic cleanup
+basic_body() {
+ touch inputs/del-test || atf_fail "touching del-test"
+ atf_check ../cmd/del inputs/del-test
+
+ mkdir -p inputs/del-test.d || atf_fail "mkdir del-test.d"
+ atf_check ../cmd/del inputs/del-test.d
+}
+basic_cleanup() {
+ rm -fr inputs/del-test inputs/del-test.d || atf_fail "rm inputs/del-test{,.d}"
+}
+
+atf_test_case nopermf cleanup
+nopermf_body() {
+ touch inputs/chmod_000 || atf_fail "touching chmod_000"
+ chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000"
+
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:del: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/del inputs/chmod_000
+}
+nopermf_cleanup() {
+ rm -fr inputs/chmod_000 inputs/chmod_000.d || atf_fail "rm chmod_000{,.d}"
+}
+
+atf_test_case nopermd cleanup
+nopermd_body() {
+ mkdir -p inputs/chmod_000.d || atf_fail "mkdir chmod_000.d"
+ chmod 0000 inputs/chmod_000.d || atf_fail "chmod 0000 chmod_000.d"
+
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:del: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/del inputs/chmod_000.d
+}
+nopermd_cleanup() {
+ rm -fr inputs/chmod_000 inputs/chmod_000.d || atf_fail "rm chmod_000{,.d}"
+}
+
+atf_test_case enoent
+enoent_body() {
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:del: remove(/var/empty/e/no/ent) error: No such file or directory\n' ../cmd/del /var/empty/e/no/ent
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+ atf_add_test_case basic
+
+ # None of the supported implementations of remove(3) seem to have checks
+ # based on file mode
+ #atf_add_test_case nopermf
+ #atf_add_test_case nopermd
+
+ atf_add_test_case enoent
+}
diff --git a/test-cmd/errno b/test-cmd/errno
@@ -0,0 +1,51 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case simple
+simple_body() {
+ atf_check -o "inline:Operation not permitted\n" ../cmd/errno 1
+}
+
+atf_test_case noargs
+noargs_body() {
+ atf_check -s exit:1 -o "inline:usage: errno <number>\n" ../cmd/errno
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for puts()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for puts()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for puts()"
+
+ atf_check -s exit:1 sh -c '../cmd/errno 1 >/dev/full'
+}
+
+atf_test_case einval
+einval_body() {
+ if has_glibc; then
+ atf_check -s exit:0 -o "inline:Unknown error -1\n" ../cmd/errno -1
+ elif has_musl; then
+ atf_check -s exit:0 -o "inline:No error information\n" ../cmd/errno -1
+ else
+ atf_check -s exit:1 -e "inline:errno: strerror: Invalid argument\n" ../cmd/errno -1
+ fi
+}
+
+atf_test_case erange
+erange_body() {
+ # 36893488147419103232 = 2^65
+ atf_check -s 'exit:1' -e not-empty ../cmd/errno 36893488147419103232
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case simple
+ atf_add_test_case noargs
+ atf_add_test_case devfull
+ atf_add_test_case einval
+ atf_add_test_case erange
+}
diff --git a/test-cmd/humanize b/test-cmd/humanize
@@ -0,0 +1,107 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case one
+one_body() {
+ atf_check -o "inline:1 \n" ../cmd/humanize 1
+ atf_check -o "inline:1 \n" ../cmd/humanize -d 1
+ atf_check -o "inline:1 B\n" ../cmd/humanize -b 1
+}
+
+atf_test_case two
+two_body() {
+ atf_check -o "inline:12 \n" ../cmd/humanize 12
+ atf_check -o "inline:12 \n" ../cmd/humanize -d 12
+ atf_check -o "inline:12 B\n" ../cmd/humanize -b 12
+}
+
+atf_test_case three
+three_body() {
+ atf_check -o "inline:123 \n" ../cmd/humanize 123
+ atf_check -o "inline:123 \n" ../cmd/humanize -d 123
+ atf_check -o "inline:123 B\n" ../cmd/humanize -b 123
+}
+
+# 1 234
+atf_test_case four
+four_body() {
+ atf_check -o "inline:1.234 k\n" ../cmd/humanize 1234
+ atf_check -o "inline:1.234 k\n" ../cmd/humanize -d 1234
+ atf_check -o "inline:1.20508 KiB\n" ../cmd/humanize -b 1234
+}
+
+# 12 345
+atf_test_case five
+five_body() {
+ atf_check -o "inline:12.345 k\n" ../cmd/humanize 12345
+ atf_check -o "inline:12.345 k\n" ../cmd/humanize -d 12345
+ atf_check -o "inline:12.0557 KiB\n" ../cmd/humanize -b 12345
+}
+
+# 123 456
+atf_test_case six
+six_body() {
+ atf_check -o "inline:123.456 k\n" ../cmd/humanize 123456
+ atf_check -o "inline:123.456 k\n" ../cmd/humanize -d 123456
+ atf_check -o "inline:120.562 KiB\n" ../cmd/humanize -b 123456
+}
+
+# 1234 4567
+atf_test_case seven
+seven_body() {
+ atf_check -o "inline:1.23457 M\n" ../cmd/humanize 1234567
+ atf_check -o "inline:1.23457 M\n" ../cmd/humanize -d 1234567
+ atf_check -o "inline:1.17737 MiB\n" ../cmd/humanize -b 1234567
+}
+
+atf_test_case noarg
+noarg_body() {
+ atf_check -s exit:1 -e 'inline:Usage: humanize [-bdt] number\n' ../cmd/humanize
+}
+
+atf_test_case badflag
+badflag_body() {
+ atf_check -s exit:1 -e "inline:humanize: Error: Unrecognised option: '-f'\n"'Usage: humanize [-bdt] number\n' ../cmd/humanize -f
+ atf_check -s exit:1 -e "inline:humanize: Error: Unrecognised option: '-f'\n"'Usage: humanize [-bdt] number\n' ../cmd/humanize -f 123
+}
+
+atf_test_case duration
+duration_body() {
+ atf_check -o "inline:1s\n" ../cmd/humanize -t 1
+ atf_check -o "inline:1m \n" ../cmd/humanize -t 60
+ atf_check -o "inline:1h \n" ../cmd/humanize -t 3600
+ atf_check -o "inline:1日 \n" ../cmd/humanize -t 86400
+ atf_check -o "inline:1月 \n" ../cmd/humanize -t 2629743
+ atf_check -o "inline:1年 \n" ../cmd/humanize -t 31556926
+ atf_check -o "inline:1年 1月 1日 1h 1m 1s\n" ../cmd/humanize -t 34276730
+}
+
+atf_test_case limits
+limits_body() {
+ atf_check -o 'inline:9.22337 E\n' ../cmd/humanize 9223372036854775806
+ atf_check -s exit:1 -e 'inline:humanize: 9223372036854775808 is too large\n' ../cmd/humanize 9223372036854775808
+
+ # Not as great as it should but at least not a lie
+ atf_check -o 'inline:-9.22337e+18 \n' ../cmd/humanize -- -9223372036854775807
+
+ atf_check -s exit:1 -e 'inline:humanize: -9223372036854775809 is too small\n' ../cmd/humanize -- -9223372036854775809
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ atf_add_test_case noarg
+ atf_add_test_case badflag
+ atf_add_test_case limits
+
+ atf_add_test_case one
+ atf_add_test_case two
+ atf_add_test_case three
+ atf_add_test_case four
+ atf_add_test_case five
+ atf_add_test_case six
+ atf_add_test_case seven
+
+ atf_add_test_case duration
+}
diff --git a/test-cmd/inputs/all_bytes b/test-cmd/inputs/all_bytes
Binary files differ.
diff --git a/test-cmd/lolcat b/test-cmd/lolcat
@@ -0,0 +1,92 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case allfile
+allfile_body() {
+ atf_check -o file:outputs/lolcat/all_bytes ../cmd/lolcat inputs/all_bytes
+}
+
+atf_test_case allinput
+allinput_body() {
+ atf_check -o file:outputs/lolcat/all_bytes ../cmd/lolcat <inputs/all_bytes
+}
+
+atf_test_case alldashinput
+alldashinput_body() {
+ atf_check -o file:outputs/lolcat/all_bytes ../cmd/lolcat - <inputs/all_bytes
+}
+
+atf_test_case devnull
+devnull_body() {
+ if [ "$(uname -s)" = "FreeBSD" ] || [ "$(uname -s)" = "NetBSD" ]; then
+ # FIXME
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Read error: Inappropriate ioctl for device\n' -o 'inline:[0m' ../cmd/lolcat /dev/null
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Read error: Inappropriate ioctl for device\n' -o 'inline:[0m' ../cmd/lolcat </dev/null
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Read error: Inappropriate ioctl for device\n' -o 'inline:[0m' ../cmd/lolcat - </dev/null
+ else
+ atf_check -o 'inline:[0m' ../cmd/lolcat /dev/null
+ atf_check -o 'inline:[0m' ../cmd/lolcat </dev/null
+ atf_check -o 'inline:[0m' ../cmd/lolcat - </dev/null
+ fi
+}
+
+atf_test_case noperm cleanup
+noperm_body() {
+ touch inputs/chmod_000 || atf_fail "touching chmod_000"
+ chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000"
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Error opening ‘inputs/chmod_000’: Permission denied\n' -o 'inline:[0m' ../cmd/lolcat inputs/chmod_000
+}
+noperm_cleanup() {
+ chmod 0600 inputs/chmod_000 || atf_fail "chmod 0600 chmod_000"
+ rm inputs/chmod_000 || atf_fail "rm chmod_000"
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for fputc()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD badly handling write errors"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD badly handling write errors"
+
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Write error: No space left on device\n' sh -c '../cmd/lolcat inputs/all_bytes >/dev/full'
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Write error: No space left on device\n' sh -c '../cmd/lolcat <inputs/all_bytes >/dev/full'
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Write error: No space left on device\n' sh -c '../cmd/lolcat - <inputs/all_bytes >/dev/full'
+}
+
+atf_test_case readslash
+readslash_body() {
+ [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD allows to read directories"
+
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Read error: Is a directory\n' -o 'inline:[0m' ../cmd/lolcat ./
+}
+
+atf_test_case enoent
+enoent_body() {
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' -o 'inline:[0m' ../cmd/lolcat /var/empty/e/no/ent
+}
+
+atf_test_case doubledash
+doubledash_body() {
+ atf_check -o file:outputs/lolcat/all_bytes -- ../cmd/lolcat -- inputs/all_bytes
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:\n[0mlolcat: Error opening ‘---’: No such file or directory\n' -o 'inline:[0m' -- ../cmd/lolcat --- inputs/all_bytes
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case allfile
+ atf_add_test_case allinput
+ atf_add_test_case alldashinput
+ atf_add_test_case devnull
+ atf_add_test_case noperm
+ atf_add_test_case devfull
+ atf_add_test_case readslash
+ atf_add_test_case enoent
+ atf_add_test_case doubledash
+}
diff --git a/test-cmd/mdate b/test-cmd/mdate
@@ -0,0 +1,13 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case basic
+basic_body() {
+ atf_check -o "match:^[0-9A-F]+,[0-9A-F]+$" ../cmd/mdate
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+ atf_add_test_case basic
+}
diff --git a/test-cmd/memsys b/test-cmd/memsys
@@ -0,0 +1,48 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+
+atf_test_case basic cleanup
+basic_body() {
+ command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found"
+ [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set"
+
+ atf_check touch ./tmp-memsys
+ atf_check -- "${BWRAP:-bwrap}" --bind / / --bind ./tmp-memsys /sys/power/state ../cmd/memsys
+ atf_check -o 'inline:mem' cat tmp-memsys
+}
+basic_cleanup() {
+ rm -f tmp-memsys
+}
+
+atf_test_case chmod_000 cleanup
+chmod_000_body() {
+ command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found"
+ [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set"
+
+ atf_check touch ./tmp-memsys.000
+ atf_check chmod 000 ./tmp-memsys.000
+ atf_check -s exit:1 -e 'inline:memsys: open("/sys/power/state"): Permission denied\n' -- "${BWRAP:-bwrap}" --bind / / --bind ./tmp-memsys.000 /sys/power/state ../cmd/memsys
+ atf_check chmod 600 ./tmp-memsys.000
+ atf_check -o empty cat tmp-memsys.000
+}
+chmod_000_cleanup() {
+ rm -f tmp-memsys.000
+}
+
+atf_test_case devfull
+devfull_body() {
+ command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found"
+ [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set"
+
+ atf_check -s exit:1 -e 'inline:memsys: open("/sys/power/state"): Permission denied\n' -- "${BWRAP:-bwrap}" --bind / / --bind /dev/full /sys/power/state ../cmd/memsys
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ atf_add_test_case basic
+ atf_add_test_case chmod_000
+ atf_add_test_case devfull
+}
diff --git a/test-cmd/mkdir b/test-cmd/mkdir
@@ -0,0 +1,45 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case empty
+empty_body() {
+ atf_check -s exit:1 -e 'inline:mkdir: missing operands\nUsage: mkdir [-p] [-m mode] [directories...]\n' ../cmd/mkdir
+}
+
+atf_test_case foo cleanup
+foo_body() {
+ atf_check rm -fr mkdir-foo
+ atf_check ../cmd/mkdir mkdir-foo
+ atf_check test -d mkdir-foo
+}
+foo_cleanup() {
+ rm -fr mkdir-foo
+}
+
+atf_test_case p_flag cleanup
+p_flag_body() {
+ atf_check rm -fr mkdir-p_flag/1/2/3
+ atf_check -s exit:1 -e 'inline:mkdir: No such file or directory\n' ../cmd/mkdir mkdir-p_flag/1/2/3
+ atf_check ../cmd/mkdir -p mkdir-p_flag/1/2/3
+ atf_check test -d mkdir-p_flag/1/2/3
+}
+p_flag_cleanup() {
+ rm -fr mkdir-p_flag/1/2/3
+}
+
+atf_test_case devnull
+devnull_body() {
+ atf_check -s exit:1 -e 'inline:mkdir: File exists\n' ../cmd/mkdir /dev/null
+ atf_check -s exit:1 -e 'inline:mkdir: File exists\n' ../cmd/mkdir -p /dev/null
+ atf_check -s exit:1 -e 'inline:mkdir: Not a directory\n' ../cmd/mkdir /dev/null/no
+ atf_check -s exit:1 -e 'inline:mkdir: Not a directory\n' ../cmd/mkdir -p /dev/null/no
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+ atf_add_test_case empty
+ atf_add_test_case foo
+ #atf_add_test_case p_flag
+ atf_add_test_case devnull
+}
diff --git a/test-cmd/outputs/lolcat/all_bytes b/test-cmd/outputs/lolcat/all_bytes
Binary files differ.
diff --git a/test-cmd/outputs/lolcat/all_bytes.l b/test-cmd/outputs/lolcat/all_bytes.l
@@ -0,0 +1,12 @@
+\000\001\002\003\004\005\006\a\b\t$
+\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\
+\035\036\037 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWX\
+YZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\
+\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\
+\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\
+\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\
+\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\
+\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\
+\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\
+\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\
+\375\376\377$
diff --git a/test-cmd/outputs/xcd/all_bytes b/test-cmd/outputs/xcd/all_bytes
@@ -0,0 +1,18 @@
+[0m[48;2;0;0;0m[38;2;64;64;64m0x000000 [38;2;64;64;64m00 [38;2;130;236;16m01 [38;2;132;235;15m02 [38;2;134;234;14m03 [38;2;137;233;13m04 [38;2;139;231;12m05 [38;2;141;230;11m06 [38;2;143;229;10m07 [38;2;146;227;10m08 [38;2;148;226;9m09 [38;2;150;224;8m0a [38;2;152;223;7m0b [38;2;155;221;6m0c [38;2;157;220;6m0d [38;2;159;218;5m0e [38;2;161;217;5m0f [0m[48;2;0;0;0m >[38;2;64;64;64m.[38;2;130;236;16m.[38;2;132;235;15m.[38;2;134;234;14m.[38;2;137;233;13m.[38;2;139;231;12m.[38;2;141;230;11m.[38;2;143;229;10m.[38;2;146;227;10m.[38;2;148;226;9m.[38;2;150;224;8m.[38;2;152;223;7m.[38;2;155;221;6m.[38;2;157;220;6m.[38;2;159;218;5m.[38;2;161;217;5m.[0m[48;2;0;0;0m<
+[38;2;164;215;4m0x000010 [38;2;164;215;4m10 [38;2;166;213;3m11 [38;2;168;212;3m12 [38;2;170;210;3m13 [38;2;172;208;2m14 [38;2;174;206;2m15 [38;2;176;204;2m16 [38;2;179;203;1m17 [38;2;181;201;1m18 [38;2;183;199;1m19 [38;2;185;197;1m1a [38;2;187;195;1m1b [38;2;189;193;1m1c [38;2;191;191;1m1d [38;2;193;189;1m1e [38;2;195;187;1m1f [0m[48;2;0;0;0m >[38;2;164;215;4m.[38;2;166;213;3m.[38;2;168;212;3m.[38;2;170;210;3m.[38;2;172;208;2m.[38;2;174;206;2m.[38;2;176;204;2m.[38;2;179;203;1m.[38;2;181;201;1m.[38;2;183;199;1m.[38;2;185;197;1m.[38;2;187;195;1m.[38;2;189;193;1m.[38;2;191;191;1m.[38;2;193;189;1m.[38;2;195;187;1m.[0m[48;2;0;0;0m<
+[38;2;197;185;1m0x000020 [38;2;197;185;1m20 [38;2;199;183;1m21 [38;2;200;181;1m22 [38;2;202;179;1m23 [38;2;204;177;1m24 [38;2;206;175;2m25 [38;2;208;173;2m26 [38;2;210;170;3m27 [38;2;211;168;3m28 [38;2;213;166;3m29 [38;2;215;164;4m2a [38;2;216;162;4m2b [38;2;218;160;5m2c [38;2;219;157;6m2d [38;2;221;155;6m2e [38;2;223;153;7m2f [0m[48;2;0;0;0m >[38;2;197;185;1m [38;2;199;183;1m![38;2;200;181;1m"[38;2;202;179;1m#[38;2;204;177;1m$[38;2;206;175;2m%[38;2;208;173;2m&[38;2;210;170;3m'[38;2;211;168;3m([38;2;213;166;3m)[38;2;215;164;4m*[38;2;216;162;4m+[38;2;218;160;5m,[38;2;219;157;6m-[38;2;221;155;6m.[38;2;223;153;7m/[0m[48;2;0;0;0m<
+[38;2;224;151;8m0x000030 [38;2;224;151;8m30 [38;2;226;148;9m31 [38;2;227;146;9m32 [38;2;228;144;10m33 [38;2;230;142;11m34 [38;2;231;139;12m35 [38;2;232;137;13m36 [38;2;234;135;14m37 [38;2;235;132;15m38 [38;2;236;130;16m39 [38;2;237;128;17m3a [38;2;238;126;18m3b [38;2;240;123;20m3c [38;2;241;121;21m3d [38;2;242;119;22m3e [38;2;243;116;23m3f [0m[48;2;0;0;0m >[38;2;224;151;8m0[38;2;226;148;9m1[38;2;227;146;9m2[38;2;228;144;10m3[38;2;230;142;11m4[38;2;231;139;12m5[38;2;232;137;13m6[38;2;234;135;14m7[38;2;235;132;15m8[38;2;236;130;16m9[38;2;237;128;17m:[38;2;238;126;18m;[38;2;240;123;20m<[38;2;241;121;21m=[38;2;242;119;22m>[38;2;243;116;23m?[0m[48;2;0;0;0m<
+[38;2;244;114;25m0x000040 [38;2;244;114;25m40 [38;2;244;112;26m41 [38;2;245;110;28m42 [38;2;246;107;29m43 [38;2;247;105;30m44 [38;2;248;103;32m45 [38;2;248;101;33m46 [38;2;249;98;35m47 [38;2;250;96;37m48 [38;2;250;94;38m49 [38;2;251;92;40m4a [38;2;251;90;41m4b [38;2;252;87;43m4c [38;2;252;85;45m4d [38;2;253;83;47m4e [38;2;253;81;48m4f [0m[48;2;0;0;0m >[38;2;244;114;25m@[38;2;244;112;26mA[38;2;245;110;28mB[38;2;246;107;29mC[38;2;247;105;30mD[38;2;248;103;32mE[38;2;248;101;33mF[38;2;249;98;35mG[38;2;250;96;37mH[38;2;250;94;38mI[38;2;251;92;40mJ[38;2;251;90;41mK[38;2;252;87;43mL[38;2;252;85;45mM[38;2;253;83;47mN[38;2;253;81;48mO[0m[48;2;0;0;0m<
+[38;2;253;79;50m0x000050 [38;2;253;79;50m50 [38;2;254;77;52m51 [38;2;254;75;54m52 [38;2;254;73;56m53 [38;2;254;71;58m54 [38;2;254;69;60m55 [38;2;254;67;62m56 [38;2;254;65;63m57 [38;2;254;63;65m58 [38;2;254;61;67m59 [38;2;254;59;69m5a [38;2;254;57;72m5b [38;2;254;55;74m5c [38;2;254;53;76m5d [38;2;254;51;78m5e [38;2;253;49;80m5f [0m[48;2;0;0;0m >[38;2;253;79;50mP[38;2;254;77;52mQ[38;2;254;75;54mR[38;2;254;73;56mS[38;2;254;71;58mT[38;2;254;69;60mU[38;2;254;67;62mV[38;2;254;65;63mW[38;2;254;63;65mX[38;2;254;61;67mY[38;2;254;59;69mZ[38;2;254;57;72m[[38;2;254;55;74m\[38;2;254;53;76m][38;2;254;51;78m^[38;2;253;49;80m_[0m[48;2;0;0;0m<
+[38;2;253;48;82m0x000060 [38;2;253;48;82m60 [38;2;253;46;84m61 [38;2;252;44;86m62 [38;2;252;42;88m63 [38;2;251;41;91m64 [38;2;251;39;93m65 [38;2;250;37;95m66 [38;2;249;36;97m67 [38;2;249;34;99m68 [38;2;248;33;102m69 [38;2;247;31;104m6a [38;2;247;30;106m6b [38;2;246;28;108m6c [38;2;245;27;111m6d [38;2;244;25;113m6e [38;2;243;24;115m6f [0m[48;2;0;0;0m >[38;2;253;48;82m`[38;2;253;46;84ma[38;2;252;44;86mb[38;2;252;42;88mc[38;2;251;41;91md[38;2;251;39;93me[38;2;250;37;95mf[38;2;249;36;97mg[38;2;249;34;99mh[38;2;248;33;102mi[38;2;247;31;104mj[38;2;247;30;106mk[38;2;246;28;108ml[38;2;245;27;111mm[38;2;244;25;113mn[38;2;243;24;115mo[0m[48;2;0;0;0m<
+[38;2;242;23;118m0x000070 [38;2;242;23;118m70 [38;2;241;22;120m71 [38;2;240;20;122m72 [38;2;239;19;124m73 [38;2;238;18;127m74 [38;2;237;17;129m75 [38;2;236;16;131m76 [38;2;234;15;134m77 [38;2;233;14;136m78 [38;2;232;13;138m79 [38;2;230;12;140m7a [38;2;229;11;143m7b [38;2;228;10;145m7c [38;2;226;9;147m7d [38;2;225;8;149m7e [38;2;223;7;152m7f [0m[48;2;0;0;0m >[38;2;242;23;118mp[38;2;241;22;120mq[38;2;240;20;122mr[38;2;239;19;124ms[38;2;238;18;127mt[38;2;237;17;129mu[38;2;236;16;131mv[38;2;234;15;134mw[38;2;233;14;136mx[38;2;232;13;138my[38;2;230;12;140mz[38;2;229;11;143m{[38;2;228;10;145m|[38;2;226;9;147m}[38;2;225;8;149m~[38;2;223;7;152m.[0m[48;2;0;0;0m<
+[38;2;222;7;154m0x000080 [38;2;222;7;154m80 [38;2;220;6;156m81 [38;2;219;5;158m82 [38;2;217;5;161m83 [38;2;216;4;163m84 [38;2;214;4;165m85 [38;2;212;3;167m86 [38;2;210;3;169m87 [38;2;209;2;171m88 [38;2;207;2;174m89 [38;2;205;2;176m8a [38;2;203;1;178m8b [38;2;201;1;180m8c [38;2;200;1;182m8d [38;2;198;1;184m8e [38;2;196;1;186m8f [0m[48;2;0;0;0m >[38;2;222;7;154m.[38;2;220;6;156m.[38;2;219;5;158m.[38;2;217;5;161m.[38;2;216;4;163m.[38;2;214;4;165m.[38;2;212;3;167m.[38;2;210;3;169m.[38;2;209;2;171m.[38;2;207;2;174m.[38;2;205;2;176m.[38;2;203;1;178m.[38;2;201;1;180m.[38;2;200;1;182m.[38;2;198;1;184m.[38;2;196;1;186m.[0m[48;2;0;0;0m<
+[38;2;194;1;188m0x000090 [38;2;194;1;188m90 [38;2;192;1;190m91 [38;2;190;1;192m92 [38;2;188;1;194m93 [38;2;186;1;196m94 [38;2;184;1;198m95 [38;2;182;1;200m96 [38;2;180;1;202m97 [38;2;178;1;204m98 [38;2;176;2;205m99 [38;2;173;2;207m9a [38;2;171;2;209m9b [38;2;169;3;211m9c [38;2;167;3;212m9d [38;2;165;4;214m9e [38;2;163;4;216m9f [0m[48;2;0;0;0m >[38;2;194;1;188m.[38;2;192;1;190m.[38;2;190;1;192m.[38;2;188;1;194m.[38;2;186;1;196m.[38;2;184;1;198m.[38;2;182;1;200m.[38;2;180;1;202m.[38;2;178;1;204m.[38;2;176;2;205m.[38;2;173;2;207m.[38;2;171;2;209m.[38;2;169;3;211m.[38;2;167;3;212m.[38;2;165;4;214m.[38;2;163;4;216m.[0m[48;2;0;0;0m<
+[38;2;160;5;217m0x0000a0 [38;2;160;5;217ma0 [38;2;158;5;219ma1 [38;2;156;6;220ma2 [38;2;154;7;222ma3 [38;2;151;8;224ma4 [38;2;149;8;225ma5 [38;2;147;9;226ma6 [38;2;145;10;228ma7 [38;2;142;11;229ma8 [38;2;140;12;231ma9 [38;2;138;13;232maa [38;2;136;14;233mab [38;2;133;15;234mac [38;2;131;16;236mad [38;2;129;17;237mae [38;2;126;18;238maf [0m[48;2;0;0;0m >[38;2;160;5;217m.[38;2;158;5;219m.[38;2;156;6;220m.[38;2;154;7;222m.[38;2;151;8;224m.[38;2;149;8;225m.[38;2;147;9;226m.[38;2;145;10;228m.[38;2;142;11;229m.[38;2;140;12;231m.[38;2;138;13;232m.[38;2;136;14;233m.[38;2;133;15;234m.[38;2;131;16;236m.[38;2;129;17;237m.[38;2;126;18;238m.[0m[48;2;0;0;0m<
+[38;2;124;19;239m0x0000b0 [38;2;124;19;239mb0 [38;2;122;20;240mb1 [38;2;120;22;241mb2 [38;2;117;23;242mb3 [38;2;115;24;243mb4 [38;2;113;26;244mb5 [38;2;110;27;245mb6 [38;2;108;28;246mb7 [38;2;106;30;247mb8 [38;2;104;31;247mb9 [38;2;101;33;248mba [38;2;99;34;249mbb [38;2;97;36;250mbc [38;2;95;38;250mbd [38;2;93;39;251mbe [38;2;90;41;251mbf [0m[48;2;0;0;0m >[38;2;124;19;239m.[38;2;122;20;240m.[38;2;120;22;241m.[38;2;117;23;242m.[38;2;115;24;243m.[38;2;113;26;244m.[38;2;110;27;245m.[38;2;108;28;246m.[38;2;106;30;247m.[38;2;104;31;247m.[38;2;101;33;248m.[38;2;99;34;249m.[38;2;97;36;250m.[38;2;95;38;250m.[38;2;93;39;251m.[38;2;90;41;251m.[0m[48;2;0;0;0m<
+[38;2;88;43;252m0x0000c0 [38;2;88;43;252mc0 [38;2;86;44;252mc1 [38;2;84;46;253mc2 [38;2;82;48;253mc3 [38;2;80;50;253mc4 [38;2;78;51;254mc5 [38;2;75;53;254mc6 [38;2;73;55;254mc7 [38;2;71;57;254mc8 [38;2;69;59;254mc9 [38;2;67;61;254mca [38;2;65;63;254mcb [38;2;63;65;254mcc [38;2;61;67;254mcd [38;2;59;69;254mce [38;2;57;71;254mcf [0m[48;2;0;0;0m >[38;2;88;43;252m.[38;2;86;44;252m.[38;2;84;46;253m.[38;2;82;48;253m.[38;2;80;50;253m.[38;2;78;51;254m.[38;2;75;53;254m.[38;2;73;55;254m.[38;2;71;57;254m.[38;2;69;59;254m.[38;2;67;61;254m.[38;2;65;63;254m.[38;2;63;65;254m.[38;2;61;67;254m.[38;2;59;69;254m.[38;2;57;71;254m.[0m[48;2;0;0;0m<
+[38;2;56;73;254m0x0000d0 [38;2;56;73;254md0 [38;2;54;75;254md1 [38;2;52;77;254md2 [38;2;50;79;253md3 [38;2;48;81;253md4 [38;2;46;83;253md5 [38;2;45;86;252md6 [38;2;43;88;252md7 [38;2;41;90;251md8 [38;2;40;92;251md9 [38;2;38;94;250mda [38;2;36;96;250mdb [38;2;35;99;249mdc [38;2;33;101;248mdd [38;2;32;103;248mde [38;2;30;105;247mdf [0m[48;2;0;0;0m >[38;2;56;73;254m.[38;2;54;75;254m.[38;2;52;77;254m.[38;2;50;79;253m.[38;2;48;81;253m.[38;2;46;83;253m.[38;2;45;86;252m.[38;2;43;88;252m.[38;2;41;90;251m.[38;2;40;92;251m.[38;2;38;94;250m.[38;2;36;96;250m.[38;2;35;99;249m.[38;2;33;101;248m.[38;2;32;103;248m.[38;2;30;105;247m.[0m[48;2;0;0;0m<
+[38;2;29;108;246m0x0000e0 [38;2;29;108;246me0 [38;2;27;110;245me1 [38;2;26;112;244me2 [38;2;25;114;243me3 [38;2;23;117;242me4 [38;2;22;119;241me5 [38;2;21;121;240me6 [38;2;20;124;239me7 [38;2;18;126;238me8 [38;2;17;128;237me9 [38;2;16;130;236mea [38;2;15;133;235meb [38;2;14;135;234mec [38;2;13;137;232med [38;2;12;140;231mee [38;2;11;142;230mef [0m[48;2;0;0;0m >[38;2;29;108;246m.[38;2;27;110;245m.[38;2;26;112;244m.[38;2;25;114;243m.[38;2;23;117;242m.[38;2;22;119;241m.[38;2;21;121;240m.[38;2;20;124;239m.[38;2;18;126;238m.[38;2;17;128;237m.[38;2;16;130;236m.[38;2;15;133;235m.[38;2;14;135;234m.[38;2;13;137;232m.[38;2;12;140;231m.[38;2;11;142;230m.[0m[48;2;0;0;0m<
+[38;2;10;144;228m0x0000f0 [38;2;10;144;228mf0 [38;2;9;146;227mf1 [38;2;8;149;225mf2 [38;2;8;151;224mf3 [38;2;7;153;222mf4 [38;2;6;155;221mf5 [38;2;6;158;219mf6 [38;2;5;160;218mf7 [38;2;4;162;216mf8 [38;2;4;164;214mf9 [38;2;3;166;213mfa [38;2;3;169;211mfb [38;2;2;171;209mfc [38;2;2;173;208mfd [38;2;2;175;206mfe [38;2;1;177;204mff [0m[48;2;0;0;0m[0m[48;2;0;0;0m >[38;2;10;144;228m.[38;2;9;146;227m.[38;2;8;149;225m.[38;2;8;151;224m.[38;2;7;153;222m.[38;2;6;155;221m.[38;2;6;158;219m.[38;2;5;160;218m.[38;2;4;162;216m.[38;2;4;164;214m.[38;2;3;166;213m.[38;2;3;169;211m.[38;2;2;171;209m.[38;2;2;173;208m.[38;2;2;175;206m.[38;2;1;177;204m.[0m[48;2;0;0;0m<
+[38;2;64;64;64m0x000100
+[0m[48;2;0;0;0m[0m
+\ No newline at end of file
diff --git a/test-cmd/outputs/xcd/null b/test-cmd/outputs/xcd/null
@@ -0,0 +1,3 @@
+[0m[48;2;0;0;0m[38;2;64;64;64m0x000000 [0m[48;2;0;0;0m[0m[48;2;0;0;0m >[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[38;2;64;64;64m.[0m[48;2;0;0;0m<
+[38;2;64;64;64m0x000000
+[0m[48;2;0;0;0m[0m
+\ No newline at end of file
diff --git a/test-cmd/pat b/test-cmd/pat
@@ -0,0 +1,78 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case allfile
+allfile_body() {
+ atf_check -o file:outputs/pat/all_bytes ../cmd/pat outputs/pat/all_bytes
+}
+
+atf_test_case allinput
+allinput_body() {
+ atf_check -o file:outputs/pat/all_bytes ../cmd/pat <outputs/pat/all_bytes
+}
+
+atf_test_case alldashinput
+alldashinput_body() {
+ atf_check -o file:outputs/pat/all_bytes ../cmd/pat - <outputs/pat/all_bytes
+}
+
+atf_test_case devnull
+devnull_body() {
+ atf_check ../cmd/pat /dev/null
+ atf_check ../cmd/pat </dev/null
+ atf_check ../cmd/pat - </dev/null
+}
+
+atf_test_case noperm cleanup
+noperm_body() {
+ touch inputs/chmod_000 || atf_fail "touching chmod_000"
+ chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000"
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:pat: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/pat inputs/chmod_000
+}
+noperm_cleanup() {
+ chmod 0600 inputs/chmod_000 || atf_fail "chmod 0600 chmod_000"
+ rm inputs/chmod_000 || atf_fail "rm chmod_000"
+}
+
+atf_test_case devfull
+devfull_body() {
+ atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat outputs/pat/all_bytes >/dev/full'
+ atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat <outputs/pat/all_bytes >/dev/full'
+ atf_check -s exit:1 -e 'inline:pat: Error writing: No space left on device\n' sh -c '../cmd/pat - <outputs/pat/all_bytes >/dev/full'
+}
+
+atf_test_case readslash
+readslash_body() {
+ [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD allows to read directories"
+
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:pat: Error reading ‘/’: Is a directory\n' ../cmd/pat /
+}
+
+atf_test_case enoent
+enoent_body() {
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:pat: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/pat /var/empty/e/no/ent
+}
+
+atf_test_case doubledash
+doubledash_body() {
+ atf_check -o file:outputs/pat/all_bytes -- ../cmd/pat -- outputs/pat/all_bytes
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:pat: Error opening ‘---’: No such file or directory\n' -o empty -- ../cmd/pat --- outputs/pat/all_bytes
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+ atf_add_test_case allfile
+ atf_add_test_case allinput
+ atf_add_test_case alldashinput
+ atf_add_test_case devnull
+ atf_add_test_case noperm
+ atf_add_test_case devfull
+ atf_add_test_case readslash
+ atf_add_test_case enoent
+ atf_add_test_case doubledash
+}
diff --git a/test-cmd/shellcheck b/test-cmd/shellcheck
@@ -0,0 +1,14 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+set -ex
+
+SHELLCHECK="${SHELLCHECK:-shellcheck}"
+
+${SHELLCHECK} "$0"
+
+cd "$(dirname "$0")"
+
+# shellcheck disable=SC2046
+${SHELLCHECK} --shell=sh -x $(grep -v -- '--' ./Kyuafile | grep atf_test_program | sed -E 's;^atf_test_program\{name="([^"]*)".*;./\1;')
diff --git a/test-cmd/sizeof b/test-cmd/sizeof
@@ -0,0 +1,26 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case empty
+empty_body() {
+ atf_check -o not-empty ../cmd/sizeof
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for printf()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_skip "FreeBSD ignoring write errors for printf()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD ignoring write errors for printf()"
+
+ atf_check -s exit:1 sh -c '../cmd/sizeof >/dev/full'
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case empty
+ atf_add_test_case devfull
+}
diff --git a/test-cmd/sname b/test-cmd/sname
@@ -0,0 +1,33 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case generic
+generic_body() {
+ # sed is because of FreeBSD
+ atf_check -o "inline:machine $(uname -m)
+nodename $(uname -n)
+release $(uname -r)
+sysname $(uname -s)
+version $(uname -v | sed 's; *$;;')
+" \
+ ../cmd/sname
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for puts()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for puts()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for puts()"
+
+ atf_check -s exit:1 -e 'inline:sname: printf: No space left on device\n' sh -c '../cmd/sname >/dev/full'
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case generic
+ atf_add_test_case devfull
+}
diff --git a/test-cmd/xcd b/test-cmd/xcd
@@ -0,0 +1,61 @@
+#!/usr/bin/env atf-sh
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+atf_test_case openfile
+openfile_body() {
+ atf_check -o file:outputs/xcd/all_bytes ../cmd/xcd inputs/all_bytes
+}
+
+atf_test_case stdinput
+stdinput_body() {
+ atf_check -o file:outputs/xcd/all_bytes ../cmd/xcd <inputs/all_bytes
+ atf_check -o file:outputs/xcd/all_bytes ../cmd/xcd - <inputs/all_bytes
+}
+
+atf_test_case nullfile
+nullfile_body() {
+ atf_check -o file:outputs/xcd/null ../cmd/xcd /dev/null
+}
+
+atf_test_case nullinput
+nullinput_body() {
+ atf_check -o file:outputs/xcd/null ../cmd/xcd </dev/null
+}
+
+atf_test_case noperm cleanup
+noperm_body() {
+ touch inputs/chmod_000 || atf_fail "touching chmod_000"
+ chmod 0000 inputs/chmod_000 || atf_fail "chmod 0000 chmod_000"
+ # shellcheck disable=SC1112
+ atf_check -s exit:1 -e 'inline:\n[0mxcd: Error opening ‘inputs/chmod_000’: Permission denied\n' -o 'inline:[0m' ../cmd/xcd inputs/chmod_000
+}
+noperm_cleanup() {
+ chmod 0600 inputs/chmod_000 || atf_fail "chmod 0600 chmod_000"
+ rm inputs/chmod_000 || atf_fail "rm chmod_000"
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for printf()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for printf()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for printf()"
+
+ # shellcheck disable=SC1112
+ atf_check -s 'exit:1' -e 'inline:\n[0mxcd: Write error: No space left on device\n\n[0mxcd: Error closing ‘inputs/all_bytes’: No space left on device\n' sh -c '../cmd/xcd inputs/all_bytes >/dev/full'
+ atf_check -s 'exit:1' -e 'inline:\n[0mxcd: Write error: No space left on device\n' sh -c '../cmd/xcd <inputs/all_bytes >/dev/full'
+ atf_check -s 'exit:1' -e 'inline:\n[0mxcd: Write error: No space left on device\n' sh -c '../cmd/xcd - <inputs/all_bytes >/dev/full'
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case openfile
+ atf_add_test_case stdinput
+ atf_add_test_case nullfile
+ atf_add_test_case nullinput
+ atf_add_test_case noperm
+ atf_add_test_case devfull
+}
diff --git a/test_functions.sh b/test_functions.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+has_glibc() {
+ test -f /usr/include/features.h && grep -q '#define\W__GLIBC__' /usr/include/features.h
+}
+
+has_musl() {
+ command -v ldd && ldd 2>&1 | grep -q musl
+}