logo

utils-std

commit: 9b77db4471c3d5661d1c26028a512d2f0a8b325e
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sun, 24 Sep 2023 15:42:51 +0200

Extract from utils

Diffstat:

A.builds/freebsd.yml24++++++++++++++++++++++++
A.builds/netbsd.yml24++++++++++++++++++++++++
A.clang-format23+++++++++++++++++++++++
A.gitignore14++++++++++++++
A.reuse/dep56++++++
AKyuafile7+++++++
ALICENSES/GFDL-1.1-only.txt119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSES/MPL-2.0.txt373+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile52++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md24++++++++++++++++++++++++
Acmd/base64.c233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/basename.139+++++++++++++++++++++++++++++++++++++++
Acmd/basename.c64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/cat.134++++++++++++++++++++++++++++++++++
Acmd/cat.c120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/chroot.152++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/chroot.c62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/date.155+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/date.c107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/dirname.130++++++++++++++++++++++++++++++
Acmd/dirname.c28++++++++++++++++++++++++++++
Acmd/echo.144++++++++++++++++++++++++++++++++++++++++++++
Acmd/echo.c77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/env.171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/env.c132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/false.118++++++++++++++++++
Acmd/false.c8++++++++
Acmd/id.c420+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/link.128++++++++++++++++++++++++++++
Acmd/link.c25+++++++++++++++++++++++++
Acmd/nproc.c51+++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/pwd.c36++++++++++++++++++++++++++++++++++++
Acmd/seq.c120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/sleep.130++++++++++++++++++++++++++++++
Acmd/sleep.c129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/strings.158++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/strings.c212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/sync.122++++++++++++++++++++++
Acmd/sync.c14++++++++++++++
Acmd/tee.c105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/time.148++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/time.c112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/touch.185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/touch.c138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/true.118++++++++++++++++++
Acmd/true.c8++++++++
Acmd/tty.c38++++++++++++++++++++++++++++++++++++++
Acmd/unlink.125+++++++++++++++++++++++++
Acmd/unlink.c22++++++++++++++++++++++
Aconfigure242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acoreutils.txt106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/iso_parse.c80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/iso_parse.h6++++++
Alsb_commands.txt141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/Kyuafile27+++++++++++++++++++++++++++
Atest-cmd/base6480+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/basename80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/cat78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/date93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/dirname38++++++++++++++++++++++++++++++++++++++
Atest-cmd/echo40++++++++++++++++++++++++++++++++++++++++
Atest-cmd/env73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/false25+++++++++++++++++++++++++
Atest-cmd/foo0
Atest-cmd/id164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/inputs/all_bytes0
Atest-cmd/inputs/strings/libc_start_main0
Atest-cmd/inputs/strings/true0
Atest-cmd/link35+++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/base64/all_bytes5+++++
Atest-cmd/outputs/strings/all_bytes1+
Atest-cmd/outputs/strings/all_bytes_td1+
Atest-cmd/outputs/strings/all_bytes_to1+
Atest-cmd/outputs/strings/all_bytes_tx1+
Atest-cmd/outputs/strings/true91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_865+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_8_td65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_8_to65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_8_tx65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_td91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_to91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/strings/true_tx91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/outputs/tee/hello_all_bytes0
Atest-cmd/pwd49+++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/seq75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/sleep.t30++++++++++++++++++++++++++++++
Atest-cmd/stat_atime8++++++++
Atest-cmd/stat_mtime8++++++++
Atest-cmd/strings123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/tee92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/time.t31+++++++++++++++++++++++++++++++
Atest-cmd/touch205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/true25+++++++++++++++++++++++++
Atest-cmd/tty43+++++++++++++++++++++++++++++++++++++++++++
Atest-cmd/unlink24++++++++++++++++++++++++
Atest_functions.sh12++++++++++++
96 files changed, 6250 insertions(+), 0 deletions(-)

diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + +image: freebsd/latest +packages: + - pkgconf + - kyua + - gcc + - devel/cram +sources: + - https://hacktivis.me/git/utils-std.git +tasks: + - clang: | + cd utils-std + CC=clang ./configure + make clean test + - gcc: | + cd utils-std + CC=gcc ./configure + make clean test +triggers: + - action: email + condition: failure + to: contact+builds.sr.ht@hacktivis.me diff --git a/.builds/netbsd.yml b/.builds/netbsd.yml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + +image: netbsd/latest +packages: + - pkg-config + - kyua + - clang +# FIXME: cram/prysk on NetBSD, where? +sources: + - https://hacktivis.me/git/utils-std.git +tasks: + - clang: | + cd utils-std + CC=clang CRAM=true ./configure + DESTDIR=/tmp/clang-destdir make clean test + - gcc: | + cd utils-std + CC=gcc CRAM=true ./configure + DESTDIR=/tmp/gcc-destdir make clean test +triggers: + - action: email + condition: failure + to: contact+builds.sr.ht@hacktivis.me diff --git a/.clang-format b/.clang-format @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: true +AlignOperands: true +AlignTrailingComments: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AlwaysBreakAfterReturnType: AllDefinitions +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Allman +SpaceBeforeParens: Never +IncludeBlocks: Regroup +ReflowComments: false +SortIncludes: true +UseTab: ForIndentation +IndentWidth: 2 +TabWidth: 2 +ColumnLimit: 100 + +NamespaceIndentation: All 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-std") + +include("test-cmd/Kyuafile") diff --git a/LICENSES/GFDL-1.1-only.txt b/LICENSES/GFDL-1.1-only.txt @@ -0,0 +1,119 @@ +GNU Free Documentation License +Version 1.1, March 2000 + +Copyright (C) 2000 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other written document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. + +This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. + +We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". + +A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (For example, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. + +The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. + +A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, whose contents can be viewed and edited directly and straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup has been designed to thwart or discourage subsequent modification by readers is not Transparent. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML designed for human modification. Opaque formats include PostScript, PDF, proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML produced by some word processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and you may publicly display copies. + +3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. + +If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a publicly-accessible computer-network location containing a complete Transparent copy of the Document, free of added material, which the general network-using public has access to download anonymously at no charge using public-standard network protocols. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. + +It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: + + A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. + B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has less than five). + C. State on the Title page the name of the publisher of the Modified Version, as the publisher. + D. Preserve all the copyright notices of the Document. + E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. + F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. + G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. + H. Include an unaltered copy of this License. + I. Preserve the section entitled "History", and its title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. + J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. + K. In any section entitled "Acknowledgements" or "Dedications", preserve the section's title, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. + L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. + M. Delete any section entitled "Endorsements". Such a section may not be included in the Modified Version. + N. Do not retitle any existing section as "Endorsements" or to conflict in title with any Invariant Section. + +If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. + +You may add a section entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. + +You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice. + +The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections entitled "History" in the various original documents, forming one section entitled "History"; likewise combine any sections entitled "Acknowledgements", and any sections entitled "Dedications". You must delete all sections entitled "Endorsements." + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, does not as a whole count as a Modified Version of the Document, provided no compilation copyright is claimed for the compilation. Such a compilation is called an "aggregate", and this License does not apply to the other self-contained works thus compiled with the Document, on account of their being thus compiled, if they are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one quarter of the entire aggregate, the Document's Cover Texts may be placed on covers that surround only the Document within the aggregate. Otherwise they must appear on covers around the whole aggregate. + +8. TRANSLATION + +Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License provided that you also include the original English version of this License. In case of a disagreement between the translation and the original English version of this License, the original English version will prevail. + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the license is included in the section entitled "GNU Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" instead of saying which ones are invariant. If you have no Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. 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,52 @@ +# 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) + MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 $(CRAM) test-cmd/*.t + +.PHONY: lint +lint: + $(SHELLCHECK) ./configure ./test_functions.sh + ${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: all + mkdir -p ${DESTDIR}${BINDIR}/ + cp -p ${EXE} ${DESTDIR}${BINDIR}/ + 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/date: cmd/date.c lib/iso_parse.c Makefile + rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} + $(CC) -std=c99 $(CFLAGS) -o $@ cmd/date.c lib/iso_parse.c $(LDFLAGS) + +cmd/touch: cmd/touch.c lib/iso_parse.c Makefile + rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} + $(CC) -std=c99 $(CFLAGS) -o $@ cmd/touch.c lib/iso_parse.c $(LDFLAGS) diff --git a/README.md b/README.md @@ -0,0 +1,24 @@ +<!-- +SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +SPDX-License-Identifier: MPL-2.0 +--> + +Collection of Unix tools, comparable to coreutils +Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +SPDX-License-Identifier: MPL-2.0 + +Tested on Linux(musl), FreeBSD, NetBSD, OpenBSD: +<https://builds.sr.ht/~lanodan/utils> + +# Dependencies +- POSIX System +- (optional, test) ATF: <https://github.com/jmmv/atf> +- (optional, test) Kyua: <https://github.com/jmmv/kyua> +- (optional, test) bwrap: <https://github.com/containers/bubblewrap/> For safely overlaying false files on the root filesystem +- (optional, test) [cram](https://bitheap.org/cram) or [prysk](https://www.prysk.net/) +- (optional, lint) mandoc: <https://mdocml.bsd.lv/> For linting the manual pages +- (optional, lint) shellcheck: <https://www.shellcheck.net/> For linting `./configure` and shell scripts + +## Packaging +- The `./configure` script isn't auto*hell based, you can pass it arguments via key-value arguments or environment variables +- You can define the `NO_BWRAP` environment variable to skip the bwrap-based tests which can cause issues in some environments like Gentoo `sandbox` diff --git a/cmd/base64.c b/cmd/base64.c @@ -0,0 +1,233 @@ +// 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 <assert.h> /* assert */ +#include <errno.h> /* errno */ +#include <fcntl.h> /* open(), O_RDONLY */ +#include <stdint.h> /* uint8_t */ +#include <stdio.h> /* fprintf(), BUFSIZ */ +#include <string.h> /* strerror(), strncmp() */ +#include <unistd.h> /* read(), write(), close(), getopt(), opt* */ + +// 64(26+26+10+2) +static char b64_encmap[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static size_t c_out = 0; +// TODO: -w option ; 76 is lowest of all base64 related RFCs +static size_t wrap_nl = 76; + +static inline uint8_t +b64_get(uint8_t pos) +{ + assert(pos <= 64); + return b64_encmap[pos]; +} + +static void +b64encode(uint8_t out[4], uint8_t in[3]) +{ + out[0] = b64_get(in[0] >> 2); + out[1] = b64_get((uint8_t)(((in[0] & 0x03) << 4) | (in[1] >> 4))); + out[2] = b64_get((uint8_t)(((in[1] & 0x0f) << 2) | (in[2] >> 6))); + out[3] = b64_get(in[2] & 0x3f); +} + +static int +encode(int fd, const char *fdname) +{ + ssize_t c = 0; + char ibuf[3]; + + while((c = read(fd, ibuf, sizeof(ibuf))) > 0) + { + uint8_t obuf[4] = "----"; + ssize_t pad = 0; + assert(pad >= 0); + + for(; c < 3; pad++, c++) + { + ibuf[c] = 0; + } + assert(c == 3); + + b64encode(obuf, (uint8_t *)ibuf); + + for(; pad > 0; pad--) + { + obuf[4 - pad] = '='; + } + + if(write(1, (char *)obuf, 4) != 4) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } + c_out += 4; + + if(wrap_nl != 0 && c_out >= wrap_nl) + { + c_out = 0; + if(write(1, "\n", 1) != 1) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } + } + } + + if(c < 0) + { + fprintf(stderr, "base64: Error reading ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} + +#if 0 +static void +b64decode(uint8_t out[3], uint8_t in[4]) +{ + if(in[0] == '=') return; + + strchr(b64_encmap, ); + out[0] = (uint8_t)(b << 2); +} +#endif + +static int +decode(int fd, const char *fdname) +{ +#if 0 + ssize_t c = 0; + char ibuf[BUFSIZ]; + + while((c = read(fd, ibuf, sizeof(ibuf))) > 0) + { + int state = 0; + uint8_t out[3] = 0; + ssize_t i = 0; + + for(size_t ic = 0;ic < c; ic++) { + char *b = ibuf[ic]; + + if (isspace(b)) continue; + + if (c == '=') break; + + const char *pos = strchr(b64_encmap, b); + if(pos == NULL) return EFTYPE; + + uint8_t b8 = (uint8_t)(pos - b64_encmap); + + switch(state) { + case 0: + out[0] = (uint8_t)(b << 2); + break; + case 1: + out[0] |= b >> 4; + out[1] = (uint8_t)((b & 0xf) << 4); + break; + case 2: + out[1] |= b >> 2; + out[2] = (uint8_t)((b & 0x3) << 6); + case 3: + out[2] |= b; + break; + default: + abort(); + } + state = (state + 1) & 3; + } + + if(write(1, out, 4) != 4) + { + fprintf(stderr, "base64: Error writing: %s\n", strerror(errno)); + return 1; + } + out = {0, 0, 0}; + } + + return 1; +#endif +} + +int +main(int argc, char *argv[]) +{ + int (*process)(int, const char *) = &encode; + + int c = 0, ret = 0; + + while((c = getopt(argc, argv, ":d")) != -1) + { + switch(c) + { + case 'd': + process = &decode; + break; + case ':': + fprintf(stderr, "base64: Error: Missing operand for option: ‘-%c’\n", optopt); + return 1; + case '?': + fprintf(stderr, "base64: Error: Unrecognised option: ‘-%c’\n", optopt); + return 1; + } + } + + argc -= optind; + argv += optind; + + if(argc <= 0) + { + ret = process(0, "<stdin>"); + goto end; + } + + for(int argi = 0; argi < argc; argi++) + { + if(strncmp(argv[argi], "-", 2) == 0) + { + if(process(0, "<stdin>") != 0) + { + ret = 1; + goto end; + } + } + else if(strncmp(argv[argi], "--", 3) == 0) + { + continue; + } + else + { + int fd = open(argv[argi], O_RDONLY); + if(fd < 0) + { + fprintf(stderr, "base64: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); + ret = 1; + goto end; + } + + if(process(fd, argv[argi]) != 0) + { + ret = 1; + goto end; + } + + if(close(fd) < 0) + { + fprintf(stderr, "base64: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); + ret = 1; + goto end; + } + } + } + +end: + if(c_out > 0) + { + printf("\n"); + } + return ret; +} diff --git a/cmd/basename.1 b/cmd/basename.1 @@ -0,0 +1,39 @@ +.\" 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 BASENAME 1 +.Os +.Sh NAME +.Nm basename +.Nd return last path compoment of a pathname +.Sh SYNOPSIS +.Nm +.Op Ar string +.Op Ar suffix +.Sh DESCRIPTION +When +.Ar string +isn't given +.Dq \&. +is returned. +Otherwise it gives the last path compoment of +.Ar string +with removing the last part matching +.Ar suffix +when applicable. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr basename 3 +.Sh STANDARDS +.Nm +is close but not compliant with the +.St -p1003.1-2008 +specification as it uses +.Xr basename 3 +directly instead of extracting the non-directory portion of +.Ar string +with it's own algorithm. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/basename.c b/cmd/basename.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 + +#include <assert.h> // assert() +#include <libgen.h> // basename() +#include <stdio.h> // fprintf(), puts(), perror() +#include <string.h> // strlen(), strncmp() + +static char * +suffix_basename(char *name, char *suffix) +{ + assert(name); + assert(suffix); + char *string = basename(name); + + size_t suflen = suffix ? strlen(suffix) : 0; + size_t len = strlen(string); + + if(suflen < len && strcmp(&string[len - suflen], suffix) == 0) + { + string[len - suflen] = '\0'; + } + + return string; +} + +int +main(int argc, char *argv[]) +{ + int ret = 0; + + if((argc > 1) && (strncmp(argv[1], "--", 3) == 0)) + { + argv++; + argc--; + } + + switch(argc) + { + case 1: + ret = printf(".\n"); + break; + case 2: + ret = puts(basename(argv[1])); + break; + case 3: + ret = puts(suffix_basename(argv[1], argv[2])); + break; + default: + fprintf(stderr, "usage: basename string [suffix]\n"); + return 1; + } + + if(ret < 0) + { + perror("basename: puts"); + return 1; + } + else + { + return 0; + } +} diff --git a/cmd/cat.1 b/cmd/cat.1 @@ -0,0 +1,34 @@ +.\" Collection of Unix tools, comparable to coreutils +.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd 2022-02-13 +.Dt CAT 1 +.Os +.Sh NAME +.Nm cat +.Nd concatenate files +.Sh SYNOPSIS +.Nm +.Op Fl u +.Op Ar files ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes it on the standard output. +If no +.Ar file +is given, +.Nm +reads from the standard input. +.Sh OPTIONS +.Fl u is ignored, present only for POSIX compatibility. +.Sh EXIT STATUS +.Ex -std +.Sh STANDARDS +.Nm +is mostly compliant with the +.St -p1003.1-2008 +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/cat.c b/cmd/cat.c @@ -0,0 +1,120 @@ +// 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 +#define _GNU_SOURCE /* splice(2) */ +#include <errno.h> /* errno */ +#include <fcntl.h> /* open(), O_RDONLY, splice(2) */ +#include <stdint.h> /* SIZE_MAX */ +#include <stdio.h> /* fprintf(), BUFSIZ */ +#include <string.h> /* strerror(), strncmp() */ +#include <unistd.h> /* read(), write(), close() */ + +int +concat(int fd, const char *fdname) +{ + ssize_t c; + char buf[BUFSIZ]; + + while((c = read(fd, buf, sizeof(buf))) > 0) + { + if(write(1, buf, (size_t)c) < 0) + { + fprintf(stderr, "cat: Error writing: %s\n", strerror(errno)); + return 1; + } + } + + if(c < 0) + { + fprintf(stderr, "cat: Error reading ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} + +#ifdef __linux__ +int +fd_copy(int fd, const char *fdname) +{ + ssize_t c = 0; + +again: + while((c = splice( + fd, NULL, 1, NULL, SIZE_MAX, SPLICE_F_MOVE | SPLICE_F_NONBLOCK | SPLICE_F_MORE)) > 0) + ; + + if(c < 0) + { + if(errno == EINVAL) + { + return concat(fd, fdname); + } + if(errno == EAGAIN) + { + goto again; + } + fprintf(stderr, "cat: Error copying ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} +#else // __linux__ +#define fd_copy concat +#endif // __linux__ + +int +main(int argc, char *argv[]) +{ + if(argc <= 1) + { + return fd_copy(0, "<stdin>"); + } + + // For POSIX compatibility + if(strncmp(argv[1], "-u", 3) == 0) + { + argc--; + argv++; + } + + for(int argi = 1; argi < argc; argi++) + { + if(strncmp(argv[argi], "-", 2) == 0) + { + if(fd_copy(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, "cat: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); + return 1; + } + + if(fd_copy(fd, argv[argi]) != 0) + { + return 1; + } + + if(close(fd) < 0) + { + fprintf(stderr, "cat: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); + return 1; + } + } + } + + return 0; +} diff --git a/cmd/chroot.1 b/cmd/chroot.1 @@ -0,0 +1,52 @@ +.\" 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-08-04 +.Dt CHROOT 1 +.Os +.Sh NAME +.Nm chroot +.Nd run in another root directory +.Sh SYNOPSIS +.Nm +.Ar newroot +.Op Ar command Op Ar args ... +.Sh DESCRIPTION +.Nm +runs +.Ar command +in the root directory at +.Ar newroot . +If no +.Ar command +was given, +.Nm +instead runs +.Ql $SHELL -i +with +.Ev SHELL +itself defaulting to +.Pa /bin/sh . +.Sh EXIT STATUS +If +.Ar command +is invoked, the exit status of +.Nm +shall be the exit status of +.Ar command ; +Otherwise, the +.Nm +utility shall exit with one of the following values: +.Bl -tag -width Ds +.It 125 +An error occured in +.Nm +.It 126 +.Ar command +was found but couldn't be invoked. +.It 127 +.Ar command +wasn't found. +.El +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/chroot.c b/cmd/chroot.c @@ -0,0 +1,62 @@ +// 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 +#define _DEFAULT_SOURCE // chroot +#include <assert.h> // assert +#include <errno.h> // errno +#include <stdbool.h> // false +#include <stdio.h> // fprintf, perror +#include <stdlib.h> // getenv +#include <unistd.h> // chroot, execl, execv + +int +main(int argc, char *argv[]) +{ + if(argc < 2) + { + fprintf(stderr, "chroot: Needs arguments\n"); + fprintf(stderr, "Usage: chroot <newroot> [command [args ...]]\n"); + return 125; + } + + if(chroot(argv[1]) < 0) + { + perror("chroot"); + return 125; + } + + if(chdir("/") < 0) + { + perror("chdir"); + return 125; + } + + int ret = 0; + errno = 0; + if(argc == 2) + { + char *shell = getenv("SHELL"); + if(shell == NULL) shell = "/bin/sh"; + + /* flawfinder: ignore. No restrictions on commands is intended */ + ret = execlp(shell, shell, "-i", NULL); + } + else + { + argv += 2; + /* flawfinder: ignore. No restrictions on commands is intended */ + ret = execvp(argv[0], argv); + } + + if(ret != 0) + { + perror("chroot: exec"); + if(errno == ENOENT) return 127; + + return 126; + } + + assert(false); +} diff --git a/cmd/date.1 b/cmd/date.1 @@ -0,0 +1,55 @@ +.\" Collection of Unix tools, comparable to coreutils +.\" Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd 2023-06-03 +.Dt DATE 1 +.Os +.Sh NAME +.Nm date +.Nd display date and time +.Sh SYNOPSIS +.Nm +.Op Fl u +.Op Fl d Ar datetime +.Op Cm + Ns Ar format +.Sh DESCRIPTION +When +.Nm +is invoked without arguments it displays the current datetime +Otherwise, depending on the options specified, will print the datetime in a user-defined way. +.Bl -tag -width Ds +.It Fl d Ar datetime +.Ar datetime +can contain the Unix timestamp (number of seconds before and after 1970-01-01T00:00:00Z) prefixed by an @ (at) symbol, +or a date-time formatted as +.Ql YYYY-MM-DDThh:mm:SS[frac][Z] , +see +.Xr touch 1 +for more details on the format. +.It Fl u +Use UTC (coordinated universal time) instead of the local time. +.El +.Pp +The plus operand +.Pq Sq + +specifies in which format the datetime should be displayed, the format string is specified in the +.Xr strftime 3 +manual page. +.Sh ENVIRONMENT +Look at the manual page of +.Xr strftime 3 +for the environment variables, typical ones are +.Ev TZ , +.Ev LC_TIME and +.Ev LC_ALL but that depends on your system. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr strftime 3 +.Sh STANDARDS +.Nm +is mostly compliant with the +.St -p1003.1-2008 +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/date.c b/cmd/date.c @@ -0,0 +1,107 @@ +// 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 "../lib/iso_parse.h" /* iso_parse */ + +#include <errno.h> /* errno */ +#include <locale.h> /* setlocale() */ +#include <stdio.h> /* BUFSIZ, perror(), puts() */ +#include <stdlib.h> /* exit(), strtol() */ +#include <time.h> /* time, localtime, tm, strftime */ +#include <unistd.h> /* getopt(), optarg, optind */ + +void +usage() +{ + fprintf(stderr, "date [-uR][-d datetime] [+format]\n"); +} + +int +main(int argc, char *argv[]) +{ + char outstr[BUFSIZ]; + struct tm *tm; + time_t now; + char *format = "%c"; + int uflag = 0; + int c; + + //setlocale(LC_ALL, ""); + + now = time(NULL); + if(now == (time_t)-1) + { + perror("date: time"); + exit(EXIT_FAILURE); + } + + while((c = getopt(argc, argv, ":d:uR")) != -1) + { + switch(c) + { + case 'd': /* Custom datetime */ + now = iso_parse(optarg).tv_sec; + break; + case 'u': /* UTC timezone */ + uflag++; + break; + case 'R': /* Email (RFC 5322) format */ + format = "%a, %d %b %Y %H:%M:%S %z"; + break; + case ':': + fprintf(stderr, "date: Error: Missing operand for option: '-%c'\n", optopt); + usage(); + return 1; + case '?': + fprintf(stderr, "date: Error: Unrecognised option: '-%c'\n", optopt); + usage(); + return 1; + } + } + + if(uflag) + { + tm = gmtime(&now); + + if(tm == NULL) + { + perror("date: gmtime"); + exit(EXIT_FAILURE); + } + } + else + { + tm = localtime(&now); + + if(tm == NULL) + { + perror("date: localtime"); + exit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + + (void)argc; + + if(*argv && **argv == '+') format = *argv + 1; + + errno = 0; + if(strftime(outstr, sizeof(outstr), format, tm) == 0 && errno != 0) + { + perror("date: strftime"); + exit(EXIT_FAILURE); + } + + if(puts(outstr) < 0) + { + perror("date: puts"); + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/cmd/dirname.1 b/cmd/dirname.1 @@ -0,0 +1,30 @@ +.\" 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 DIRNAME 1 +.Os +.Sh NAME +.Nm dirname +.Nd return the parent directory of a file pathname +.Sh SYNOPSIS +.Nm +.Ar string +.Sh DESCRIPTION +Returns the parent directory of +.Ar string . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr direname 3 +.Sh STANDARDS +.Nm +is close but not compliant with the +.St -p1003.1-2008 +specification as it uses +.Xr dirname 3 +directly instead of extracting the non-directory portion of +.Ar string +with it's own algorithm. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/dirname.c b/cmd/dirname.c @@ -0,0 +1,28 @@ +// 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 <libgen.h> // dirname() +#include <stdio.h> // puts() +#include <string.h> // strcmp() + +int +main(int argc, char *argv[]) +{ + if(argc != 2) + { + if((argc == 3) && (strcmp(argv[1], "--") == 0)) + { + argv++; + argc--; + } + else + { + fputs("usage: dirname string\n", stderr); + return 1; + } + } + + puts(dirname(argv[1])); + return 0; +} diff --git a/cmd/echo.1 b/cmd/echo.1 @@ -0,0 +1,44 @@ +.\" 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-05-31 +.Dt ECHO 1 +.Os +.Sh NAME +.Nm echo +.Nd write arguments to standard output +.Sh SYNOPSIS +.Nm +.Op Ar string ... +.Sh DESCRIPTION +.Nm +buffers all of it's arguments and writes them all at once to standard output, followed by a newline. +If there is no arguments, only the newline is written. +.Pp +The +.Fl - +operand, which generally denotes an end to option processing, is treated as part of +.Ar string . +.Pp +.Nm +supports the following options: +.Bl -tag -width Ds +.It Fl n +Do not print the trailing newline character. +.El +.Pp +Should also be noted that this version of +.Nm +isn't XSI-compliant as +.Fl n +is parsed as an option and backslash operators aren't supported. +See +.Xr printf 1 +for such an utility. +.Sh EXIT STATUS +.Ex -std +.Sh STANDARDS +Not XSI-compliant but should be compliant to +.St -p1003.1-2008 +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/echo.c b/cmd/echo.c @@ -0,0 +1,77 @@ +// 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 <stdbool.h> /* bool */ +#include <stdio.h> /* perror */ +#include <stdlib.h> /* malloc */ +#include <string.h> /* strlen */ +#include <unistd.h> /* write */ + +int +main(int argc, char *argv[]) +{ + size_t arg_len = 0; + char *buffer, *buffer_p; + int err = 0; + bool opt_n = false; + + if((argc >= 2) && (strncmp(argv[1], "-n", 3) == 0)) + { + opt_n = true; + argc--; + argv++; + } + + for(int i = 1; i < argc; i++) + { + arg_len += strlen(argv[i]) + 1; // str + space|newline + } + + if(arg_len == 0) + { + if(opt_n) return 0; + + if(write(1, "\n", 1) < 1) + { + perror("echo: write(1, \"\n\", 1)"); + return 1; + } + + return 0; + } + + if(opt_n) arg_len--; // no newline + + buffer = malloc(arg_len); + if(buffer == NULL) + { + perror("echo: malloc(arg_len)"); + return 1; + } + + buffer_p = buffer; + for(int i = 1; i < argc; i++) + { + size_t len = strlen(argv[i]); + memcpy(buffer_p, argv[i], len); + buffer_p += len; + + if(i < argc - 1) + { + *buffer_p++ = ' '; + } + } + if(!opt_n) *buffer_p++ = '\n'; + + if(write(1, buffer, arg_len) < (ssize_t)arg_len) + { + perror("echo: write(1, buffer, arg_len)"); + err++; + } + + free(buffer); + + return err; +} diff --git a/cmd/env.1 b/cmd/env.1 @@ -0,0 +1,71 @@ +.\" 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 env +.Nd control and print environment +.Sh SYNOPSIS +.Nm +.Op Fl i +.Op Fl u Ar name +.Op Fl -unset= Ns Ar name +.Op Ar name Ns = Ns Ar value +.Op Ar command Op Ar argument ... +.Sh DESCRIPTION +.Nm +with no arguments prints the environment, or if specified runs +.Ar command . +.Pp +The environment can be modified via the following options: +.Bl -tag -width Ds +.It Fl i +Ignore the existing environment. +.It Fl u Ar name | Fl -unset= Ns Ar name +Removes the variable named +.Ar name +from the new environment. +.It Ar name Ns = Ns Ar value +Adds the variable named +.Ar name +with the value +.Ar value +into the new environment, it cannot itself contain the +.Qq = +character. +.El +.Sh EXIT STATUS +If +.Ar command +is invoked, the exit status of +.Nm +shall be the exit status of +.Ar command ; +Otherwise, the env utility shall exit with one of the following values: +.Bl -tag -width Ds +.It 0 +.Nm +completed successfully. +.It 1 +An error occured in +.Nm +.It 126 +.Ar command +was found but couldn't be invoked. +.It 127 +.Ar command +wasn't found. +.El +.Sh STANDARDS +.Nm +is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The +.Fl u +flag is an extension known to be present in FreeBSD, NetBSD, GNU coreutils, BusyBox, ... +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/env.c b/cmd/env.c @@ -0,0 +1,132 @@ +// 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 <assert.h> // assert +#include <errno.h> // errno +#include <stdbool.h> // bool, true, false +#include <stdio.h> // puts, fprintf +#include <stdlib.h> // putenv +#include <string.h> // strchr, strerror +#include <unistd.h> // getopt, opt* + +extern char **environ; +char *envclear[1]; + +int export() +{ + int i = 0; + + for(; environ[i] != NULL; i++) + { + if(puts(environ[i]) < 0) + { + perror("env: puts(environ[i])"); + return 1; + } + } + + return 0; +} + +void +usage() +{ + fprintf(stderr, "env [-i] [-u key | --unset=key] [key=value ...] [command [args]]\n"); +} + +int +main(int argc, char *argv[]) +{ + int c; + bool flag_i = false; + char *val; + + /* flawfinder: ignore. Old implementations of getopt should fix themselves */ + while((c = getopt(argc, argv, ":iu:-:")) != -1) + { + switch(c) + { + case 'i': + flag_i = true; + break; + case 'u': + unsetenv(optarg); + break; + case '-': + val = strchr(optarg, '='); + if(val == NULL) + { + fprintf(stderr, "env: Error: Missing = in long option\n"); + return 1; + } + + *val = 0; + val++; + + if(strcmp(optarg, "unset") != 0) + { + fprintf(stderr, "env: Error: Unknown long option --%s\n", optarg); + return 1; + } + unsetenv(val); + + break; + case ':': + fprintf(stderr, "env: Error: Missing operand for option: '-%c'\n", optopt); + usage(); + return 1; + case '?': + fprintf(stderr, "env: Error: Unrecognised option: '-%c'\n", optopt); + usage(); + return 1; + default: + assert(false); + } + } + + argc -= optind; + argv += optind; + + if(flag_i) + { + environ = envclear; + envclear[0] = NULL; + } + + for(; argv[0]; argv++, argc--) + { + char *sep = strchr(argv[0], '='); + if(sep == NULL) + { + break; + } + + *sep = 0; + sep++; + + if(setenv(argv[0], sep, 1)) + { + fprintf(stderr, "env: setenv(%s, %s, 1): %s\n", argv[0], sep, strerror(errno)); + return 1; + } + } + + if(argc < 1) + { + return export(); + } + + assert(argv[0]); + errno = 0; + /* flawfinder: ignore. No restrictions on commands is intended */ + if(execvp(argv[0], argv) < 0) + { + fprintf(stderr, "env: execvp(\"%s\", ...): %s\n", argv[0], strerror(errno)); + + return (errno == ENOENT) ? 127 : 126; + } + + assert(false); +} diff --git a/cmd/false.1 b/cmd/false.1 @@ -0,0 +1,18 @@ +.\" 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 FALSE 1 +.Os +.Sh NAME +.Nm false +.Nd do nothing, unsuccessfully +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +will return with exit status code one. +.Sh EXIT STATUS +One. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/false.c b/cmd/false.c @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +int +main(void) +{ + return 1; +} diff --git a/cmd/id.c b/cmd/id.c @@ -0,0 +1,420 @@ +// 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 _DEFAULT_SOURCE // getgrouplist (4.4BSD+) +#include <grp.h> // getgrgid, getgroups, getgrouplist(glibc) +#include <pwd.h> // getpwuid +#include <stdbool.h> // bool +#include <stdio.h> // printf, perror +#include <stdlib.h> // malloc, free +#include <sys/types.h> // uid_t +#include <unistd.h> // getuid, getgid, getopt, opt*, getgrouplist(FreeBSD, NetBSD) + +bool name_flag = false; + +static int +simple_list_groups(struct passwd *pw, int ngroups, gid_t *groups) +{ + for(int i = 0; i < ngroups; i++) + { + if(name_flag) + { + struct group *lgr = getgrgid(groups[i]); + if(lgr == NULL) + { + return 1; + } + + int sep = ' '; + if(i == ngroups - 1) + { + sep = '\n'; + } + + int ret = printf("%s%c", lgr->gr_name, sep); + if(ret < 0) + { + return 1; + } + } + else + { + int sep = ' '; + if(i == ngroups - 1) + { + sep = '\n'; + } + + int ret = printf("%u%c", groups[i], sep); + if(ret < 0) + { + return 1; + } + } + } + + return 0; +} + +static int +list_groups(struct passwd *pw, int ngroups, gid_t *groups) +{ + printf(" groups="); + + for(int i = 0; i < ngroups; i++) + { + struct group *lgr = getgrgid(groups[i]); + + if(name_flag) + { + if(lgr == NULL) + { + return 1; + } + + int sep = ' '; + if(i == ngroups - 1) + { + sep = '\0'; + } + + int ret = printf("%s%c", lgr->gr_name, sep); + if(ret < 0) + { + return 1; + } + } + else + { + int ret = printf("%u", groups[i]); + if(ret < 0) + { + return 1; + } + + if(lgr != NULL) + { + int ret = printf("(%s)", lgr->gr_name); + if(ret < 0) + { + return 1; + } + } + + if(i != ngroups - 1) + { + printf(","); + } + } + } + + return 0; +} + +int +print_gid(char *field, struct group *gr, gid_t gid) +{ + if(gr && gr->gr_name) + { + if(name_flag) + { + return printf("%s=%s", field, gr->gr_name); + } + + return printf("%s=%u(%s)", field, gid, gr->gr_name); + } + else + { + if(name_flag) + { + return -1; + } + + return printf("%s=%u", field, gid); + } +} + +int +print_uid(char *field, struct passwd *pw, uid_t uid) +{ + if(pw && pw->pw_name) + { + if(name_flag) + { + return printf("%s=%s", field, pw->pw_name); + } + + return printf("%s=%u(%s)", field, uid, pw->pw_name); + } + else + { + if(name_flag) + { + return -1; + } + + return printf("%s=%u", field, uid); + } +} + +void +safe_getpwuid(uid_t uid, struct passwd *res) +{ + struct passwd *pw = getpwuid(uid); + + if(pw != NULL) + { + *res = *pw; + } +} + +enum id_modes +{ + ID_NORMAL, + ID_GROUPS, + ID_GID, + ID_UID, +}; + +void +usage() +{ + fprintf(stderr, "Usage: id [-Ggu] [-nr] [user]\n"); +} + +int +main(int argc, char *argv[]) +{ + int ret = 0, c = 0; + enum id_modes mode = ID_NORMAL; + bool real_flag = false; + + int ngroups = 0; + int ngroups_max = (int)sysconf(_SC_NGROUPS_MAX) + 1; + gid_t *groups = malloc(sizeof(gid_t) * ngroups_max); + if(groups == NULL) + { + perror("groups malloc"); + return 1; + } + + // geteuid, getuid, getegid, getgid shall always be successful + uid_t uid = getuid(); + uid_t euid = geteuid(); + gid_t gid = getgid(); + gid_t egid = getegid(); + + struct passwd pw = {.pw_uid = uid, .pw_gid = gid}; + struct passwd epw = {.pw_uid = euid, .pw_gid = egid}; + + /* flawfinder: ignore. Old implementations of getopt should fix themselves */ + while((c = getopt(argc, argv, ":Ggunr")) != EOF) + { + switch(c) + { + case 'G': + mode = ID_GROUPS; + break; + case 'u': + mode = ID_UID; + break; + case 'g': + mode = ID_GID; + break; + case 'n': + name_flag = true; + break; + case 'r': + real_flag = true; + break; + default: + usage(); + free(groups); + return 1; + } + } + + argc -= optind; + argv += optind; + + if(argc == 0) + { + safe_getpwuid(uid, &pw); + safe_getpwuid(euid, &epw); + + // Get groups from currently running process instead of configuration + ngroups = getgroups(ngroups_max, groups); + if(ngroups < 0) + { + perror("getgroups"); + goto failure; + } + } + else if(argc == 1) + { + struct passwd *pw_n = getpwnam(argv[0]); + + if(pw_n == NULL) + { + goto failure; + } + pw = *pw_n; + epw = *pw_n; + + uid = pw.pw_uid; + euid = epw.pw_uid; + gid = pw.pw_gid; + egid = epw.pw_gid; + + // Can only get groups from configuration + ngroups = getgrouplist(pw.pw_name, pw.pw_gid, groups, &ngroups_max); + if(ngroups < 0) + { + perror("getgrouplist"); + goto failure; + } + } + else + { + usage(); + } + + struct group *gr = getgrgid(gid); + struct group *egr = getgrgid(egid); + + if(mode == ID_GID) + { + if(!real_flag) + { + gid = egid; + } + + if(!name_flag) + { + ret = printf("%u\n", gid); + } + else + { + if(gr == NULL || gr->gr_name == NULL) + { + ret--; + fprintf(stderr, "id: cannot find name for group ID %u\n", gid); + printf("%u\n", gid); + } + else + { + ret = printf("%s\n", gr->gr_name); + } + } + + if(ret < 0) + { + goto failure; + } + + goto done; + } + + if(mode == ID_UID) + { + if(!real_flag) + { + uid = euid; + } + + if(!name_flag) + { + ret = printf("%u\n", uid); + } + else + { + if(pw.pw_name == NULL) + { + ret--; + fprintf(stderr, "id: cannot find name for user ID %u\n", uid); + printf("%u\n", uid); + } + else + { + ret = printf("%s\n", pw.pw_name); + } + } + + if(ret < 0) + { + goto failure; + } + + goto done; + } + + if(mode == ID_GROUPS) + { + if(real_flag) + { + ret = simple_list_groups(&pw, ngroups, groups); + } + else + { + ret = simple_list_groups(&epw, ngroups, groups); + } + + if(ret != 0) + { + goto failure; + } + + goto done; + } + + ret = print_uid("uid", &pw, uid); + if(ret < 0) + { + goto failure; + } + + if(euid != uid) + { + ret = print_uid(" euid", &epw, euid); + if(ret < 0) + { + goto failure; + } + } + + ret = print_gid(" gid", gr, gid); + if(ret < 0) + { + goto failure; + } + + if(egid != gid) + { + ret = print_gid(" egid", egr, egid); + + if(ret < 0) + { + goto failure; + } + } + + if(list_groups(&pw, ngroups, groups) != 0) + { + goto failure; + } + + ret = printf("\n"); + if(ret < 0) + { + goto failure; + } + +done: + free(groups); + return 0; + +failure: + free(groups); + return 1; +} diff --git a/cmd/link.1 b/cmd/link.1 @@ -0,0 +1,28 @@ +.\" 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-22 +.Dt LINK 1 +.Os +.Sh NAME +.Nm link +.Nd call +.Xr link 3 +function +.Sh SYNOPSIS +.Nm +.Ar reference +.Ar destination +.Sh DESCRIPTION +.Nm +will call +.Xr link 3 +with +.Ar reference +and +.Ar destination , +returning an error message in case of failure. +.Sh EXIT STATUS +.Ex -std +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/link.c b/cmd/link.c @@ -0,0 +1,25 @@ +// 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> /* perror, fprintf */ +#include <unistd.h> /* link */ + +int +main(int argc, char *argv[]) +{ + if(argc != 3) + { + fprintf(stderr, "usage: link <reference> <destination>\n"); + return 1; + } + + if(link(argv[1], argv[2]) != 0) + { + perror("link"); + return 1; + } + + return 0; +} diff --git a/cmd/nproc.c b/cmd/nproc.c @@ -0,0 +1,51 @@ +// 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 _DEFAULT_SOURCE // _SC_NPROCESSORS_CONF +#include <stdio.h> // printf +#include <unistd.h> // sysconf, getopt, opt* + +void +usage() +{ + fprintf(stderr, "Usage: nproc [-a]\n"); +} + +int +main(int argc, char *argv[]) +{ + // _SC_NPROCESSORS_CONF &_SC_NPROCESSORS_ONLN aren't in POSIX yet but have been accepted + // https://www.austingroupbugs.net/view.php?id=339 + int target = _SC_NPROCESSORS_ONLN; + + int c; + while((c = getopt(argc, argv, ":a")) != EOF) + { + switch(c) + { + case 'a': // all processors + target = _SC_NPROCESSORS_CONF; + break; + case ':': + fprintf(stderr, "nproc: Error: Missing operand for option: '-%c'\n", optopt); + usage(); + return 1; + case '?': + fprintf(stderr, "nproc: Error: Unrecognised option: '-%c'\n", optopt); + usage(); + return 1; + } + } + + long np = sysconf(target); + if(np < 0) + { + perror("sysconf"); + return 1; + } + + printf("%ld\n", np); + + return 0; +} diff --git a/cmd/pwd.c b/cmd/pwd.c @@ -0,0 +1,36 @@ +// 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> /* puts, perror, printf */ +#include <unistd.h> /* getcwd() */ + +int +main(int argc, char *argv[]) +{ + char pwd[BUFSIZ]; + + if(argc != 1) + { + fprintf(stderr, "usage: pwd\n"); + return 1; + } + + if(getcwd(pwd, sizeof(pwd)) == NULL) + { + perror("pwd: getcwd"); + return 1; + } + else + { + if(puts(pwd) < 0) + { + return 1; + } + else + { + return 0; + } + } +} diff --git a/cmd/seq.c b/cmd/seq.c @@ -0,0 +1,120 @@ +// 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 <stdbool.h> // bool, true, false +#include <stdio.h> // puts, fprintf +#include <stdlib.h> // atoi, exit +#include <unistd.h> // getopt, optarg, optind + +char *separator = "\n"; +bool zero_pad = false; + +void +seq(long i, long step, long last) +{ + if(i == last) + { + printf("%li%s", i, separator); + } + else if(i < last) + { + for(; i <= last; i += step) + printf("%li%s", i, separator); + } + else if(i > last) + { + for(; i >= last; i -= step) + printf("%li%s", i, separator); + } +} + +static long unsigned int +safe_labs(long int a) +{ + if(a >= 0) + { + return (long unsigned int)a; + } + else + { + return (long unsigned int)-a; + } +} + +long +get_num(char *str) +{ + errno = 0; + + long num = strtol(str, NULL, 10); + + if(errno != 0) + { + perror("seq: strtol:"); + exit(1); + } + + return num; +} + +void +usage(void) +{ + fprintf(stderr, "usage: seq [-w] [-s separator] [first [step]] last\n"); +} + +int +main(int argc, char *argv[]) +{ + int c; + + /* flawfinder: ignore. Old implementations of getopt should fix themselves */ + while((c = getopt(argc, argv, ":ws:")) != -1) + { + switch(c) + { + case 'w': + zero_pad = true; + break; + case 's': + separator = optarg; + break; + case '?': + usage(); + return 1; + } + } + + argc -= optind; + argv += optind; + + long first = 1; + long step = 1; + long last = 1; + + switch(argc) + { + case 1: + last = get_num(argv[0]); + break; + case 2: + first = get_num(argv[0]); + last = get_num(argv[1]); + break; + case 3: + first = get_num(argv[0]); + step = (long)safe_labs(get_num(argv[1])); + last = get_num(argv[2]); + break; + default: + usage(); + return 1; + } + + seq(first, step, last); + + return 0; +} diff --git a/cmd/sleep.1 b/cmd/sleep.1 @@ -0,0 +1,30 @@ +.\" Collection of Unix tools, comparable to coreutils +.\" Copyright 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd 2022-11-19 +.Dt SLEEP 1 +.Os +.Sh NAME +.Nm sleep +.Nd delay for a specified amount of time +.Sh SYNOPSIS +.Nm +.Ar duration ... +.Sh DESCRIPTION +The +.Nm +utily shell suspends execution for the total of each +.Ar duration +argument. +.Pp +.Ar duration +is a non-negative decimal number including floats, optionally followed by a suffix: s for seconds (default), m for minutes, h for hours. +Longer durations are taken as out of scope. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr at 1 , +.Xr crontab 1 , +.Xr nanosleep 3 +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/sleep.c b/cmd/sleep.c @@ -0,0 +1,129 @@ +// 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 <stdio.h> // fprintf, perror, sscanf +#include <stdlib.h> // exit +#include <time.h> // nanosleep, timespec + +// Maybe should be moved in ./lib with iso_parse +static struct timespec +strtodur(char *s) +{ + struct timespec dur = {.tv_sec = 0, .tv_nsec = 0}; + + if(s[0] == 0) + { + fprintf(stderr, "sleep: Got an empty string as duration\n"); + return dur; + } + + int parsed = 0; + if(s[0] != '.' && s[0] != ',') + { + errno = 0; + if(sscanf(s, "%10ld%n", &dur.tv_sec, &parsed) < 1) + { + if(errno == 0) + { + fprintf(stderr, "sleep: Not a number: %s\n", s); + } + else + { + perror("sleep: sscanf"); + } + exit(EXIT_FAILURE); + } + + s += parsed; + + if(s[0] == 0) return dur; + } + + if(s[0] == '.' || s[0] == ',') + { + double fraction = 0.0; + if(s[0] == ',') s[0] = '.'; + + parsed = 0; + errno = 0; + if(sscanf(s, "%10lf%n", &fraction, &parsed) < 1) + { + perror("sleep: sscanf"); + exit(EXIT_FAILURE); + } + + dur.tv_nsec = fraction * 1000000000; + s += parsed; + } + + if(s[0] == 0) return dur; + + if(s[1] != 0) + { + fprintf(stderr, "sleep: suffix '%s' is too long, should be only one character\n", s); + exit(1); + } + + switch(s[0]) + { + case 's': // seconds + break; + case 'm': // minutes + dur.tv_sec *= 60; + break; + case 'h': // hours + dur.tv_sec *= 60 * 60; + break; + default: + fprintf(stderr, "sleep: Unknown suffix %c\n", s[0]); + exit(1); + } + + return dur; +} + +int +main(int argc, char *argv[]) +{ + struct timespec dur = {.tv_sec = 0, .tv_nsec = 0}; + for(int i = 1; i < argc; i++) + { + struct timespec arg_dur = strtodur(argv[i]); + + dur.tv_sec += arg_dur.tv_sec; + dur.tv_nsec += arg_dur.tv_nsec; + if(dur.tv_nsec > 999999999) + { + dur.tv_nsec = 0; + dur.tv_sec += 1; + } + } + if(dur.tv_sec == 0 && dur.tv_nsec == 0) + { + fprintf(stderr, "sleep: Got a duration of 0\n"); + return 1; + } + + //fprintf(stderr, "sleep: going to sleep for %ld.%ld seconds\n", dur.tv_sec, dur.tv_nsec); + + errno = 0; + if(nanosleep(&dur, &dur) < 0) + { + if(errno == EINTR) + { + fprintf(stderr, + "sleep: Interrupted during sleep, still had %ld.%ld seconds remaining\n", + dur.tv_sec, + dur.tv_nsec); + } + else + { + perror("sleep: nanosleep"); + } + } + + return 0; +} diff --git a/cmd/strings.1 b/cmd/strings.1 @@ -0,0 +1,58 @@ +.\" 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-08-11 +.Dt STRINGS 1 +.Os +.Sh NAME +.Nm strings +.Nd find printable strings +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl t Ar format +.Op Fl n Ar number +.Op Ar files ... +.Sh DESCRIPTION +.Nm +reads each +.Ar file +in sequence and writes printable strings longer than 4 or +.Ar number . +If no +.Ar file +is given, +.Nm +reads from the standard input. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Ignored, present for POSIX compatibility. +Files are always scanned in their entirety and it is considered a flaw to do +otherwise. +.It Fl n Ar number +Change the minimum string length (default: 4). +.It Fl t Ar format +Write the byte offset of each string found, the format is dependend on the +format argument: +.Bl -tag -width d +.It d +Decimal +.It o +Octal +.It x +Hexadecimal +.El +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr nm 1 , +.Xr isprint 3 +.Sh STANDARDS +.Nm +is compliant with the +.St -p1003.1-2008 +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/strings.c b/cmd/strings.c @@ -0,0 +1,212 @@ +// 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 <ctype.h> /* isprint() */ +#include <errno.h> /* errno */ +#include <fcntl.h> /* open(), O_RDONLY */ +#include <limits.h> /* LONG_MIN, LONG_MAX */ +#include <stdio.h> /* fprintf(), BUFSIZ */ +#include <stdlib.h> /* strtol() */ +#include <string.h> /* strerror(), strncmp(), memset() */ +#include <unistd.h> /* read(), write(), close(), getopt(), optarg, optind */ + +size_t opt_min_strlen = 4; +char *opt_offset_format = NULL; + +int +print_string(char *buffer, size_t offset) +{ + int ret = 0; + + if(opt_offset_format == NULL) + { + ret = printf("%s\n", buffer); + } + else + { + /* flawfinder: ignore. opt_offset_format isn't user-provided */ + ret = printf(opt_offset_format, offset, buffer); + } + + if(ret < 0) + { + fprintf(stderr, "strings: Error writing: %s\n", strerror(errno)); + return 1; + } + else + { + return 0; + } +} + +int +concat(int fd, const char *fdname) +{ + ssize_t c; + char read_buf[4096]; + char write_buf[4096]; + size_t write_pos = 0; + size_t offset = 0; + + memset(write_buf, 0, sizeof(write_buf)); + + while((c = read(fd, read_buf, sizeof(read_buf))) > 0) + { + int read_pos = 0; + char b = 0; + + for(; read_pos < c; read_pos++) + { + b = read_buf[read_pos]; + + if(isprint(b) && write_pos < 4096) + { + write_buf[write_pos++] = b; + } + else + { + if(write_pos >= opt_min_strlen) + { + write_buf[write_pos + 1] = 0; + + if(print_string(write_buf, offset) != 0) + { + return 1; + } + } + + offset += write_pos; + offset++; + write_pos = 0; + memset(write_buf, 0, sizeof(write_buf)); + } + } + } + + if(c < 0) + { + fprintf(stderr, "strings: Error reading ‘%s’: %s\n", fdname, strerror(errno)); + return 1; + } + + return 0; +} + +void +usage() +{ + fprintf(stderr, "strings: [-a] [-t format] [-n number] [file...]\n"); +} + +int +main(int argc, char *argv[]) +{ + int c; + const char *errstr = NULL; + + /* flawfinder: ignore. Old implementations of getopt should fix themselves */ + while((c = getopt(argc, argv, ":an:t:")) != -1) + { + switch(c) + { + case 'a': + /* Structure is always ignored */ + break; + case 'n': + errno = 0; + char *endptr = ""; + opt_min_strlen = strtol(optarg, &endptr, 10); + if(*endptr != 0) + { + // extraneous characters is invalid + errno = EINVAL; + } + if(errno != 0) + { + fprintf(stderr, "strings: Option `-n %s`: %s\n", optarg, strerror(errno)); + usage(); + return 1; + } + if(opt_min_strlen == LLONG_MIN || opt_min_strlen < 1) + { + fprintf(stderr, "strings: Option `-n %s` is too small\n", optarg); + usage(); + return 1; + } + if(opt_min_strlen == LLONG_MAX || opt_min_strlen > 4096) + { + fprintf(stderr, "strings: Option `-n %s` is too large\n", optarg); + usage(); + return 1; + } + break; + case 't': + if(strnlen(optarg, 2) > 1) + { + usage(); + return 1; + } + + switch(optarg[0]) + { + case 'o': + opt_offset_format = "%zo %s\n"; + break; + case 'x': + opt_offset_format = "%zx %s\n"; + break; + case 'd': + opt_offset_format = "%zd %s\n"; + break; + default: + fprintf(stderr, "strings: Unknown format: %s\n", optarg); + usage(); + return 1; + } + break; + } + } + + argc -= optind; + argv += optind; + + if(argc < 1) + { + return concat(0, "<stdin>"); + } + + for(int argi = 0; argi < argc; argi++) + { + if(strncmp(argv[argi], "-", 2) == 0) + { + if(concat(0, "<stdin>") != 0) + { + return 1; + } + } + else + { + int fd = open(argv[argi], O_RDONLY); + if(fd <= 0) + { + fprintf(stderr, "strings: 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, "strings: Error closing ‘%s’: %s\n", argv[argi], strerror(errno)); + return 1; + } + } + } + + return 0; +} diff --git a/cmd/sync.1 b/cmd/sync.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-12-27 +.Dt SYNC 1 +.Os +.Sh NAME +.Nm sync +.Nd Schedule file system updates +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +shall cause all information in memory that updates file systems to be scheduled for writing out to all file systems. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr sync 2 +.Sh STANDARDS +No applicable one known. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/sync.c b/cmd/sync.c @@ -0,0 +1,14 @@ +// 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 _XOPEN_SOURCE 700 // XSI but not mere POSIX +#include <unistd.h> // sync() + +int +main(void) +{ + // Maybe consider hooking up to fsync(3p) + sync(); + return 0; +} diff --git a/cmd/tee.c b/cmd/tee.c @@ -0,0 +1,105 @@ +// 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 <assert.h> /* assert() */ +#include <errno.h> /* errno */ +#include <stdio.h> /* fprintf(), fgetc(), fputc(), fclose(), fopen() */ +#include <stdlib.h> /* malloc(), free(), abort() */ +#include <string.h> /* strerror() */ +#include <unistd.h> /* getopt(), opt… */ + +void +cleanup(FILE **fds) +{ + if(fds != NULL) + { + free(fds); + } +} + +int +main(int argc, char *argv[]) +{ + const char *mode = "w"; + FILE **fds = {NULL}; // Shut up GCC + int c; + + while((c = getopt(argc, argv, ":ai")) != -1) + { + switch(c) + { + case 'a': + mode = "a"; + break; + case 'i': /* ignore SIGINT */; + break; + } + } + + argc -= optind; + argv += optind; + + if(argc > 0) + { + fds = malloc(sizeof(FILE *) * (size_t)argc); + + if(!fds) + { + fprintf(stderr, "tee: Cannot allocate fd array: %s\n", strerror(errno)); + return 1; + } + } + + for(int argi = 0; argi < argc; argi++) + { + assert(argv[argi]); + + // POSIX: implementations shouldn't treat '-' as stdin + fds[argi] = fopen(argv[argi], mode); + + if(fds[argi] == NULL) + { + fprintf(stderr, "tee: Error opening ‘%s’: %s\n", argv[argi], strerror(errno)); + cleanup(fds); + return 1; + } + } + + // main loop, note that failed writes shouldn't make tee exit + int err = 0; + + while((c = fgetc(stdin)) != EOF) + { + if(fputc(c, stdout) == EOF) + { + fprintf(stderr, "tee: Error writing ‘<stdout>’: %s\n", strerror(errno)); + err = 1; + errno = 0; + } + + for(int argi = 0; argi < argc; argi++) + { + if(fputc(c, fds[argi]) == EOF) + { + fprintf(stderr, "tee: Error writing to argument %d: %s\n", argi, strerror(errno)); + err = 1; + errno = 0; + } + } + } + + // cleanup + for(int argi = 0; argi < argc; argi++) + { + if(fclose(fds[argi]) != 0) + { + abort(); + } + } + + cleanup(fds); + + return err; +} diff --git a/cmd/time.1 b/cmd/time.1 @@ -0,0 +1,48 @@ +.\" 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-12-01 +.Dt TIME 1 +.Os +.Sh NAME +.Nm time +.Nd measure time used by a command +.Sh SYNOPSIS +.Nm +.Op Fl p +.Ar command +.Op Ar argument... +.Sh DESCRIPTION +.Nm +measures the wall-clock time, user CPU time, system/kernel CPU time, used by +.Ar command +and outputs it to stderr. +.Sh OPTIONS +.Fl p +is ignored for compatibility reasons, the output is always in the POSIX format. +.Sh EXIT STATUS +If +.Ar command +is invoked, the exit status should be the one given by +.Ar command . +Otherwise, it will exit with the following values: +.Bl -tag -width 1-125 +.It 1-125 +An error occured in +.Ar command +.It 126 +.Ar command +was found but couldn't be invoked +.It 127 +.Ar command +could not be found +.El +.Sh SEE ALSO +.Xr times 3 +.Sh STANDARDS +.Nm +is compliant with the +.St -p1003.1-2008 +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/time.c b/cmd/time.c @@ -0,0 +1,112 @@ +// 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> // perror, fprintf +#include <stdlib.h> // abort +#include <sys/times.h> // times +#include <sys/wait.h> // waitpid +#include <unistd.h> // sysconf, fork, execvp, getopt + +void +usage() +{ + fprintf(stderr, "Usage: time command [argument ...]\n"); +} + +int +main(int argc, char *argv[]) +{ + struct tms tms; + int ret = 0; + + if(argc <= 1) + { + usage(); + return 0; + } + + int c = -1; + while((c = getopt(argc, argv, ":p")) != -1) + { + switch(c) + { + case 'p': // POSIX format (default) + break; + case ':': + fprintf(stderr, "time: Error: Missing operand for option: '-%c'\n", optopt); + usage(); + return 1; + case '?': + fprintf(stderr, "time: Error: Unrecognised option: '-%c'\n", optopt); + usage(); + return 1; + } + } + + argc -= optind; + argv += optind; + (void)argc; + + long ticks = sysconf(_SC_CLK_TCK); + if(ticks <= 0) + { + perror("time: sysconf(_SC_CLK_TCK)"); + return 1; + } + + clock_t t0 = times(&tms); + if(t0 == (clock_t)-1) + { + perror("time: times"); + return 1; + } + + // Note: posix_spawnp seems to lack enough error information + pid_t pid = fork(); + switch(pid) + { + case -1: + perror("time: fork"); + return 1; + case 0: + /* flawfinder: ignore. No restrictions on commands is intended */ + execvp(argv[0], argv); + ret = 126 + (errno == ENOENT); + perror("time: execvp"); + return ret; + default: + break; + } + + int status = 0; + waitpid(pid, &status, 0); + + int t1 = times(&tms); + if(t1 == (clock_t)-1) + { + perror("time: times"); + return 1; + } + + if(WIFSIGNALED(status)) + { + fprintf(stderr, "time: Command terminated by signal %d\n", WTERMSIG(status)); + ret = 128 + WTERMSIG(status); + } + + fprintf(stderr, + "real %f\nuser %f\nsys %f\n", + (t1 - t0) / (double)ticks, + tms.tms_cutime / (double)ticks, + tms.tms_cstime / (double)ticks); + + if(WIFEXITED(status)) + { + ret = WEXITSTATUS(status); + } + + return ret; +} diff --git a/cmd/touch.1 b/cmd/touch.1 @@ -0,0 +1,85 @@ +.\" Collection of Unix tools, comparable to coreutils +.\" Copyright 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd 2023-06-03 +.Dt TOUCH 1 +.Os +.Sh NAME +.Nm touch +.Nd change file access and modification times +.Sh SYNOPSIS +.Nm +.Op Fl achm +.Op Fl d Ar datetime | Fl r Ar ref_file +.Ar file... +.Sh DESCRIPTION +.Nm +changes the date modification and access times on each +.Ar file +it is given. +.Bl -tag -width Ds +.It Fl a +Change the access time, no changes to modification time unless +.Fl m +is also given. +.It Fl c +Do not create +.Ar file . +.It Fl h +Do not follow symlinks. +.It Fl m +Change the modification time, no changes to access time unless +.Fl a +is also given. +.It Fl d Ar datetime +Use the specified +.Ar datetime +instead of the current time. +Should be formatted as +.Ql YYYY-MM-DDThh:mm:SS[frac][Z] , +where: +.Bl -tag -width Ds +.It Ql YYYY-MM-DD +Corresponds to %Y-%m-%d of +.Xr strptime 3 . +.It Ql T +Is either +.Ql T +or a space. +.It Ql [frac] +Is either empty, or fractional seconds starting with either a comma +.Pq \&, +or a period +.Pq \&. . +.It Ql [Z] +Is either empty, signifying local time, or the letter +.Qq Z , +signifying UTC. +.El +.Pp +For example: +.Ql 2003-06-02T13:37:42.713Z +.It Fl r Ar ref_file +Use the corresponding times of the file at +.Ar ref_file +instead of the current time. +.El +.Sh EXIT STATUS +.Ex -std +Note: Will exit with failure when +.Fl c +is given but the file doesn't exists. +.Sh SEE ALSO +.Xr stat 1 , +.Xr futimens 3 +.Sh STANDARDS +.Nm +is mostly compliant with the +.St -p1003.1-2008 +specification. +.Fl t +is intentionally missing. +.Fl h +is an extension. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/touch.c b/cmd/touch.c @@ -0,0 +1,138 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _DEFAULT_SOURCE // tm_gmtoff/tm_zone +#define _XOPEN_SOURCE 700 // strptime (NetBSD) +#define _POSIX_C_SOURCE 200809L // O_NOFOLLOW, st_atim/st_mtim + +#include "../lib/iso_parse.h" /* iso_parse */ + +#include <errno.h> /* errno */ +#include <fcntl.h> /* open */ +#include <stdbool.h> /* bool */ +#include <stdio.h> /* perror */ +#include <sys/stat.h> /* futimens, stat, utimensat */ +#include <unistd.h> /* getopt, opt*, close */ + +int +main(int argc, char *argv[]) +{ + bool ch_atime = false, ch_mtime = false; + char *ref_file = NULL; + struct timespec times[2] = { + {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, // access + {.tv_sec = 0, .tv_nsec = UTIME_OMIT} // modification + }; + struct timespec target = {0, UTIME_NOW}; + int open_flags = O_WRONLY | O_CREAT | O_NOCTTY; + int utimensat_flags = 0; + + int c = 0; + while((c = getopt(argc, argv, ":achmr:t:d:")) != -1) + { + switch(c) + { + case 'a': + ch_atime = true; + break; + case 'c': + open_flags ^= O_CREAT; + break; + case 'h': + open_flags |= O_NOFOLLOW; + utimensat_flags |= AT_SYMLINK_NOFOLLOW; + break; + case 'm': + ch_mtime = true; + break; + case 'r': + ref_file = optarg; + break; + case 't': // [[CC]YY]MMDDhhmm[.SS] + // Too legacy of a format, too annoying to parse + fprintf(stderr, "touch: Option -d not supported, use -t\n"); + return 1; + break; + case 'd': + target = iso_parse(optarg); + break; + case ':': + fprintf(stderr, "touch: Error: Missing operand for option: '-%c'\n", optopt); + return 1; + case '?': + fprintf(stderr, "touch: Error: Unrecognised option: '-%c'\n", optopt); + return 1; + } + } + + argc -= optind; + argv += optind; + + // When neither -a nor -m are specified, change both + if(!ch_atime && !ch_mtime) + { + ch_atime = true; + ch_mtime = true; + } + + if(ref_file == NULL) + { + if(ch_atime) times[0] = target; + if(ch_mtime) times[1] = target; + } + else + { + struct stat ref; + + if(stat(ref_file, &ref) != 0) + { + perror("touch: stat"); + return 1; + } + + if(ch_atime) + { + times[0] = ref.st_atim; + } + if(ch_mtime) + { + times[1] = ref.st_mtim; + } + } + + for(int i = 0; i < argc; i++) + { + int fd = open(argv[i], open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(fd == -1) + { + if(errno == EISDIR) + { + if(utimensat(AT_FDCWD, argv[i], times, utimensat_flags) != 0) + { + perror("touch: utimensat"); + return 1; + } + + return 0; + } + if(errno != ENOENT) perror("touch: open"); + + return 1; + } + + if(futimens(fd, times) != 0) + { + perror("touch: futimens"); + return 1; + } + + if(close(fd) != 0) + { + perror("touch: close"); + return 1; + } + } + + return 0; +} diff --git a/cmd/true.1 b/cmd/true.1 @@ -0,0 +1,18 @@ +.\" 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 TRUE 1 +.Os +.Sh NAME +.Nm true +.Nd do nothing, successfully +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +.Nm +will return with exit status code zero. +.Sh EXIT STATUS +Zero. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/true.c b/cmd/true.c @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +int +main(void) +{ + return 0; +} diff --git a/cmd/tty.c b/cmd/tty.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 + +#include <errno.h> // errno +#include <stdio.h> // puts() +#include <unistd.h> // ttyname() + +int +main(void) +{ + char *name = ttyname(STDIN_FILENO); + + if(!name) + { + if(errno == ENOTTY) + { + if(puts("not a tty") < 0) + { + return 2; + } + + return 1; + } + else + { + perror("ttyname"); + return 2; + } + } + + if(puts(name) < 0) + { + return 2; + } + + return 0; +} diff --git a/cmd/unlink.1 b/cmd/unlink.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 2022-05-10 +.Dt UNLINK 1 +.Os +.Sh NAME +.Nm unlink +.Nd unlink filenames and possibly the referred files +.Sh SYNOPSIS +.Nm +.Op Ar files ... +.Sh DESCRIPTION +.Nm +deletes a filename from the filesystem. +If that filename was the last link to a file and no processes have the file open, the file is deleted. +.Sh EXIT STATUS +.Ex -std +.Sh STANDARDS +.Nm +is compliant with the +.St -p1003.1-2008 +specification. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me diff --git a/cmd/unlink.c b/cmd/unlink.c @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#include <errno.h> // errno +#include <stdio.h> // fprintf +#include <string.h> // strerror +#include <unistd.h> // unlink + +int +main(int argc, char *argv[]) +{ + for(int i = 1; i < argc; i++) + { + if(unlink(argv[i]) != 0) + { + fprintf(stderr, "unlink: Cannot unlink ‘%s’: %s\n", argv[i], strerror(errno)); + return 1; + } + } + + return 0; +} diff --git a/configure b/configure @@ -0,0 +1,242 @@ +#!/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 + CRAM=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 | grep -v -f target_filter | sed 's;.c$;;' | tr -d '\n' + echo + + printf 'MAN1 = ' + printf '%s\n ' cmd/*.1 | grep -v -f target_filter | 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}" +CRAM="${CRAM:-cram}" +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 && echo '#' > target_filter ; 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 CRAM "$CRAM" +then + : +else + echo "Notice: cram not found, trying prysk" + + CRAM="prysk" + if check_cmd CRAM "$CRAM" + then + : + else + CRAM="false" + echo 'Notice: Testsuite depending on cram/psyk disabled' + fi +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} +CRAM = ${CRAM} +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/coreutils.txt b/coreutils.txt @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 +[: ?, see test +arch: No +b2sum: No +base32: No +base64: Todo +basename: Done +basenc: No +cat: Done +chcon: No, SELinux-specific, wtf +chgrp: Todo +chmod: Todo +chown: Todo +chroot: Done +cksum: No, POSIX +comm: Maybe, POSIX +cp: Todo +csplit: No. Considered obsolete +cut: Todo +date: Done +dd: No. non-Unix +df: Todo +dir: No, use ls +dircolors: No, GNU-specific +dirname: Done +du: Todo +echo: Done +env: Done +expand: No, use sed +expr: ? +factor: ? +false: Done +fmt: ? +fold: ? +head: No, use sed +hostid: No, get a better id(ea) +id: Done +install: No +join: ? +link: Done +ln: Todo +logname: No, POSIX +ls: ? +md5sum: No +mkdir: Todo +mkfifo: ? +mknod: Todo +mktemp: Todo +mv: Todo +nice: Maybe +nl: No, use sed +nohup: Maybe +nproc: ? +numfmt: Maybe, consider humanize(1) +od: Todo +paste: Todo +pathchk: ? +pinky: No +pr: No. Considered obsolete +printenv: No, the output sucks +printf: Todo +ptx: No +pwd: Done +readlink: ? +realpath: Todo +rm: Todo +rmdir: No +runcon: No. (SELinux-specific util, wtf) +seq: Done +sha1sum: No +sha224sum: No +sha256sum: No +sha384sum: No +sha512sum: No +shred: ? +shuf: Todo +sleep: Done +sort: ? +split: No. Considered obsolete +stat: Todo +stdbuf: No +stty: Todo +sum: No +sync: Done +tac: Todo +tail: ? +tee: Done +test: ? +timeout: ? +touch: Done +tr: Todo +true: Done +truncate: Todo +tsort: ? +tty: Done +uname: Use sname +unexpand: Use sed +uniq: Todo +unlink: Done +users: No, use who +vdir: No, use ls +wc: Todo +who: Todo +whoami: No. Use id +yes: No diff --git a/lib/iso_parse.c b/lib/iso_parse.c @@ -0,0 +1,80 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _DEFAULT_SOURCE // tm_gmtoff/tm_zone +#define _XOPEN_SOURCE 700 // strptime (NetBSD) +#define _POSIX_C_SOURCE 200809L // st_atim/st_mtim + +#include <ctype.h> /* isdigit */ +#include <errno.h> /* errno */ +#include <stdio.h> /* perror, sscanf */ +#include <stdlib.h> /* exit */ +#include <string.h> /* memset */ +#include <time.h> /* strptime, tm */ + +// Calls exit() on failure +struct timespec +iso_parse(char *arg) +{ + // YYYY-MM-DD[T ]hh:mm:SS([,\.]frac)?Z? + // Dammit Unixes why no nanoseconds in `struct tm` nor `strptime` + struct timespec time = {.tv_sec = 0, .tv_nsec = 0}; + + // For Alpine's abuild compatibility + if(arg[0] == '@') + { + arg++; + errno = 0; + + time.tv_sec = strtol(arg, NULL, 10); + if(errno != 0) + { + perror("strtol"); + exit(EXIT_FAILURE); + } + return time; + } + + struct tm tm; + memset(&tm, 0, sizeof(tm)); + + // No %F in POSIX + char *s = strptime(arg, "%Y-%m-%d", &tm); + + if(s[0] != 'T' && s[0] != ' ') exit(EXIT_FAILURE); + s++; + + s = strptime(s, "%H:%M:%S", &tm); + + if(s[0] == ',' || s[0] == '.') + { + double fraction = 0.0; + int parsed = 0; + + if(s[0] == ',') s[0] = '.'; + + if(sscanf(s, "%10lf%n", &fraction, &parsed) < 1) exit(EXIT_FAILURE); + + time.tv_nsec = fraction * 1000000000; + s += parsed; + + // too many digits + if(isdigit(s[0])) exit(EXIT_FAILURE); + } + + if(s[0] == 'Z') + { + tm.tm_gmtoff = 0; + tm.tm_zone = "UTC"; + } + + time.tv_sec = mktime(&tm); + if(time.tv_sec == (time_t)-1) + { + perror("mktime"); + exit(EXIT_FAILURE); + } + + return time; +} diff --git a/lib/iso_parse.h b/lib/iso_parse.h @@ -0,0 +1,6 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#include <time.h> /* struct timespec */ +extern struct timespec iso_parse(char *); diff --git a/lsb_commands.txt b/lsb_commands.txt @@ -0,0 +1,141 @@ +# SPDX-FileCopyrightText: 2015 Linux Foundation <https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/toccommand.html> +# SPDX-License-Identifier: GFDL-1.1-only +[: ?, see test +ar: out of scope +at: out of scope +awk: out of scope +basename: Done +batch: out of scope +bc: ? +cat: Done +chfn: out of scope +chgrp: Todo +chmod: Todo +chown: Todo +chsh: out of scope +cksum: No, POSIX +cmp: No +col: No? +comm: Maybe, POSIX +cp: Todo +cpio: out of scope +crontab: out of scope +csplit: No. Considered obsolete +cut: Todo +date: Done +dd: No. non-Unix +df: Todo +diff: out of scope +dirname: Done +dmesg: out of scope +du: Todo +echo: Done +ed: out of scope +egrep: No +env: Done +expand: No, use sed +expr: ? +false: Done +fgrep: No +file: No +find: out of scope +fold: ? +fuser: ? +gencat: out of scope +getconf: Maybe +gettext: out of scope +grep: out of scope +groupadd: out of scope +groupdel: out of scope +groupmod: out of scope +groups: Probably not +gunzip: out of scope +gzip: out of scope +head: No, use sed +hostname: Maybe +iconv: out of scope +id: Done +infocmp: No +install: No +install_initd +ipcrm: No? +ipcs: No? +join: ? +kill: maybe +killall: maybe +ln: Todo +locale: No +localedef: No +logger: Maybe +logname: No, POSIX +lp: No +lpr: No +ls: ? +lsb_release: No, distro dependent +m4: out of scope +mailx: No +make: out of scope +man: out of scope +md5sum: No +mkdir: Todo +mkfifo: ? +mknod: Todo +mktemp: Todo +more: No +mount: out of scope +msgfmt: out of scope +mv: Todo +newgrp: out of scope +nice: Maybe +nl: No, use sed +nohup: Maybe +od: Todo +passwd: out of scope +paste: Todo +patch: out of scope +pathchk: ? +pax: out of scope +pidof: ? +pr: No. Considered obsolete +printf: Todo +ps: ? +pwd: Done +remove_initd: ? +renice: Maybe +rm: Todo +rmdir: No +sed: out of scope +sendmail: out of scope +seq: Done +sh: out of scope +shutdown: No +sleep: Todo +sort: ? +split: No. Considered obsolete +strings: Done +strip: out of scope +stty: Todo +su: out of scope +sync: Done +tail: ? +tar: out of scope +tee: Todo +test: ? +tic: out of scope +time: Done +touch: Todo (futimens(3p)) +tput: out of scope +tr: Todo +true: Done +tsort: ? +tty: Done +umount: out of scope +uname: Use sname +unexpand: Use sed +uniq: Todo +useradd: out of scope +userdel: out of scope +usermod: out of scope +wc: Todo +xargs: ? +zcat: out of scope diff --git a/test-cmd/Kyuafile b/test-cmd/Kyuafile @@ -0,0 +1,27 @@ +-- 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="base64", timeout=1} +atf_test_program{name="basename", timeout=1} +atf_test_program{name="cat", timeout=1} +atf_test_program{name="date", timeout=1} +atf_test_program{name="dirname", timeout=1} +atf_test_program{name="echo", timeout=1} +atf_test_program{name="env", timeout=1} +atf_test_program{name="false", timeout=1} +atf_test_program{name="id", timeout=1} +atf_test_program{name="link", timeout=1} +atf_test_program{name="pwd", timeout=1} +atf_test_program{name="seq", timeout=1} +atf_test_program{name="strings", timeout=1} +atf_test_program{name="tee", timeout=1} +atf_test_program{name="touch", timeout=3} +atf_test_program{name="true", timeout=1} +atf_test_program{name="tty", timeout=1} +atf_test_program{name="unlink", timeout=1} diff --git a/test-cmd/base64 b/test-cmd/base64 @@ -0,0 +1,80 @@ +#!/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 allbytes +allbytes_body() { + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 inputs/all_bytes + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 <inputs/all_bytes + atf_check -o file:outputs/base64/all_bytes ../cmd/base64 - <inputs/all_bytes +} + +atf_test_case devnull +devnull_body() { + atf_check ../cmd/base64 /dev/null + atf_check ../cmd/base64 </dev/null + atf_check ../cmd/base64 - </dev/null +} + +atf_test_case rfc4648 +rfc4648_body() { + # Test vectors from RFC4648 + atf_check -o 'inline:' sh -c 'printf "" | ../cmd/base64' + atf_check -o 'inline:Zg==\n' sh -c 'printf "f" | ../cmd/base64' + atf_check -o 'inline:Zm8=\n' sh -c 'printf "fo" | ../cmd/base64' + atf_check -o 'inline:Zm9v\n' sh -c 'printf "foo" | ../cmd/base64' + atf_check -o 'inline:Zm9vYg==\n' sh -c 'printf "foob" | ../cmd/base64' + atf_check -o 'inline:Zm9vYmE=\n' sh -c 'printf "fooba" | ../cmd/base64' + atf_check -o 'inline:Zm9vYmFy\n' sh -c 'printf "foobar" | ../cmd/base64' +} + +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:base64: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/base64 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:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 <inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:base64: Error writing: No space left on device\n' sh -c '../cmd/base64 - <inputs/all_bytes >/dev/full' +} + +atf_test_case readslash +readslash_body() { + [ "$(uname -s)" = "NetBSD" ] && atf_skip "NetBSD allows to read directories" + + atf_check -s exit:1 -e "inline:base64: Error reading ‘/’: Is a directory\n" ../cmd/base64 / +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:base64: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/base64 /var/empty/e/no/ent +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o file:outputs/base64/all_bytes -- ../cmd/base64 -- inputs/all_bytes + # shellcheck disable=SC1112 + atf_check -s exit:1 -e "inline:base64: Error: Unrecognised option: ‘--’\n" -o empty -- ../cmd/base64 --- inputs/all_bytes +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case rfc4648 + atf_add_test_case allbytes + 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/basename b/test-cmd/basename @@ -0,0 +1,80 @@ +#!/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:.\n" ../cmd/basename +} + +atf_test_case one_slash +one_slash_body() { + atf_check -o "inline:bin\n" ../cmd/basename "/usr/bin" +} + +atf_test_case two_slash +two_slash_body() { + atf_check -o "inline:bin\n" ../cmd/basename "/usr//bin" +} + +atf_test_case two_dash +two_dash_body() { + atf_check -o "inline:bin\n" ../cmd/basename -- "/usr//bin" +} + +atf_test_case testopt +testopt_body() { + atf_check -o "inline:bin\n" ../cmd/basename "/usr//bin-test" "-test" +} + +atf_test_case usage +usage_body() { + atf_check -s exit:1 -e "inline:usage: basename string [suffix]\n" ../cmd/basename 1 2 3 +} + +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:basename: puts: No space left on device\n' sh -c '../cmd/basename >/dev/full' + atf_check -s exit:1 -e 'inline:basename: puts: No space left on device\n' sh -c '../cmd/basename "/usr/bin" >/dev/full' + atf_check -s exit:1 -e 'inline:basename: puts: No space left on device\n' sh -c '../cmd/basename "/usr//bin-test" "-test" >/dev/full' +} + +atf_test_case nullarg +nullarg_body() { + atf_check -e "inline:.\n" ../cmd/basename "$(printf '\x00')" + atf_check -e "inline:bin\n" ../cmd/basename "/usr/bin" "$(printf '\x00')" +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o 'inline:-\n' -- ../cmd/basename '-' + atf_check -o 'inline:.\n' -- ../cmd/basename '--' + atf_check -o 'inline:--\n' -- ../cmd/basename --a a + atf_check -o 'inline:---\n' -- ../cmd/basename '---' +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case noargs + atf_add_test_case one_slash + atf_add_test_case two_slash + atf_add_test_case two_dash + atf_add_test_case testopt + atf_add_test_case usage + + # puts in glibc doesn't returns -1 on failure + atf_add_test_case devfull + + # Broken behavior in ATF, might be caused by stripping out \x00 + #atf_add_test_case nullarg + + atf_add_test_case doubledash +} diff --git a/test-cmd/cat b/test-cmd/cat @@ -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:inputs/all_bytes ../cmd/cat inputs/all_bytes +} + +atf_test_case allinput +allinput_body() { + atf_check -o file:inputs/all_bytes ../cmd/cat <inputs/all_bytes +} + +atf_test_case alldashinput +alldashinput_body() { + atf_check -o file:inputs/all_bytes ../cmd/cat - <inputs/all_bytes +} + +atf_test_case devnull +devnull_body() { + atf_check ../cmd/cat /dev/null + atf_check ../cmd/cat </dev/null + atf_check ../cmd/cat - </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:cat: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/cat 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:cat: Error writing: No space left on device\n' sh -c '../cmd/cat inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:cat: Error writing: No space left on device\n' sh -c '../cmd/cat <inputs/all_bytes >/dev/full' + atf_check -s exit:1 -e 'inline:cat: Error writing: No space left on device\n' sh -c '../cmd/cat - <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:cat: Error reading ‘/’: Is a directory\n' ../cmd/cat / +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:cat: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/cat /var/empty/e/no/ent +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o file:inputs/all_bytes -- ../cmd/cat -- inputs/all_bytes + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:cat: Error opening ‘---’: No such file or directory\n' -o empty -- ../cmd/cat --- inputs/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/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/dirname b/test-cmd/dirname @@ -0,0 +1,38 @@ +#!/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 -e "inline:usage: dirname string\n" -s exit:1 ../cmd/dirname +} + +atf_test_case one_slash +one_slash_body() { + atf_check -o "inline:/usr\n" ../cmd/dirname "/usr/bin" +} + +atf_test_case two_slash +two_slash_body() { + atf_check -o "inline:/usr\n" ../cmd/dirname "/usr//bin" +} + +atf_test_case two_dash +two_dash_body() { + atf_check -o "inline:/usr\n" ../cmd/dirname -- "/usr//bin" +} + +atf_test_case badarg +badarg_body() { + atf_check -s exit:1 -e "inline:usage: dirname string\n" ../cmd/dirname -a "/usr//bin" +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case noargs + atf_add_test_case badarg + atf_add_test_case one_slash + atf_add_test_case two_slash + atf_add_test_case two_dash +} diff --git a/test-cmd/echo b/test-cmd/echo @@ -0,0 +1,40 @@ +#!/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 "inline:\n" ../cmd/echo +} + +atf_test_case hello +hello_body() { + atf_check -o "inline:hello world\n" ../cmd/echo hello world +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o "inline:-- hello\n" ../cmd/echo -- hello +} + +atf_test_case devfull +devfull_body() { + atf_check -s exit:1 -e 'inline:echo: write(1, buffer, arg_len): No space left on device\n' sh -c '../cmd/echo hello world >/dev/full' +} + +atf_test_case opt_n +opt_n_body() { + atf_check -o "inline:" ../cmd/echo -n + atf_check -o "inline:foo" ../cmd/echo -n foo + atf_check -o "inline:foo bar" ../cmd/echo -n foo bar + atf_check -o "inline:-- foo" ../cmd/echo -n -- foo +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case empty + atf_add_test_case hello + atf_add_test_case doubledash + atf_add_test_case devfull + atf_add_test_case opt_n +} diff --git a/test-cmd/env b/test-cmd/env @@ -0,0 +1,73 @@ +#!/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() { + [ "${LD_PRELOAD}" = "libsandbox.so" ] && atf_expect_fail "sandbox (gentoo) interferes with the environment" + + atf_check -o "inline:FOO=BAR\n" env -i FOO=BAR ../cmd/env +} + +atf_test_case badarg +badarg_body() { + atf_check -s not-exit:0 -e "inline:env: Error: Unrecognised option: '-f'\nenv [-i] [-u key | --unset=key] [key=value ...] [command [args]]\n" ../cmd/env -f +} + +atf_test_case iflag +iflag_body() { + [ "${LD_PRELOAD}" = "libsandbox.so" ] && atf_expect_fail "sandbox (gentoo) interferes with the environment" + + atf_check -o "inline:FOO=BAR\n" ../cmd/env -i FOO=BAR ../cmd/env + atf_check -o "inline:FOO=BAR\n" ../cmd/env -i FOO=BAR + atf_check -o "not-inline:FOO=BAR\n" ../cmd/env FOO=BAR ../cmd/env + atf_check -o "not-inline:FOO=BAR\n" ../cmd/env FOO=BAR +} + +atf_test_case uflag +uflag_body() { + [ "${LD_PRELOAD}" = "libsandbox.so" ] && atf_expect_fail "sandbox (gentoo) interferes with the environment" + + atf_check -o "inline:FOO=BAR\n" ../cmd/env -i FOO=BAR BAR=FOO ../cmd/env -u BAR + + atf_check -s not-exit:0 -e "inline:env: Error: Missing operand for option: '-u'\nenv [-i] [-u key | --unset=key] [key=value ...] [command [args]]\n" ../cmd/env -u +} + +atf_test_case unsetflag +unsetflag_body() { + [ "${LD_PRELOAD}" = "libsandbox.so" ] && atf_expect_fail "sandbox (gentoo) interferes with the environment" + + atf_check -o "inline:FOO=BAR\n" ../cmd/env -i FOO=BAR BAR=FOO ../cmd/env --unset=BAR + + atf_check -s not-exit:0 -e "inline:env: Error: Missing operand for option: '-u'\nenv [-i] [-u key | --unset=key] [key=value ...] [command [args]]\n" ../cmd/env -u +} + +atf_test_case devfull +devfull_body() { + [ "$(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:env: puts(environ[i]): No space left on device\n' sh -c '../cmd/env >/dev/full' +} + +atf_test_case noutil +noutil_body() { + atf_check -s exit:127 -e 'inline:env: execvp("/var/empty/e/no/ent", ...): No such file or directory\n' ../cmd/env /var/empty/e/no/ent +} + +atf_test_case false +false_body() { + atf_check -s exit:1 ../cmd/env false +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case noargs + atf_add_test_case badarg + atf_add_test_case iflag + atf_add_test_case uflag + atf_add_test_case unsetflag + atf_add_test_case devfull + atf_add_test_case noutil + atf_add_test_case false +} diff --git a/test-cmd/false b/test-cmd/false @@ -0,0 +1,25 @@ +#!/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 -s exit:1 ../cmd/false +} + +atf_test_case nohelp +nohelp_body() { + atf_check -s exit:1 ../cmd/false --help +} + +atf_test_case devfull +devfull_body() { + atf_check -s exit:1 ../cmd/false --help >/dev/full +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case basic + atf_add_test_case nohelp + atf_add_test_case devfull +} diff --git a/test-cmd/foo b/test-cmd/foo diff --git a/test-cmd/id b/test-cmd/id @@ -0,0 +1,164 @@ +#!/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 cleanup +noargs_body() { + atf_check -o save:noargs.out ../cmd/id + atf_check grep -q "uid=$(id -u)($(id -un)) gid=$(id -g)($(id -gn)) groups=" noargs.out +} +noargs_cleanup() { + rm -f noargs.out +} + +atf_test_case names cleanup +names_body() { + atf_check -o save:names.out ../cmd/id -n + atf_check grep -q "uid=$(id -un) gid=$(id -gn) groups=" names.out +} +names_cleanup() { + rm -f names.out +} + +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/id >/dev/full' + atf_check -s exit:1 sh -c '../cmd/id -n >/dev/full' + atf_check -s exit:1 sh -c '../cmd/id -u >/dev/full' + atf_check -s exit:1 sh -c '../cmd/id -g >/dev/full' +} + +atf_test_case group +group_body() { + atf_check -o "inline:$(id -g)\n" ../cmd/id -g + atf_check -o "inline:$(id -gr)\n" ../cmd/id -gr + atf_check -o "inline:$(id -gn)\n" ../cmd/id -gn + atf_check -o "inline:$(id -gnr)\n" ../cmd/id -gnr +} + +atf_test_case user +user_body() { + atf_check -o "inline:$(id -u)\n" ../cmd/id -u + atf_check -o "inline:$(id -ur)\n" ../cmd/id -ur + atf_check -o "inline:$(id -un)\n" ../cmd/id -un + atf_check -o "inline:$(id -unr)\n" ../cmd/id -unr +} + +atf_test_case groups +groups_body() { + # sadly GNU coreutils' id(1) sorts it's grouplist + + atf_check -o not-empty ../cmd/id -G + atf_check -o not-empty ../cmd/id -Gr + atf_check -o not-empty ../cmd/id -Gn + atf_check -o not-empty ../cmd/id -Gnr +} + +# Make sure the correct list is returned for different users +# Previously it would only return the runtime list of the current user +atf_test_case regression_groups +regression_groups() { + atf_check -o "not-inline:$(../cmd/id -G root)" ../cmd/id -G nobody + atf_check -o "not-inline:$(../cmd/id -Gr root)" ../cmd/id -Gr nobody + atf_check -o "not-inline:$(../cmd/id -Gn root)" ../cmd/id -Gn nobody + atf_check -o "not-inline:$(../cmd/id -Gnr root)" ../cmd/id -Gnr nobody +} + +atf_test_case noetc +noetc_body() { + bwrap_args="--bind / / --bind /var/empty /etc" + + command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found" + [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set" + + set -f + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -u)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -u + # shellcheck disable=SC2086 + atf_check -s exit:1 -e "inline:id: cannot find name for user ID $(id -u)\n" -o "inline:$(id -u)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -un + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -g)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -g + # shellcheck disable=SC2086 + atf_check -s exit:1 -e "inline:id: cannot find name for group ID $(id -g)\n" -o "inline:$(id -g)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -gn +} + +atf_test_case nopasswd +nopasswd_body() { + bwrap_args="--bind / / --bind /dev/null /etc/passwd" + + command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found" + [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set" + + set -f + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -u)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -u + # shellcheck disable=SC2086 + atf_check -s exit:1 -e "inline:id: cannot find name for user ID $(id -u)\n" -o "inline:$(id -u)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -un + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -g)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -g + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -gn)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -gn +} + +atf_test_case nogroup +nogroup_body() { + bwrap_args="--bind / / --bind /dev/null /etc/group" + + command -v "${BWRAP:-bwrap}" >/dev/null 2>/dev/null || atf_skip "${BWRAP:-bwrap} command not found" + [ -n "${NO_BWRAP}" ] && atf_skip "'NO_BWRAP' set" + + set -f + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -u)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -u + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -un)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -un + + # shellcheck disable=SC2086 + atf_check -o "inline:$(id -g)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -g + # shellcheck disable=SC2086 + atf_check -s exit:1 -e "inline:id: cannot find name for group ID $(id -g)\n" -o "inline:$(id -g)\n" -- "${BWRAP:-bwrap}" ${bwrap_args} ../cmd/id -gn +} + +atf_test_case badarg +badarg_body() { + atf_check -s exit:1 -e 'inline:Usage: id [-Ggu] [-nr] [user]\n' ../cmd/id -a +} + +atf_test_case root cleanup +root_body() { + atf_check -o save:root.out ../cmd/id root + atf_check grep -q "uid=$(id -u root)($(id -un root)) gid=$(id -g root)($(id -gn root)) groups=" root.out +} +root_cleanup() { + rm -f root.out +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case devfull + atf_add_test_case badarg + + atf_add_test_case noargs + atf_add_test_case names + atf_add_test_case group + atf_add_test_case user + atf_add_test_case groups + + atf_add_test_case noetc + atf_add_test_case nogroup + atf_add_test_case nopasswd + + atf_add_test_case root +} diff --git a/test-cmd/inputs/all_bytes b/test-cmd/inputs/all_bytes Binary files differ. diff --git a/test-cmd/inputs/strings/libc_start_main b/test-cmd/inputs/strings/libc_start_main Binary files differ. diff --git a/test-cmd/inputs/strings/true b/test-cmd/inputs/strings/true Binary files differ. diff --git a/test-cmd/link b/test-cmd/link @@ -0,0 +1,35 @@ +#!/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:usage: link <reference> <destination>\n" ../cmd/link +} + +atf_test_case basic cleanup +basic_body() { + touch foo + + atf_check ../cmd/link foo bar + atf_check cmp foo bar + + echo hello >> foo + atf_check cmp foo bar +} +basic_cleanup() { + atf_check rm -f foo bar +} + +atf_test_case enoent +enoent_body() { + atf_check -s exit:1 -e 'inline:link: No such file or directory\n' ../cmd/link /var/empty/e/no/ent enoent_foo + atf_check -s exit:1 -e 'inline:link: No such file or directory\n' ../cmd/link enoent_bar /var/empty/e/no/ent +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case empty + atf_add_test_case basic + atf_add_test_case enoent +} diff --git a/test-cmd/outputs/base64/all_bytes b/test-cmd/outputs/base64/all_bytes @@ -0,0 +1,5 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4 +OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx +cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq +q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj +5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w== diff --git a/test-cmd/outputs/strings/all_bytes b/test-cmd/outputs/strings/all_bytes @@ -0,0 +1 @@ + !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ diff --git a/test-cmd/outputs/strings/all_bytes_td b/test-cmd/outputs/strings/all_bytes_td @@ -0,0 +1 @@ +32 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ diff --git a/test-cmd/outputs/strings/all_bytes_to b/test-cmd/outputs/strings/all_bytes_to @@ -0,0 +1 @@ +40 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ diff --git a/test-cmd/outputs/strings/all_bytes_tx b/test-cmd/outputs/strings/all_bytes_tx @@ -0,0 +1 @@ +20 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ diff --git a/test-cmd/outputs/strings/true b/test-cmd/outputs/strings/true @@ -0,0 +1,91 @@ +/lib64/ld-linux-x86-64.so.2 +__cxa_finalize +__libc_start_main +libc.so.6 +GLIBC_2.2.5 +_ITM_deregisterTMCloneTable +__gmon_start__ +_ITM_registerTMCloneTable +AUATI +[]A\A]A^A_ +;*3$" +GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +elf-init.c +stddef.h +f.... +GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +main +long long int +GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +size_t +__init_array_start +/var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +envp +elf-init.c +long unsigned int +__libc_csu_fini +char +argc +size +__libc_csu_init +argv +long double +__init_array_end +true.c +/home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +true.c +elf-init.c +__init_array_end +_DYNAMIC +__init_array_start +__GNU_EH_FRAME_HDR +_GLOBAL_OFFSET_TABLE_ +__libc_csu_fini +_ITM_deregisterTMCloneTable +_edata +__libc_start_main@GLIBC_2.2.5 +__data_start +__gmon_start__ +__dso_handle +_IO_stdin_used +__libc_csu_init +__bss_start +main +__TMC_END__ +_ITM_registerTMCloneTable +__cxa_finalize@GLIBC_2.2.5 +.symtab +.strtab +.shstrtab +.interp +.note.ABI-tag +.gnu.hash +.dynsym +.dynstr +.gnu.version +.gnu.version_r +.rela.dyn +.init +.plt +.plt.got +.text +.fini +.rodata +.eh_frame_hdr +.eh_frame +.init_array +.fini_array +.dynamic +.data +.bss +.comment +.debug_aranges +.debug_info +.debug_abbrev +.debug_line +.debug_str +.debug_loc +.debug_line_str +.debug_rnglists diff --git a/test-cmd/outputs/strings/true_8 b/test-cmd/outputs/strings/true_8 @@ -0,0 +1,65 @@ +/lib64/ld-linux-x86-64.so.2 +__cxa_finalize +__libc_start_main +libc.so.6 +GLIBC_2.2.5 +_ITM_deregisterTMCloneTable +__gmon_start__ +_ITM_registerTMCloneTable +[]A\A]A^A_ +GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +/usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +elf-init.c +stddef.h +GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +long long int +GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +__init_array_start +/var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +elf-init.c +long unsigned int +__libc_csu_fini +__libc_csu_init +long double +__init_array_end +/home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +elf-init.c +__init_array_end +_DYNAMIC +__init_array_start +__GNU_EH_FRAME_HDR +_GLOBAL_OFFSET_TABLE_ +__libc_csu_fini +_ITM_deregisterTMCloneTable +__libc_start_main@GLIBC_2.2.5 +__data_start +__gmon_start__ +__dso_handle +_IO_stdin_used +__libc_csu_init +__bss_start +__TMC_END__ +_ITM_registerTMCloneTable +__cxa_finalize@GLIBC_2.2.5 +.shstrtab +.note.ABI-tag +.gnu.hash +.gnu.version +.gnu.version_r +.rela.dyn +.plt.got +.eh_frame_hdr +.eh_frame +.init_array +.fini_array +.dynamic +.comment +.debug_aranges +.debug_info +.debug_abbrev +.debug_line +.debug_str +.debug_loc +.debug_line_str +.debug_rnglists diff --git a/test-cmd/outputs/strings/true_8_td b/test-cmd/outputs/strings/true_8_td @@ -0,0 +1,65 @@ +680 /lib64/ld-linux-x86-64.so.2 +929 __cxa_finalize +944 __libc_start_main +962 libc.so.6 +972 GLIBC_2.2.5 +984 _ITM_deregisterTMCloneTable +1012 __gmon_start__ +1027 _ITM_registerTMCloneTable +4553 []A\A]A^A_ +12304 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +12347 GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +13435 /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +13484 elf-init.c +13498 stddef.h +13655 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +13720 long long int +13734 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +14905 __init_array_start +14924 /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +14989 elf-init.c +15000 long unsigned int +15018 __libc_csu_fini +15049 __libc_csu_init +15070 long double +15082 __init_array_end +15473 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +16232 elf-init.c +16243 __init_array_end +16260 _DYNAMIC +16269 __init_array_start +16288 __GNU_EH_FRAME_HDR +16307 _GLOBAL_OFFSET_TABLE_ +16329 __libc_csu_fini +16345 _ITM_deregisterTMCloneTable +16380 __libc_start_main@GLIBC_2.2.5 +16410 __data_start +16423 __gmon_start__ +16438 __dso_handle +16451 _IO_stdin_used +16466 __libc_csu_init +16482 __bss_start +16499 __TMC_END__ +16511 _ITM_registerTMCloneTable +16537 __cxa_finalize@GLIBC_2.2.5 +16581 .shstrtab +16599 .note.ABI-tag +16613 .gnu.hash +16639 .gnu.version +16652 .gnu.version_r +16667 .rela.dyn +16688 .plt.got +16717 .eh_frame_hdr +16731 .eh_frame +16741 .init_array +16753 .fini_array +16765 .dynamic +16785 .comment +16794 .debug_aranges +16809 .debug_info +16821 .debug_abbrev +16835 .debug_line +16847 .debug_str +16858 .debug_loc +16869 .debug_line_str +16885 .debug_rnglists diff --git a/test-cmd/outputs/strings/true_8_to b/test-cmd/outputs/strings/true_8_to @@ -0,0 +1,65 @@ +1250 /lib64/ld-linux-x86-64.so.2 +1641 __cxa_finalize +1660 __libc_start_main +1702 libc.so.6 +1714 GLIBC_2.2.5 +1730 _ITM_deregisterTMCloneTable +1764 __gmon_start__ +2003 _ITM_registerTMCloneTable +10711 []A\A]A^A_ +30020 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +30073 GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +32173 /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +32254 elf-init.c +32272 stddef.h +32527 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +32630 long long int +32646 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +35071 __init_array_start +35114 /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +35215 elf-init.c +35230 long unsigned int +35252 __libc_csu_fini +35311 __libc_csu_init +35336 long double +35352 __init_array_end +36161 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +37550 elf-init.c +37563 __init_array_end +37604 _DYNAMIC +37615 __init_array_start +37640 __GNU_EH_FRAME_HDR +37663 _GLOBAL_OFFSET_TABLE_ +37711 __libc_csu_fini +37731 _ITM_deregisterTMCloneTable +37774 __libc_start_main@GLIBC_2.2.5 +40032 __data_start +40047 __gmon_start__ +40066 __dso_handle +40103 _IO_stdin_used +40122 __libc_csu_init +40142 __bss_start +40163 __TMC_END__ +40177 _ITM_registerTMCloneTable +40231 __cxa_finalize@GLIBC_2.2.5 +40305 .shstrtab +40327 .note.ABI-tag +40345 .gnu.hash +40377 .gnu.version +40414 .gnu.version_r +40433 .rela.dyn +40460 .plt.got +40515 .eh_frame_hdr +40533 .eh_frame +40545 .init_array +40561 .fini_array +40575 .dynamic +40621 .comment +40632 .debug_aranges +40651 .debug_info +40665 .debug_abbrev +40703 .debug_line +40717 .debug_str +40732 .debug_loc +40745 .debug_line_str +40765 .debug_rnglists diff --git a/test-cmd/outputs/strings/true_8_tx b/test-cmd/outputs/strings/true_8_tx @@ -0,0 +1,65 @@ +2a8 /lib64/ld-linux-x86-64.so.2 +3a1 __cxa_finalize +3b0 __libc_start_main +3c2 libc.so.6 +3cc GLIBC_2.2.5 +3d8 _ITM_deregisterTMCloneTable +3f4 __gmon_start__ +403 _ITM_registerTMCloneTable +11c9 []A\A]A^A_ +3010 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +303b GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +347b /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +34ac elf-init.c +34ba stddef.h +3557 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +3598 long long int +35a6 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +3a39 __init_array_start +3a4c /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +3a8d elf-init.c +3a98 long unsigned int +3aaa __libc_csu_fini +3ac9 __libc_csu_init +3ade long double +3aea __init_array_end +3c71 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +3f68 elf-init.c +3f73 __init_array_end +3f84 _DYNAMIC +3f8d __init_array_start +3fa0 __GNU_EH_FRAME_HDR +3fb3 _GLOBAL_OFFSET_TABLE_ +3fc9 __libc_csu_fini +3fd9 _ITM_deregisterTMCloneTable +3ffc __libc_start_main@GLIBC_2.2.5 +401a __data_start +4027 __gmon_start__ +4036 __dso_handle +4043 _IO_stdin_used +4052 __libc_csu_init +4062 __bss_start +4073 __TMC_END__ +407f _ITM_registerTMCloneTable +4099 __cxa_finalize@GLIBC_2.2.5 +40c5 .shstrtab +40d7 .note.ABI-tag +40e5 .gnu.hash +40ff .gnu.version +410c .gnu.version_r +411b .rela.dyn +4130 .plt.got +414d .eh_frame_hdr +415b .eh_frame +4165 .init_array +4171 .fini_array +417d .dynamic +4191 .comment +419a .debug_aranges +41a9 .debug_info +41b5 .debug_abbrev +41c3 .debug_line +41cf .debug_str +41da .debug_loc +41e5 .debug_line_str +41f5 .debug_rnglists diff --git a/test-cmd/outputs/strings/true_td b/test-cmd/outputs/strings/true_td @@ -0,0 +1,91 @@ +680 /lib64/ld-linux-x86-64.so.2 +929 __cxa_finalize +944 __libc_start_main +962 libc.so.6 +972 GLIBC_2.2.5 +984 _ITM_deregisterTMCloneTable +1012 __gmon_start__ +1027 _ITM_registerTMCloneTable +4478 AUATI +4553 []A\A]A^A_ +8359 ;*3$" +12304 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +12347 GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +13435 /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +13484 elf-init.c +13498 stddef.h +13640 f.... +13655 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +13715 main +13720 long long int +13734 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +14898 size_t +14905 __init_array_start +14924 /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +14984 envp +14989 elf-init.c +15000 long unsigned int +15018 __libc_csu_fini +15034 char +15039 argc +15044 size +15049 __libc_csu_init +15065 argv +15070 long double +15082 __init_array_end +15466 true.c +15473 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +16225 true.c +16232 elf-init.c +16243 __init_array_end +16260 _DYNAMIC +16269 __init_array_start +16288 __GNU_EH_FRAME_HDR +16307 _GLOBAL_OFFSET_TABLE_ +16329 __libc_csu_fini +16345 _ITM_deregisterTMCloneTable +16373 _edata +16380 __libc_start_main@GLIBC_2.2.5 +16410 __data_start +16423 __gmon_start__ +16438 __dso_handle +16451 _IO_stdin_used +16466 __libc_csu_init +16482 __bss_start +16494 main +16499 __TMC_END__ +16511 _ITM_registerTMCloneTable +16537 __cxa_finalize@GLIBC_2.2.5 +16565 .symtab +16573 .strtab +16581 .shstrtab +16591 .interp +16599 .note.ABI-tag +16613 .gnu.hash +16623 .dynsym +16631 .dynstr +16639 .gnu.version +16652 .gnu.version_r +16667 .rela.dyn +16677 .init +16683 .plt +16688 .plt.got +16697 .text +16703 .fini +16709 .rodata +16717 .eh_frame_hdr +16731 .eh_frame +16741 .init_array +16753 .fini_array +16765 .dynamic +16774 .data +16780 .bss +16785 .comment +16794 .debug_aranges +16809 .debug_info +16821 .debug_abbrev +16835 .debug_line +16847 .debug_str +16858 .debug_loc +16869 .debug_line_str +16885 .debug_rnglists diff --git a/test-cmd/outputs/strings/true_to b/test-cmd/outputs/strings/true_to @@ -0,0 +1,91 @@ +1250 /lib64/ld-linux-x86-64.so.2 +1641 __cxa_finalize +1660 __libc_start_main +1702 libc.so.6 +1714 GLIBC_2.2.5 +1730 _ITM_deregisterTMCloneTable +1764 __gmon_start__ +2003 _ITM_registerTMCloneTable +10576 AUATI +10711 []A\A]A^A_ +20247 ;*3$" +30020 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +30073 GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +32173 /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +32254 elf-init.c +32272 stddef.h +32510 f.... +32527 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +32623 main +32630 long long int +32646 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +35062 size_t +35071 __init_array_start +35114 /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +35210 envp +35215 elf-init.c +35230 long unsigned int +35252 __libc_csu_fini +35272 char +35277 argc +35304 size +35311 __libc_csu_init +35331 argv +35336 long double +35352 __init_array_end +36152 true.c +36161 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +37541 true.c +37550 elf-init.c +37563 __init_array_end +37604 _DYNAMIC +37615 __init_array_start +37640 __GNU_EH_FRAME_HDR +37663 _GLOBAL_OFFSET_TABLE_ +37711 __libc_csu_fini +37731 _ITM_deregisterTMCloneTable +37765 _edata +37774 __libc_start_main@GLIBC_2.2.5 +40032 __data_start +40047 __gmon_start__ +40066 __dso_handle +40103 _IO_stdin_used +40122 __libc_csu_init +40142 __bss_start +40156 main +40163 __TMC_END__ +40177 _ITM_registerTMCloneTable +40231 __cxa_finalize@GLIBC_2.2.5 +40265 .symtab +40275 .strtab +40305 .shstrtab +40317 .interp +40327 .note.ABI-tag +40345 .gnu.hash +40357 .dynsym +40367 .dynstr +40377 .gnu.version +40414 .gnu.version_r +40433 .rela.dyn +40445 .init +40453 .plt +40460 .plt.got +40471 .text +40477 .fini +40505 .rodata +40515 .eh_frame_hdr +40533 .eh_frame +40545 .init_array +40561 .fini_array +40575 .dynamic +40606 .data +40614 .bss +40621 .comment +40632 .debug_aranges +40651 .debug_info +40665 .debug_abbrev +40703 .debug_line +40717 .debug_str +40732 .debug_loc +40745 .debug_line_str +40765 .debug_rnglists diff --git a/test-cmd/outputs/strings/true_tx b/test-cmd/outputs/strings/true_tx @@ -0,0 +1,91 @@ +2a8 /lib64/ld-linux-x86-64.so.2 +3a1 __cxa_finalize +3b0 __libc_start_main +3c2 libc.so.6 +3cc GLIBC_2.2.5 +3d8 _ITM_deregisterTMCloneTable +3f4 __gmon_start__ +403 _ITM_registerTMCloneTable +117e AUATI +11c9 []A\A]A^A_ +20a7 ;*3$" +3010 GCC: (Gentoo Hardened 10.3.0-r2 p3) 10.3.0 +303b GCC: (Gentoo Hardened 11.2.0 p1) 11.2.0 +347b /usr/lib/gcc/x86_64-pc-linux-gnu/10.3.0/include +34ac elf-init.c +34ba stddef.h +3548 f.... +3557 GNU C99 11.2.0 -mtune=generic -march=x86-64 -g -O2 -std=c99 +3593 main +3598 long long int +35a6 GNU C11 10.3.0 -march=znver1 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mmovbe -maes -msha -mpclmul -mpopcnt -mabm -mno-lwp -mfma -mno-fma4 -mno-xop -mbmi -mno-sgx -mbmi2 -mno-pconfig -mno-wbnoinvd -mno-tbm -mavx -mavx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mrdrnd -mf16c -mfsgsbase -mrdseed -mprfchw -madx -mfxsr -mxsave -mxsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 -mclflushopt -mxsavec -mxsaves -mno-avx512dq -mno-avx512bw -mno-avx512vl -mno-avx512ifma -mno-avx512vbmi -mno-avx5124fmaps -mno-avx5124vnniw -mno-clwb -mmwaitx -mclzero -mno-pku -mno-rdpid -mno-gfni -mno-shstk -mno-avx512vbmi2 -mno-avx512vnni -mno-vaes -mno-vpclmulqdq -mno-avx512bitalg -mno-avx512vpopcntdq -mno-movdiri -mno-movdir64b -mno-waitpkg -mno-cldemote -mno-ptwrite -mno-avx512bf16 -mno-enqcmd -mno-avx512vp2intersect --param=l1-cache-size=32 --param=l1-cache-line-size=64 --param=l2-cache-size=512 -mtune=znver1 -m64 -ggdb -ggdb -O2 -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fstack-protector-strong -fmath-errno -fno-stack-protector -fPIC -fstack-protector-strong -ftls-model=initial-exec +3a32 size_t +3a39 __init_array_start +3a4c /var/tmp/portage/sys-libs/glibc-2.33-r7/work/glibc-2.33/csu +3a88 envp +3a8d elf-init.c +3a98 long unsigned int +3aaa __libc_csu_fini +3aba char +3abf argc +3ac4 size +3ac9 __libc_csu_init +3ad9 argv +3ade long double +3aea __init_array_end +3c6a true.c +3c71 /home/haelwenn/Sources/git/hacktivis.me/git/utils/bin +3f61 true.c +3f68 elf-init.c +3f73 __init_array_end +3f84 _DYNAMIC +3f8d __init_array_start +3fa0 __GNU_EH_FRAME_HDR +3fb3 _GLOBAL_OFFSET_TABLE_ +3fc9 __libc_csu_fini +3fd9 _ITM_deregisterTMCloneTable +3ff5 _edata +3ffc __libc_start_main@GLIBC_2.2.5 +401a __data_start +4027 __gmon_start__ +4036 __dso_handle +4043 _IO_stdin_used +4052 __libc_csu_init +4062 __bss_start +406e main +4073 __TMC_END__ +407f _ITM_registerTMCloneTable +4099 __cxa_finalize@GLIBC_2.2.5 +40b5 .symtab +40bd .strtab +40c5 .shstrtab +40cf .interp +40d7 .note.ABI-tag +40e5 .gnu.hash +40ef .dynsym +40f7 .dynstr +40ff .gnu.version +410c .gnu.version_r +411b .rela.dyn +4125 .init +412b .plt +4130 .plt.got +4139 .text +413f .fini +4145 .rodata +414d .eh_frame_hdr +415b .eh_frame +4165 .init_array +4171 .fini_array +417d .dynamic +4186 .data +418c .bss +4191 .comment +419a .debug_aranges +41a9 .debug_info +41b5 .debug_abbrev +41c3 .debug_line +41cf .debug_str +41da .debug_loc +41e5 .debug_line_str +41f5 .debug_rnglists diff --git a/test-cmd/outputs/tee/hello_all_bytes b/test-cmd/outputs/tee/hello_all_bytes Binary files differ. diff --git a/test-cmd/pwd b/test-cmd/pwd @@ -0,0 +1,49 @@ +#!/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:${PWD}\n" ../cmd/pwd + atf_check -o "inline:$(atf_get_srcdir)\n" ../cmd/pwd +} + +atf_test_case args +args_body() { + atf_check -s exit:1 -e "inline:usage: pwd\n" ../cmd/pwd -H +} + +atf_test_case enoent cleanup +enoent_body() { + mkdir -p "$(atf_get_srcdir)/remove-me" || exit 1 + cd "$(atf_get_srcdir)/remove-me" || exit 1 + + atf_check -o "inline:$(atf_get_srcdir)/remove-me\n" "$(atf_get_srcdir)/../cmd/pwd" + + rm -fr "$(atf_get_srcdir)/remove-me" || exit 1 + + atf_check -s exit:1 -e 'inline:pwd: getcwd: No such file or directory\n' "$(atf_get_srcdir)/../cmd/pwd" +} +enoent_cleanup() { + rm -fr "$(atf_get_srcdir)/remove-me" +} + +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/pwd >/dev/full' +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case simple + atf_add_test_case args + atf_add_test_case enoent + atf_add_test_case devfull +} diff --git a/test-cmd/seq b/test-cmd/seq @@ -0,0 +1,75 @@ +#!/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 one +one_body() { + atf_check -o "inline:1\n" ../cmd/seq 1 + atf_check -o "inline:1\n2\n3\n4\n5\n" ../cmd/seq 5 + + atf_check -o "inline:1\n0\n-1\n" -- ../cmd/seq -- -1 + atf_check -o "inline:1\n0\n-1\n-2\n-3\n-4\n-5\n" -- ../cmd/seq -- -5 +} + +atf_test_case two +two_body() { + atf_check -o "inline:0\n1\n" ../cmd/seq 0 1 + atf_check -o "inline:0\n1\n2\n3\n4\n5\n" ../cmd/seq 0 5 + atf_check -o "inline:10\n11\n12\n13\n14\n15\n" ../cmd/seq 10 15 + + atf_check -o "inline:0\n-1\n" -- ../cmd/seq 0 -1 + atf_check -o "inline:0\n-1\n-2\n-3\n-4\n-5\n" -- ../cmd/seq 0 -5 + atf_check -o "inline:-10\n-11\n-12\n-13\n-14\n-15\n" -- ../cmd/seq -- -10 -15 + atf_check -o "inline:2\n1\n0\n-1\n-2\n" -- ../cmd/seq 2 -2 + atf_check -o "inline:-2\n-1\n0\n1\n2\n" -- ../cmd/seq -- -2 2 +} + +atf_test_case three +three_body() { + atf_check -o "inline:0\n1\n" ../cmd/seq 0 1 1 + atf_check -o "inline:0\n1\n2\n3\n4\n5\n" ../cmd/seq 0 1 5 + atf_check -o "inline:10\n11\n12\n13\n14\n15\n" ../cmd/seq 10 1 15 + + atf_check -o "inline:0\n2\n4\n" ../cmd/seq 0 2 5 + atf_check -o "inline:10\n12\n14\n" ../cmd/seq 10 2 15 + + atf_check -o "inline:0\n-1\n" -- ../cmd/seq 0 1 -1 + atf_check -o "inline:0\n-1\n-2\n-3\n-4\n-5\n" -- ../cmd/seq 0 1 -5 + atf_check -o "inline:-10\n-11\n-12\n-13\n-14\n-15\n" -- ../cmd/seq -- -10 1 -15 + + atf_check -o "inline:0\n-1\n" -- ../cmd/seq 0 -1 -1 + + atf_check -o "inline:0\n-2\n-4\n" -- ../cmd/seq 0 2 -5 + atf_check -o "inline:-10\n-12\n-14\n" -- ../cmd/seq -- -10 2 -15 +} + +atf_test_case noarg +noarg_body() { + atf_check -s exit:1 -e 'inline:usage: seq [-w] [-s separator] [first [step]] last\n' ../cmd/seq +} + +atf_test_case badflag +badflag_body() { + atf_check -s exit:1 -e 'inline:usage: seq [-w] [-s separator] [first [step]] last\n' ../cmd/seq -f +} + +atf_test_case limits +limits_body() { + atf_check ../cmd/seq 9223372036854775806 9223372036854775807 + atf_check -s exit:1 ../cmd/seq 9223372036854775806 9223372036854775808 + + atf_check ../cmd/seq -- -9223372036854775807 -9223372036854775808 + atf_check -s exit:1 ../cmd/seq -- -9223372036854775807 -9223372036854775809 +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + atf_add_test_case one + atf_add_test_case two + atf_add_test_case three + + atf_add_test_case noarg + atf_add_test_case badflag + #atf_add_test_case limits +} diff --git a/test-cmd/sleep.t b/test-cmd/sleep.t @@ -0,0 +1,30 @@ +#!/usr/bin/env cram +# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + + $ cd $TESTDIR/../cmd + + $ ./sleep + sleep: Got a duration of 0 + [1] + + $ ./sleep -f + sleep: Not a number: -f + [1] + + $ ./time ./sleep 0 + sleep: Got a duration of 0 + real 0.0* (re) + user 0.0* (re) + sys 0.0* (re) + [1] + + $ ./time ./sleep 1 + real 1.0* (re) + user 0.0* (re) + sys 0.0* (re) + + $ ./time ./sleep .1 + real 0.10* (re) + user 0.0* (re) + sys 0.0* (re) diff --git a/test-cmd/stat_atime b/test-cmd/stat_atime @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 +case "$(uname -s)" in + FreeBSD) stat -f%Sa -t'%F %T%z' "$@" ;; + *BSD) stat -f%Sa -t'%F %T.%f%z' "$@" ;; + *) stat -c%x "$@" ;; +esac diff --git a/test-cmd/stat_mtime b/test-cmd/stat_mtime @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2017-2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 +case "$(uname -s)" in + FreeBSD) stat -f%Sm -t'%F %T%z' "$@" ;; + *BSD) stat -f%Sm -t'%F %T.%f%z' "$@" ;; + *) stat -c%y "$@" ;; +esac diff --git a/test-cmd/strings b/test-cmd/strings @@ -0,0 +1,123 @@ +#!/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 allbytes +allbytes_body() { + atf_check -o file:outputs/strings/all_bytes ../cmd/strings inputs/all_bytes + atf_check -o file:outputs/strings/all_bytes ../cmd/strings <inputs/all_bytes + atf_check -o file:outputs/strings/all_bytes ../cmd/strings - <inputs/all_bytes +} + +atf_test_case trueelf +trueelf_body() { + atf_check -o file:outputs/strings/true ../cmd/strings inputs/strings/true + atf_check -o file:outputs/strings/true ../cmd/strings <inputs/strings/true + atf_check -o file:outputs/strings/true ../cmd/strings - <inputs/strings/true +} + +atf_test_case true8elf +true8elf_body() { + atf_check -o file:outputs/strings/true_8 ../cmd/strings -n 8 inputs/strings/true + atf_check -o file:outputs/strings/true_8 ../cmd/strings -n 8 <inputs/strings/true + atf_check -o file:outputs/strings/true_8 ../cmd/strings -n 8 - <inputs/strings/true +} + +atf_test_case devnull +devnull_body() { + atf_check ../cmd/strings /dev/null + atf_check ../cmd/strings </dev/null + atf_check ../cmd/strings - </dev/null +} + +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:strings: Error writing: No space left on device\n' sh -c '../cmd/strings inputs/strings/true >/dev/full' +} + +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:strings: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/strings 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 octalformat +octalformat_body() { + atf_check -o file:outputs/strings/all_bytes_to ../cmd/strings -to inputs/all_bytes + atf_check ../cmd/strings -to /dev/null + atf_check -o file:outputs/strings/true_to ../cmd/strings -to inputs/strings/true + atf_check -o file:outputs/strings/true_8_to ../cmd/strings -to -n 8 inputs/strings/true +} + +atf_test_case hexformat +hexformat_body() { + atf_check -o file:outputs/strings/all_bytes_tx ../cmd/strings -tx inputs/all_bytes + atf_check ../cmd/strings -tx /dev/null + atf_check -o file:outputs/strings/true_tx ../cmd/strings -tx inputs/strings/true + atf_check -o file:outputs/strings/true_8_tx ../cmd/strings -tx -n 8 inputs/strings/true +} + +atf_test_case decformat +decformat_body() { + atf_check -o file:outputs/strings/all_bytes_td ../cmd/strings -td inputs/all_bytes + atf_check ../cmd/strings -td /dev/null + atf_check -o file:outputs/strings/true_td ../cmd/strings -td inputs/strings/true + atf_check -o file:outputs/strings/true_8_td ../cmd/strings -td -n 8 inputs/strings/true +} + +atf_test_case badformat +badformat_body() { + usage="strings: [-a] [-t format] [-n number] [file...]\n" + + atf_check -s exit:1 -e "inline:strings: Unknown format: t\n${usage}" ../cmd/strings -tt inputs/all_bytes + atf_check -s exit:1 -e "inline:strings: Unknown format: t\n${usage}" ../cmd/strings -tt /dev/null + atf_check -s exit:1 -e "inline:strings: Unknown format: t\n${usage}" ../cmd/strings -tt inputs/strings/true + atf_check -s exit:1 -e "inline:strings: Unknown format: t\n${usage}" ../cmd/strings -tt -n 8 inputs/strings/true +} + +atf_test_case erange_n +erange_n_body() { + usage="strings: [-a] [-t format] [-n number] [file...]\n" + + atf_check -s exit:1 -e "inline:strings: Option \`-n 0\` is too small\n${usage}" -- ../cmd/strings -n 0 inputs/all_bytes + atf_check -s exit:1 -e "inline:strings: Option \`-n 4097\` is too large\n${usage}" -- ../cmd/strings -n 4097 inputs/all_bytes + atf_check -s exit:1 -e "inline:strings: Option \`-n f\`: Invalid argument\n${usage}" -- ../cmd/strings -n f inputs/all_bytes + atf_check -s exit:1 -e "inline:strings: Option \`-n 42f\`: Invalid argument\n${usage}" -- ../cmd/strings -n 42f inputs/all_bytes +} + +atf_test_case usage +usage_body() { + atf_check -s exit:1 -e 'inline:strings: [-a] [-t format] [-n number] [file...]\n' ../cmd/strings -t aa inputs/all_bytes +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case allbytes + atf_add_test_case trueelf + atf_add_test_case true8elf + atf_add_test_case devnull + + atf_add_test_case devfull + atf_add_test_case noperm + atf_add_test_case erange_n + + atf_add_test_case octalformat + atf_add_test_case hexformat + atf_add_test_case decformat + atf_add_test_case badformat + + atf_add_test_case usage +} diff --git a/test-cmd/tee b/test-cmd/tee @@ -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 allinput +allinput_body() { + atf_check -o file:inputs/all_bytes ../cmd/tee <inputs/all_bytes +} + +atf_test_case writefile cleanup +writefile_body() { + echo 'hello' > tmp_tee.log + atf_check -o file:inputs/all_bytes ../cmd/tee tmp_tee.log <inputs/all_bytes + atf_check -o empty -s exit:1 grep hello tmp_tee.log +} +writefile_cleanup() { + rm tmp_tee.log +} + +atf_test_case appendfile cleanup +appendfile_body() { + echo 'hello' > tmp_tee.log + atf_check -o file:inputs/all_bytes ../cmd/tee -a tmp_tee.log <inputs/all_bytes + atf_check -o file:outputs/tee/hello_all_bytes cat tmp_tee.log +} +appendfile_cleanup() { + rm tmp_tee.log +} + +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:tee: Error opening ‘inputs/chmod_000’: Permission denied\n' ../cmd/tee inputs/chmod_000 </dev/null +} +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 fputs()" + [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for fputs()" + [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for fputs()" + + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:tee: Error writing ‘<stdout>’: No space left on device\n' sh -c '../cmd/tee <inputs/all_bytes >/dev/full' + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:tee: Error writing ‘<stdout>’: No space left on device\n' sh -c '../cmd/tee - <inputs/all_bytes >/dev/full' +} + +atf_test_case nullinput +nullinput_body() { + atf_check ../cmd/tee </dev/null +} + +atf_test_case writeslash +writeslash_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:tee: Error opening ‘./’: Is a directory\n' ../cmd/tee ./ <inputs/all_bytes +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:tee: Error opening ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/tee /var/empty/e/no/ent <inputs/all_bytes +} + +atf_test_case doubledash +doubledash_body() { + atf_check -o file:inputs/all_bytes -- ../cmd/tee -- <inputs/all_bytes + #atf_check -s exit:1 -e 'inline:tee: Error opening ‘---’: No such file or directory\n' -o empty -- ../cmd/tee --- <inputs/all_bytes +} + + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case allinput + atf_add_test_case writefile + atf_add_test_case appendfile + atf_add_test_case noperm + atf_add_test_case devfull + atf_add_test_case nullinput + atf_add_test_case writeslash + atf_add_test_case enoent + atf_add_test_case doubledash +} diff --git a/test-cmd/time.t b/test-cmd/time.t @@ -0,0 +1,31 @@ +#!/usr/bin/env cram +# SPDX-FileCopyrightText: 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + + $ cd $TESTDIR/../cmd + + $ ./time + Usage: time command [argument ...] + + $ ./time -f + time: Error: Unrecognised option: '-f' + Usage: time command [argument ...] + [1] + + $ ./time /var/empty/e/no/ent + time: execvp: No such file or directory + real 0.[0-9]* (re) + user 0.[0-9]* (re) + sys 0.[0-9]* (re) + [127] + + $ ./time false + real 0.[0-9]* (re) + user 0.[0-9]* (re) + sys 0.[0-9]* (re) + [1] + + $ ./time true + real 0.[0-9]* (re) + user 0.[0-9]* (re) + sys 0.[0-9]* (re) diff --git a/test-cmd/touch b/test-cmd/touch @@ -0,0 +1,205 @@ +#!/usr/bin/env atf-sh +# SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + +atf_test_case noargs +noargs_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo +} + +atf_test_case ref_noargs +ref_noargs_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -r ../cmd/touch ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o "inline:$(./stat_atime ../cmd/touch)\n" ./stat_atime ./foo + atf_check -o "inline:$(./stat_mtime ../cmd/touch)\n" ./stat_mtime ./foo +} + +atf_test_case mtime +mtime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -m ./foo + atf_check -o "inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo +} + +atf_test_case ref_mtime +ref_mtime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -m -r ../cmd/touch ./foo + atf_check -o "inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o "not-inline:$(./stat_atime ../cmd/touch)\n" ./stat_atime ./foo + atf_check -o "inline:$(./stat_mtime ../cmd/touch)\n" ./stat_mtime ./foo +} + +atf_test_case atime +atime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -a ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "inline:${mtime}\n" ./stat_mtime ./foo +} + +atf_test_case ref_atime +ref_atime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -a -r ../cmd/touch ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o "inline:$(./stat_atime ../cmd/touch)\n" ./stat_atime ./foo + atf_check -o "not-inline:$(./stat_mtime ../cmd/touch)\n" ./stat_mtime ./foo +} + +atf_test_case amtime +amtime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -a -m ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo +} + +atf_test_case ref_amtime +ref_amtime_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + atf_check ../cmd/touch -a -m -r ../cmd/touch ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o "inline:$(./stat_atime ../cmd/touch)\n" ./stat_atime ./foo + atf_check -o "inline:$(./stat_mtime ../cmd/touch)\n" ./stat_mtime ./foo +} + +atf_test_case dir +dir_body() { + mkdir -p ./foo.d + atime="$(./stat_atime ./foo.d)" + mtime="$(./stat_mtime ./foo.d)" + + maybe_sleep + atf_check ../cmd/touch ./foo.d + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo.d + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo.d +} + +atf_test_case optd +optd_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + unset TZ + + atf_check ../cmd/touch -d 2003-06-02T13:37:42Z ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42(\.0+)? ?(Z|[\+\-]00:?00)$' ./stat_atime ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42(\.0+)? ?(Z|[\+\-]00:?00)$' ./stat_mtime ./foo +} + +atf_test_case optd_frac +optd_frac_body() { + atf_check touch -a ./foo + maybe_sleep + atf_check touch -m ./foo + maybe_sleep + atime="$(./stat_atime ./foo)" + mtime="$(./stat_mtime ./foo)" + + unset TZ + + atf_check ../cmd/touch -d 2003-06-02T13:37:42.713Z ./foo + atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo + atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42.7130+ ?(Z|[\+\-]00:?00)$' ./stat_atime ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42.7130+ ?(Z|[\+\-]00:?00)$' ./stat_mtime ./foo + + atf_check ../cmd/touch -d 2003-06-02T13:37:42.123456789Z ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42.123456789 ?(Z|[\+\-]00:?00)$' ./stat_atime ./foo + atf_check -o 'match:^2003-06-02[T ]13:37:42.123456789 ?(Z|[\+\-]00:?00)$' ./stat_mtime ./foo + + atf_check -s 'exit:1' ../cmd/touch -d 2003-06-02T13:37:42.1234567890Z ./foo +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + atf_add_test_case noargs + atf_add_test_case atime + atf_add_test_case mtime + atf_add_test_case amtime + + atf_add_test_case ref_noargs + atf_add_test_case ref_atime + atf_add_test_case ref_mtime + atf_add_test_case ref_amtime + + atf_add_test_case dir + + atf_add_test_case optd + + # No support for displaying fractional seconds on FreeBSD stat(1) + if uname -s | grep -iq FreeBSD; then + # shellcheck disable=SC2317 + maybe_sleep() { sleep 1; } + else + atf_add_test_case optd_frac + # shellcheck disable=SC2317 + maybe_sleep() { sleep .1; } + fi +} diff --git a/test-cmd/true b/test-cmd/true @@ -0,0 +1,25 @@ +#!/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 ../cmd/true +} + +atf_test_case nohelp +nohelp_body() { + atf_check ../cmd/true --help +} + +atf_test_case devfull +devfull_body() { + atf_check ../cmd/true --help >/dev/full +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case basic + atf_add_test_case nohelp + atf_add_test_case devfull +} diff --git a/test-cmd/tty b/test-cmd/tty @@ -0,0 +1,43 @@ +#!/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() { + tty >/dev/null || atf_expect_fail "test environment not in a tty" + + atf_check -o 'not-inline:not a tty\n' ../cmd/tty + +} + +atf_test_case in_devnull +in_devnull_body() { + atf_check -s 'exit:1' -o 'inline:not a tty\n' ../cmd/tty </dev/null +} + +atf_test_case out_devnull +out_devnull_body() { + tty >/dev/null || atf_expect_fail "test environment not in a tty" + + atf_check -o 'not-inline:not a tty\n' sh -c '../cmd/tty >/dev/null' +} + +atf_test_case out_devfull +out_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:2" sh -c '../cmd/tty >/dev/full' +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + + . ../test_functions.sh + + atf_add_test_case basic + atf_add_test_case in_devnull + atf_add_test_case out_devnull + atf_add_test_case out_devfull +} diff --git a/test-cmd/unlink b/test-cmd/unlink @@ -0,0 +1,24 @@ +#!/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/unlink-this || atf_fail "touch inputs/unlink-this" + atf_check ../cmd/unlink inputs/unlink-this +} +basic_cleanup() { + rm -f inputs/unlink-this || atf_fail "rm -f inputs/unlink-this" +} + +atf_test_case enoent +enoent_body() { + # shellcheck disable=SC1112 + atf_check -s exit:1 -e 'inline:unlink: Cannot unlink ‘/var/empty/e/no/ent’: No such file or directory\n' ../cmd/unlink /var/empty/e/no/ent +} + +atf_init_test_cases() { + cd "$(atf_get_srcdir)" || exit 1 + atf_add_test_case basic + atf_add_test_case enoent +} 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 +}