From ef1416cd42c2555f2d1e8f03ea17e79a8907563c Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 24 Dec 2023 17:13:17 -0700 Subject: [PATCH] intcmp(1): new tool; npc(1): new tool; scrut(1): new tool; str(1): new tool; strcmp(1): new tool; tests: added POSIX compatibility test and C compiler compatibility test; Makefile: converted to GNUmakefile; README: added README; docs: added docs --- GNUmakefile | 73 +++++++++++++++ Makefile | 41 --------- README | 32 +++++++ docs/false.1 | 35 +++++++ docs/intcmp.1 | 78 ++++++++++++++++ docs/npc.1 | 68 ++++++++++++++ docs/str.1 | 58 ++++++++++++ docs/strcmp.1 | 63 +++++++++++++ docs/true.1 | 35 +++++++ src/cat.c | 104 --------------------- src/false.c | 18 +--- src/intcmp.c | 84 +++++++++++++++++ src/libfileis.c | 63 +++++++++++++ src/libfileis.h | 62 +++++++++++++ src/npc.c | 64 +++++++++++++ src/scrut.c | 98 ++++++++++++++++++++ src/str.c | 75 +++++++++++++++ src/strcmp.c | 25 +++++ src/tail.c | 209 ------------------------------------------ src/true.c | 18 +--- src/yac.c | 77 ---------------- src/yac.h | 26 ------ tests/cc-compat.sh | 35 +++++++ tests/posix-compat.sh | 24 +++++ tests/posix/test | 19 ++++ 25 files changed, 997 insertions(+), 487 deletions(-) create mode 100644 GNUmakefile delete mode 100644 Makefile create mode 100644 README create mode 100644 docs/false.1 create mode 100644 docs/intcmp.1 create mode 100644 docs/npc.1 create mode 100644 docs/str.1 create mode 100644 docs/strcmp.1 create mode 100644 docs/true.1 delete mode 100644 src/cat.c create mode 100644 src/intcmp.c create mode 100644 src/libfileis.c create mode 100644 src/libfileis.h create mode 100644 src/npc.c create mode 100644 src/scrut.c create mode 100644 src/str.c create mode 100644 src/strcmp.c delete mode 100644 src/tail.c delete mode 100644 src/yac.c delete mode 100644 src/yac.h create mode 100755 tests/cc-compat.sh create mode 100755 tests/posix-compat.sh create mode 100755 tests/posix/test diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..9c2eaa9 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,73 @@ +# Copyright (c) 2023 Emma Tebibyte +# Copyright (c) 2023 DTB +# SPDX-License-Identifier: FSFAP +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice and this +# notice are preserved. This file is offered as-is, without any warranty. + +# If we want to use POSIX make we can’t use ifeq +# .POSIX: +# .PRAGMA: posix_202x +.PHONY: clean +.PHONY: install +.PHONY: test + +PREFIX=/usr/local +CC=cc +CFLAGS=-O3 -Lbuild + +ifeq ($(CC), gcc) + CFLAGS=-O3 -s -Wl,-z,noseparate-code,-z,nosectionheader -flto -Lbuild +endif + +ifeq ($(CC), clang) + CFLAGS=-O3 -Wall -Lbuild +endif + +ifeq ($(CC), tcc) + CFLAGS=-O3 -s -Wl -flto -Lbuild +endif + +build: build_dir false intcmp scrut str strcmp true + +build_dir: + mkdir -p build + +clean: + rm -rf build/ + +install: build + mkdir -p $(PREFIX)/bin $(PREFIX)/lib $(PREFIX)/man/man1 $(PREFIX)/man/man3 + cp -f build/*.o $(PREFIX)/lib/ + cp -f build/*.so $(PREFIX)/lib/ + cp -f build/* $(PREFIX)/bin/ + cp -f docs/*.1 $(PREFIX)/man/man1/ + cp -f docs/*.3 $(PREFIX)/man/man3/ + +test: build + tests/cc-compat.sh + tests/posix-compat.sh + +false: src/false.c build_dir + $(CC) $(CFLAGS) -o build/false src/false.c + +intcmp: src/intcmp.c build_dir + $(CC) $(CFLAGS) -o build/intcmp src/intcmp.c + +scrut: src/scrut.c libfileis build_dir + $(CC) $(CFLAGS) -lfileis -o build/scrut src/scrut.c + +str: src/str.c build_dir + $(CC) $(CFLAGS) -o build/str src/str.c + +strcmp: src/strcmp.c build_dir + $(CC) $(CFLAGS) -o build/strcmp src/strcmp.c + +true: src/true.c build_dir + $(CC) $(CFLAGS) -o build/true src/true.c + +libfileis: src/libfileis.c src/libfileis.h build_dir + $(CC) $(CFLAGS) -c -fPIC -o build/libfileis.o src/libfileis.c + $(CC) -shared -o build/libfileis.so build/libfileis.o + diff --git a/Makefile b/Makefile deleted file mode 100644 index 2e01c79..0000000 --- a/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2023 Emma Tebibyte -# SPDX-License-Identifier: FSFAP -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice and this -# notice are preserved. This file is offered as-is, without any warranty. - -.POSIX: - -PREFIX=/usr/local -CFLAGS=-O3 -s -Wl,-z,noseparate-code,-z,nosectionheader -flto -Lbuild - -build: build_dir cat false tail true - -clean: build_dir - rm -rf build/ - -cat: src/cat.c build_dir lib - cc $(CFLAGS) -lyac -o build/cat src/cat.c - -false: src/false.c build_dir - cc $(CFLAGS) -o build/false src/false.c - -tail: src/tail.c build_dir lib - cc $(CFLAGS) -lyac -o build/tail src/tail.c - -true: src/true.c build_dir - cc $(CFLAGS) -o build/true src/true.c - -lib: src/yac.c src/yac.h - cc $(CFLAGS) -c -fPIC -o build/yac.o src/yac.c - cc -shared -o build/libyac.so build/yac.o - -build_dir: - mkdir -p build - -install: build - rm build/*.o - mkdir -p $(PREFIX)/lib $(PREFIX)/bin - cp -f build/*.so $(PREFIX)/lib/ - cp -f build/* $(PREFIX)/bin/ diff --git a/README b/README new file mode 100644 index 0000000..3f20417 --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +“Seek not to walk the path of the masters; seek what they sought.” +– Matsuo Basho + +The Bonsai core utilities are the result of the careful examination of the +current state of POSIX and Unix utilies. The Unix Philosophy, “do one thing and +do it well” is its core but these tools do not cling to the names of the past. + +The era of the original Unix tools has been long and fruitful, but they have +their flaws. The new, non-POSIX era of this project started with frustration +with the way certain tools work and how other projects that extend POSIX don’t +make anything better. + +This project will not follow in the footsteps of GNU; extensions of POSIX will +not be found here. GNU extensions are a gateway to the misuse of the shell. The +Bonsai core utilities will intentionally discourage use of the shell for +purposes beyond its scope. + +See docs/ for more on the specific utilities currently implemented. + +Read More + +An Introduction to the Unix Shell + + +Master Foo and the Ten Thousand Lines + + +Master Foo Discourses on the Unix-Nature + + +Shell Programming! + diff --git a/docs/false.1 b/docs/false.1 new file mode 100644 index 0000000..4f55f23 --- /dev/null +++ b/docs/false.1 @@ -0,0 +1,35 @@ +.\" Copyright (c) 2022 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH FALSE 1 + +.SH NAME + +false \(en do nothing, unsuccessfully + +.SH DESCRIPTION + +False does nothing regardless of operands or standard input. +False will always return an exit code of 1. + +.SH RATIONALE + +False exists for the construction of control flow and loops based on a failure. + +False functions as described in POSIX.1-2017. + +.SH AUTHOR + +Written by Emma Tebibyte . + +.SH COPYRIGHT + +This work is marked with CC0 1.0. To see a copy of this license, visit +. + +.SH SEE ALSO + +true(1) diff --git a/docs/intcmp.1 b/docs/intcmp.1 new file mode 100644 index 0000000..1a9a5e9 --- /dev/null +++ b/docs/intcmp.1 @@ -0,0 +1,78 @@ +.\" Copyright (c) 2023 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH intcmp 1 + +.SH NAME + +intcmp \(en compare integers + +.SH SYNOPSIS + +intcmp +.RB ( -eghl ) +.RB [ integer ] +.RB [ integer... ] + +.SH DESCRIPTION + +Intcmp compares integers. + +.SH USAGE + +The -e option permits given integers to be equal to each other. If combined +with -g or -l, only adjacent integers in the argument sequence can be equal. +.PP +The -g option permits a given integer to be greater than the following integer. +.PP +The -l option permits a given integer to be less than the following integer. +.PP +It may help to think of the -e, -g, and -l options as equivalent to the +infix algebraic “=”, “>”, and “<” operators respectively, with each option +putting its symbol between every given integer. For example, +.R intcmp -l 1 2 3 +is equivalent to evaluating "1 < 2 < 3". + +.SH DIAGNOSTICS + +Intcmp exits 0 for a valid expression and 1 for an invalid expression. +.PP +Intcmp prints a debug message and exits with the appropriate sysexits(3) error +code in the event of an error. + +.SH BUGS + +There are multiple ways to express compound comparisons; “less than or equal +to” can be -le or -el, for example. +.PP +The inequality comparison is -gl or -lg for “less than or greater than”; this +is elegant but unintuitive. +.PP +-egl, "equal to or less than or greater than", exits 0 no matter what for valid +program usage and may be abused to function as an integer validator. +Use str(1) instead. + +.SH RATIONALE + +The traditional tool for integer comparisons in POSIX and other Unix shells has +been test(1). This tool also handles string comparisons and file scrutiny. +These parts of its functionality have been broken out into multiple utilities. + +Strcmp’s functionality may be performed on a POSIX-compliant system with +test(1p). + +.SH AUTHOR + +Written by DTB . + +.SH COPYRIGHT + +Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +strcmp(1), scrut(1), str(1), test(1) diff --git a/docs/npc.1 b/docs/npc.1 new file mode 100644 index 0000000..3c459a7 --- /dev/null +++ b/docs/npc.1 @@ -0,0 +1,68 @@ +.\" Copyright (c) 2023 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH npc 1 + +.SH NAME + +npc \(en show non-printing characters + +.SH SYNOPSIS + +npc +.RB ( -eht ) + +.SH DESCRIPTION + +Npc reads from standard input and writes to standard output, replacing non- +printing characters with printable equivalents. Control characters print as a +carat ('^') followed by the character '@' through '_' corresponding to the +character replaced (e.g. control-X becomes "^X"). The delete character (0x7F) +becomes "^?". Characters with the high bit set (>127) are printed as "M-" +followed by the graphical representation for the same character without the +high bit set. +.PP +The +.B -e +option prints a currency sign ('$') before each line ending. +.PP +The +.B -t +option prints tab characters as "^I" rather than a literal horizontal tab. + +.SH DIAGNOSTICS + +Npc prints a debug message and exits with the appropriate sysexits(3) error +code in the event of an error, otherwise it exits successfully. + +.SH BUGS + +Npc operates in single-byte chunks regardless of intended encoding. + +.SH RATIONALE + +POSIX currently lacks a way to display non-printing characters in the terminal +using a standard tool. A popular extension to cat(1p), the -v option, is the +bandage solution GNU and other software suites use. + +This functionality should be a separate tool because its usefulness extends +beyond that of cat(1p). + +.SH AUTHOR + +Written by DTB . + +.SH COPYRIGHT + +Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +cat(1), cat-v(1) + +.I UNIX Style, or cat -v Considered Harmful +by Rob Pike diff --git a/docs/str.1 b/docs/str.1 new file mode 100644 index 0000000..2e8de5d --- /dev/null +++ b/docs/str.1 @@ -0,0 +1,58 @@ +.\" Copyright (c) 2023 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH STR 1 + +.SH NAME + +str \(en test the character types of string arguments + +.SH SYNOPSIS + +str +.RB [ type ] +.RB [ string... ] + +.SH DESCRIPTION + +Str tests each character in an arbitrary quantity of string arguments against +the function of the same name within ctype(3). + +.SH DIAGNOSTICS + +Str exits successfully if all tests pass and unsuccessfully if a test failed. +.PP +Str will exit unsuccessfully if a string is empty, as none of its contents +passed the test. +.PP +Str will print a message to standard error and exit unsuccessfully if used +improperly. + +.SH DEPRECATED FEATURES + +Str used to have an "isvalue" type as an extension to ctype(3). This was +removed in favor of using strcmp(1) to compare strings against the empty string +(''). + +.SH BUGS + +There's no way of knowing which argument failed the test without re-testing +arguments individually. +.PP +If a character in a string isn't valid ASCII str will exit unsuccessfully. + +.SH AUTHOR + +Written by DTB . + +.SH COPYRIGHT + +Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +ctype(3), strcmp(1), ascii(7) diff --git a/docs/strcmp.1 b/docs/strcmp.1 new file mode 100644 index 0000000..6a39a57 --- /dev/null +++ b/docs/strcmp.1 @@ -0,0 +1,63 @@ +.\" Copyright (c) 2023 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + + +.TH STRCMP 1 + +.SH NAME + +strcmp \(en compare strings + +.SH SYNOPSIS + +strcmp +.RM [ string ] +.RB [ strings... ] + +.SH DESCRIPTION + +Strcmp checks whether the given strings are the same. +Strcmp exits successfully if the strings are identical. Otherwise, strcmp exits +with the value 1 if an earlier string has a greater byte value than a later +string (e.g. +.R strcmp b a +) +and 255 if an earlier string has a lesser byte value (e.g. +.R strcmp a b +). + +.SH DIAGNOSTICS + +Strcmp will print an error message and exit unsuccessfully with a status +described in sysexits(3) if used incorrectly (given less than two operands). + +.SH UNICODE + +Strcmp will exit unsuccessfully if the given strings are not identical; +Unicode strings may need to be normalized if the intent is to check visual +similarity and not byte similarity. + +.SH RATIONALE + +The traditional tool for string comparisons in POSIX and other Unix shells has +been test(1). This tool also handles integer comparisons and file scrutiny. +These parts of its functionality have been broken out into multiple utilities. + +Strcmp’s functionality may be performed on a POSIX-compliant system with +test(1p). + +.SH AUTHOR + +Written by DTB . + +.SH COPYRIGHT + +Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +strcmp(3), intcmp(1), scrut(1), test(1) diff --git a/docs/true.1 b/docs/true.1 new file mode 100644 index 0000000..b451810 --- /dev/null +++ b/docs/true.1 @@ -0,0 +1,35 @@ +.\" Copyright (c) 2022 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH TRUE 1 + +.SH NAME + +true \(en do nothing, successfully + +.SH DESCRIPTION + +True does nothing regardless of operands or standard input. +True will always return an exit code of 0. + +.SH RATIONALE + +True exists for the construction of control flow and loops based on a success. + +True functions as described in POSIX.1-2017. + +.SH AUTHOR + +Written by Emma Tebibyte . + +.SH COPYRIGHT + +This work is marked with CC0 1.0. To see a copy of this license, visit +. + +.SH SEE ALSO + +false(1) diff --git a/src/cat.c b/src/cat.c deleted file mode 100644 index 34eb43f..0000000 --- a/src/cat.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include -#include -#include -#include -#include -#include - -#include "yac.h" - -void cat(FILE *file, bool u) { - int byte = 0; /* variable for storing bytes as they are read */ - int p = 0; /* index counter for bytes in buffered reading */ - char buf[4096]; /* buffer for buffered reading */ - - if (u) { - while ((byte = fgetc(file)) != EOF) { putchar(byte); } - } else { - while ((byte = fgetc(file)) != EOF) { - if (p > sizeof(buf) - 1) { - fputs(buf, stdout); - p = 0; - } else { - buf[p] = byte; - p++; - } - } - - fwrite(buf, 1, p, stdout); - fflush(stdout); - } -} - -int main(int argc, char *argv[]) { - bool u = false; - int opt; - int i; - - extern int optind; - while ((opt = getopt(argc, argv, "u")) != -1) { - switch (opt) { - /* - * From cat(1p): - * - * -u Write bytes from the input file to the standard output - * without delay as each is read. - */ - case 'u': - u = true; - break; - default: - fprintf(stderr, "Usage: %s (-u) file...\n", argv[0]); - return EX_USAGE; - } - } - - /* - * From cat(1p): - * - * file A pathname of an input file. If no file operands are - * specified, the standard input shall be used. If a file is - * '-', the cat utility shall read from the standard input at - * that point in the sequence. The cat utility shall not close - * and reopen standard input when it is referenced in this way, - * but shall accept multiple occurrences of '-' as a file - * operand. - */ - - if (optind == argc) { - cat(stdin, u); - return 0; - } - - FILE *file; - - for (i = optind; i < argc; i++) { - file = rpath(argv[0], argv[i]); - if (file != NULL) { - cat(file, u); - if (file != stdin) { fclose(file); } - } else { continue; } - } - - return EX_OK; -} diff --git a/src/false.c b/src/false.c index 1890c85..3b6ec2a 100644 --- a/src/false.c +++ b/src/false.c @@ -1,21 +1,9 @@ /* * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: CC0 * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. + * This work is marked with CC0 1.0. To view a copy of this license, visit + * . */ int main() { return 1; } diff --git a/src/intcmp.c b/src/intcmp.c new file mode 100644 index 0000000..fc7e0e9 --- /dev/null +++ b/src/intcmp.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include /* errno */ +#include /* fprintf(3), stderr */ +#include /* strtol(3), size_t */ +#ifndef EX_USAGE +# include /* EX_USAGE */ +#endif +#include /* getopt(3), optind */ + +/* 0b00? */ /* Equal | -e | 0b001 | 1 */ +#define EQUAL 0x01 /* Greater | -g | 0b010 | 2 */ +/* 0b0?0 */ /* Greater or Equal | -ge | 0b011 | 3 */ +#define GREATER 0x02 /* Less | -l | 0b100 | 4 */ +/* 0b?00 */ /* Less or Equal | -le | 0b101 | 5 */ +#define LESS 0x04 /* Inequal (Greater or Less) | -gl | 0b110 | 6 */ + +static char *program_name = "intcmp"; + +int main(int argc, char *argv[]){ + int c; + size_t i; + unsigned char mode; + int r; /* reference integer */ + + mode = 0; + + if(argc < 3) + goto usage; + + while((c = getopt(argc, argv, "egl")) != -1) + switch(c){ + case 'e': mode |= EQUAL; break; + case 'g': mode |= GREATER; break; + case 'l': mode |= LESS; break; + default: goto usage; + } + + if(optind + 2 /* ref cmp */ > argc){ +usage: fprintf(stderr, + "Usage: %s (-eghl) [integer] [integer...]\n", + argv[0] == NULL ? program_name : argv[0]); + return EX_USAGE; + } + + i = optind; + + do{ r = c; + c = strtol(argv[i], &argv[i], 10); + if(*argv[i] != '\0' || errno != 0){ + fprintf(stderr, "%s: argument #%d: Invalid integer\n", + argv[0], (int)i); + return EX_USAGE; + } + + if(i == optind) + continue; + + /* rule enforcement; if a mode isn't permitted and the numbers + * correspond to it, return 1 */ + if( (!(mode & EQUAL) && r == c) + || (!(mode & GREATER) && r > c) + || (!(mode & LESS) && r < c)) + return 1; + }while(++i < argc); + + return 0; +} diff --git a/src/libfileis.c b/src/libfileis.c new file mode 100644 index 0000000..fe68730 --- /dev/null +++ b/src/libfileis.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include "libfileis.h" + +#include /* access(3), F_OK, R_OK, W_OK, X_OK */ + +int f_executable(char *path){ return access(path, X_OK) == 0; } /* test -x */ +int f_exists(char *path){ return access(path, F_OK) == 0; } /* test -e */ +int f_readable(char *path){ return access(path, R_OK) == 0; } /* test -r */ +int f_writeable(char *path){ return access(path, W_OK) == 0; } /* test -w */ + +#include /* lstat(3), struct stat, S_ISBLK, S_ISCHR, S_ISDIR, + * S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK, + * S_ISUID, S_ISVTX */ + +static struct stat buf; + +int f_blockspecial(char *path){ /* test -b */ + return lstat(path, &buf) != -1 && S_ISBLK(buf.st_mode); +} +int f_charspecial(char *path){ /* test -c */ + return lstat(path, &buf) != -1 && S_ISCHR(buf.st_mode); +} +int f_directory(char *path){ /* test -d */ + return lstat(path, &buf) != -1 && S_ISDIR(buf.st_mode); +} +int f_fifospecial(char *path){ /* test -p */ + return lstat(path, &buf) != -1 && S_ISFIFO(buf.st_mode); +} +int f_gid(char *path){ /* test -g */ + return lstat(path, &buf) != -1 && (buf.st_mode & S_ISGID); +} +int f_regular(char *path){ /* test -f */ + return lstat(path, &buf) != -1 && S_ISREG(buf.st_mode); +} +int f_socket(char *path){ /* test -S */ + return lstat(path, &buf) != -1 && S_ISSOCK(buf.st_mode); +} +int f_sticky(char *path){ /* test -k */ + return lstat(path, &buf) != -1 && (buf.st_mode & S_ISVTX); +} +int f_symlink(char *path){ /* test -h; test -L */ + return lstat(path, &buf) != -1 && S_ISLNK(buf.st_mode); +} +int f_uid(char *path){ /* test -u */ + return lstat(path, &buf) != -1 && (buf.st_mode & S_ISUID); +} diff --git a/src/libfileis.h b/src/libfileis.h new file mode 100644 index 0000000..0b77cc7 --- /dev/null +++ b/src/libfileis.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +/* libfileis functions return true if the condition is true and false if the + * condition is false. Returned values are 0 or 1 only. */ + +/* True if file exists and is a block special file. */ + int f_blockspecial(char *path); + +/* True if file exists and is a character special file. */ + int f_charspecial(char *path); + +/* True if file exists and is a directory. */ + int f_directory(char *path); + +/* True if file exists and is executable. */ + int f_executable(char *path); + +/* True if file exists (regardless of type). */ + int f_exists(char *path); + +/* True if file exists and is a named pipe (FIFO). */ + int f_fifospecial(char *path); + +/* True if file exists and its set group ID flag is set. */ + int f_gid(char *path); + +/* True if file exists and is readable. */ + int f_readable(char *path); + +/* True if file exists and is a regular file. */ + int f_regular(char *path); + +/* True if file exists and is a socket. */ + int f_socket(char *path); + +/* True if file exists and its sticky bit is set. */ + int f_sticky(char *path); + +/* True if file exists and is a symbolic link. */ + int f_symlink(char *path); + +/* True if file exists and its set user ID flag is set. */ + int f_uid(char *path); + +/* True if file exists and is writeable. */ + int f_writeable(char *path); diff --git a/src/npc.c b/src/npc.c new file mode 100644 index 0000000..c927d28 --- /dev/null +++ b/src/npc.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include /* fprintf(3), fputs(3), getc(3), putc(3), stdin, stdout, + * EOF */ +#include /* getopt(3) */ +#if !defined EX_USAGE || !defined EX_OK +# include +#endif + +int main(int argc, char *argv[]){ + int c; + char showend; + char showtab; + + showend = 0; + showtab = 0; + + if(argc > 0) + while((c = getopt(argc, argv, "et")) != -1) + switch(c){ + case 'e': showend = 1; break; + case 't': showtab = 1; break; + default: goto usage; + } + + if(argc > optind){ +usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]); + return EX_USAGE; + } + + while((c = getc(stdin)) != EOF){ + if((c & 0x80) != 0) + fputs("M-", stdout); + switch(c ^ 0x80 /* 0b 1000 0000 */){ + case 0x7f: fputs("^?", stdout); + break; + case '\n': if(showend) + putc('$', stdout); + default: + if(c >= ' ' || c == '\n' || (!showtab && c == '\t')) + putc(c, stdout); + else + fprintf(stdout, "^%c", c + '@'); + } + } + + return EX_OK; +} diff --git a/src/scrut.c b/src/scrut.c new file mode 100644 index 0000000..4cff59d --- /dev/null +++ b/src/scrut.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include /* fprintf(3), stderr, NULL */ +#include /* strchr(3) */ +#ifndef EX_USAGE +# include +#endif +#include /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */ +#include /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR, + * S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK, + * S_ISUID, S_ISVTX */ + +static char args[] = "bcdefghkprsuwxLS"; +static char ops[(sizeof args) / (sizeof *args)]; +static char *program_name = "scrut"; + +int main(int argc, char *argv[]){ + struct stat buf; + int c; + size_t i; + + if(argc < 2) + goto usage; + + i = 0; + while((c = getopt(argc, argv, args)) != -1) + if(strchr(args, c) == NULL) + goto usage; + else + ops[i++] = c; + ops[i] = '\0'; + + if(optind == argc) + goto usage; + + argv += optind; + do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) + return 1; /* doesn't exist or isn't stattable */ + + for(i = 0; ops[i] != '\0'; ++i) + if(ops[i] == 'e') + continue; + else if(ops[i] == 'h'){ +usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n", + argv[0] == NULL + ? program_name + : argv[0], + args); + + return EX_USAGE; + }else if( + (ops[i] == 'b' + && !S_ISBLK(buf.st_mode)) + || (ops[i] == 'c' + && !S_ISCHR(buf.st_mode)) + || (ops[i] == 'd' + && !S_ISDIR(buf.st_mode)) + || (ops[i] == 'f' + && !S_ISREG(buf.st_mode)) + || (ops[i] == 'g' + && !(buf.st_mode & S_ISGID)) + || (ops[i] == 'k' + && !(buf.st_mode & S_ISVTX)) + || (ops[i] == 'p' + && !S_ISFIFO(buf.st_mode)) + || (ops[i] == 'r' + && access(*argv, R_OK) != 0) + || (ops[i] == 'u' + && !(buf.st_mode & S_ISUID)) + || (ops[i] == 'w' + && access(*argv, W_OK) != 0) + || (ops[i] == 'x' + && access(*argv, X_OK) != 0) + || (ops[i] == 'L' + && !S_ISLNK(buf.st_mode)) + || (ops[i] == 'S' + && !S_ISSOCK(buf.st_mode))) + return 1; + }while(*++argv != NULL); + + return 0; +} diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..ea0aa3c --- /dev/null +++ b/src/str.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include +#include /* NULL */ +#include /* fprintf(3) */ +#include /* strcmp(3) */ +#if !defined EX_USAGE +# include +#endif + +static char *program_name = "str"; + +static struct { + char *name; + int (*f)(int); +}ctypes[] = { + { "isalnum", isalnum }, + { "isalpha", isalpha }, + { "isblank", isblank }, + { "iscntrl", iscntrl }, + { "isdigit", isdigit }, + { "isxdigit", isxdigit }, + { "isgraph", isgraph }, + { "islower", islower }, + { "isprint", isprint }, + { "ispunct", ispunct }, + { "isspace", isspace }, + { "isupper", isupper } +}; + +int main(int argc, char *argv[]){ + int ctype; + int i; + int r; + + if(argc >= 3){ + for(ctype = 0; ctype < (sizeof ctypes) / (sizeof *ctypes); + ++ctype) + if(strcmp(argv[1], ctypes[ctype].name) == 0) + goto pass; + } + + fprintf(stderr, "Usage: %s [type] [string...]\n", + argv[0] == NULL ? program_name : argv[0]); + + return EX_USAGE; + +pass: for(argv += 2, r = 1; *argv != NULL; ++argv) + for(i = 0; argv[0][i] != '\0'; ++i) + /* First checks if argv[0][i] is valid ASCII; ctypes(3) + * don't handle non-ASCII. + * This is bad. */ + if(argv[0][i] < 0x80 && !ctypes[ctype].f(argv[0][i])) + return 1; + else + r = 0; + + return r; +} diff --git a/src/strcmp.c b/src/strcmp.c new file mode 100644 index 0000000..d2fbc2b --- /dev/null +++ b/src/strcmp.c @@ -0,0 +1,25 @@ +#include /* fprintf(3), stderr */ +#ifndef EX_USAGE +# include /* EX_USAGE */ +#endif + +static char *program_name = "strcmp"; + +int main(int argc, char *argv[]){ + int i; + + if(argc < 3){ + fprintf(stderr, "Usage: %s [string] [string...]\n", + argv[0] == NULL ? program_name : argv[0]); + return EX_USAGE; + } + + for(; *argv[1] != '\0'; ++argv[1]) + for(i = 2; i < argc; ++i) + if(*argv[i-1] > *argv[i]) + return 1; + else if(*argv[i-1] < *argv[i]++) + return -1; /* actually 255 */ + + return 0; +} diff --git a/src/tail.c b/src/tail.c deleted file mode 100644 index 9ba7dfe..0000000 --- a/src/tail.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * Copyright (c) 2023 Marceline Cramer - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "yac.h" - -typedef struct string_t { - char *mem; - int len; - int capacity; -} string_t; - -string_t *string_new() { - string_t *string = calloc(1, sizeof(string_t)); - string->capacity = 1024; - string->len = 0; - string->mem = calloc(string->capacity, sizeof(char)); - return string; -} - -void string_putc(string_t *string, char c) { - string->mem[string->len] = c; - string->len++; - if (string->len >= string->capacity) { - string->capacity *= 2; - string->mem = realloc(string->mem, string->capacity * sizeof(char)); - } -} - -void tailc(FILE *f, long num) {} - -void tailn(FILE *f, long num) { - string_t *lines[num]; - int cursor = 0; - int looped = 0; - - lines[cursor] = string_new(); - - int c = fgetc(f); - for (;;) { - if (c == EOF) { - string_putc(lines[cursor], '\0'); - break; - } - - string_putc(lines[cursor], c); - - if (c == '\n') { - string_putc(lines[cursor], '\0'); - - if ((c = fgetc(f)) == EOF) { - break; - } - - if (++cursor >= num) { - looped = 1; - cursor = 0; - } - - lines[cursor] = string_new(); - } else { - c = fgetc(f); - } - } - - int read = looped ? cursor + 1 : 0; - do { - if (read >= num) { - read = 0; - } - - fputs(lines[read]->mem, stdout); - } while (read++ != cursor); -} - -void tailf(FILE *file) { - int byte; - - while(true) { - if ((byte = fgetc(file)) != EOF) { putchar(byte); } - } -} - -int main(int argc, char *argv[]) { - bool c = false; - bool f = false; - bool n = false; - int i; - int opt; - long num; - void (*fn)(FILE *, long) = tailn; - - extern int optind; - while ((opt = getopt(argc, argv, "c:fn:")) != -1) { - switch (opt) { - /* - * From tail(1p): - * - * -c number The application shall ensure that the number option-argument - * is a decimal integer, optionally including a sign. The sign - * shall affect the location in the file, measured in bytes, to - * begin the copying: - * - * ┌─────┬────────────────────────────────────────┐ - * │Sign │ Copying Starts │ - * ├─────┼────────────────────────────────────────┤ - * │ + │ Relative to the beginning of the file. │ - * │ - │ Relative to the end of the file. │ - * │none │ Relative to the end of the file. │ - * └─────┴────────────────────────────────────────┘ - * The application shall ensure that if the sign of the number - * option-argument is '+', the number option-argument is a non- - * zero decimal integer. - * - * The origin for counting shall be 1; that is, -c +1 represents - * the first byte of the file, -c -1 the last. - * - * -f If the input file is a regular file or if the file operand - * specifies a FIFO, do not terminate after the last line of the - * input file has been copied, but read and copy further bytes - * from the input file when they become available. If no file - * operand is specified and standard input is a pipe or FIFO, - * the -f option shall be ignored. If the input file is not a - * FIFO, pipe, or regular file, it is unspecified whether or not - * the -f option shall be ignored. - * - * -n number This option shall be equivalent to -c number, except the - * starting location in the file shall be measured in lines - * instead of bytes. The origin for counting shall be 1; that - * is, -n +1 represents the first line of the file, -n -1 the - * last. - * - * If neither -c nor -n is specified, -n 10 shall be assumed. - */ - case 'c': - c = true; - fn = tailc; - num = strtol(optarg, NULL, 10); - break; - case 'f': - f = true; - case 'n': - n = true; - num = strtol(optarg, NULL, 10); - break; - default: - fprintf( - stderr, - "Usage: %s (-f) [-c characters] [-n lines] file...\n", - argv[0] - ); - return EX_USAGE; - } - } - - if (!n && !c) { - num = 10; - } else if (n && c) { - fprintf( - stderr, - "Usage: %s (-f) [-c characters] [-n lines] file...\n", - argv[0] - ); - return EX_USAGE; - } - - FILE *file; - - if (optind == argc) { - fn(stdin, num); - - if (f) { tailf(stdin); } - } else { - for (i = optind; i < argc; i++) { - if ((file = rpath(argv[0], argv[i])) != NULL) { - fn(file, num); - } - - if (f) { tailf(file); } - fclose(file); - } - } - return EX_OK; -} diff --git a/src/true.c b/src/true.c index c012c36..ab8da96 100644 --- a/src/true.c +++ b/src/true.c @@ -1,21 +1,9 @@ /* * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: CC0 * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. + * This work is marked with CC0 1.0. To view a copy of this license, visit + * . */ int main() {} diff --git a/src/yac.c b/src/yac.c deleted file mode 100644 index dd1f7c4..0000000 --- a/src/yac.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include -#include -#include -#include -#include - -/* Resolve a file from a path */ -FILE *rpath(char *argv0, char *path) { - struct stat stats; - FILE *file; - - if (path[0] == '-') { - switch (path[1]) { - case '\0': - file = stdin; - break; - default: - file = NULL; - break; - } - } else if (stat(path, &stats) == 0 && S_ISDIR(stats.st_mode)) { - fprintf(stderr, "%s: %s: Is a directory.\n", argv0, path); - exit(EX_NOINPUT); - } else if ((file = fopen(path, "r")) == NULL) { - switch (errno) { - case EACCES: - fprintf(stderr, "%s: %s: Permission denied.\n", argv0, path); - exit(EX_NOINPUT); - case EISDIR: - fprintf(stderr, "%s: %s: Is a directory.\n", argv0, path); - exit(EX_NOINPUT); - case ELOOP: - fprintf( - stderr, - "%s: %s: Is a symbolic link loop.\n", - argv0, - path - ); - exit(EX_UNAVAILABLE); - case EMFILE: - fprintf(stderr, "%s: Internal error.\n", argv0); - exit(EX_SOFTWARE); - case ENOENT: case ENOTDIR: case ENXIO: - fprintf( - stderr, - "%s: %s: No such path or directory.\n", - argv0, - path - ); - exit(EX_NOINPUT); - default: - fprintf(stderr, "%s: Unknown error.\n", argv0); - exit(EX_UNAVAILABLE); - } - } - return file; -} diff --git a/src/yac.h b/src/yac.h deleted file mode 100644 index 4729708..0000000 --- a/src/yac.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This file is part of YAC coreutils. - * - * YAC coreutils is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#ifndef YAC_H -#define YAC_H - -FILE *rpath(char *argv0, char *path); - -#endif diff --git a/tests/cc-compat.sh b/tests/cc-compat.sh new file mode 100755 index 0000000..0fe28d8 --- /dev/null +++ b/tests/cc-compat.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# Copyright (c) 2023 Emma Tebibyte +# SPDX-License-Identifier: FSFAP +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice and this +# notice are preserved. This file is offered as-is, without any warranty. + +set -e + +if ! ls GNUmakefile >/dev/null 2>&1 +then + printf '%s: Run this script in the root of the project.\n' "$0" 1>&2 + exit 1 +fi + +make clean + +for CC in cc\ + clang \ + gcc \ + tcc \ + 'zig cc' +do + export CC + printf '%s: %s: Testing build.\n' "$0" "$CC" + + make CC="$CC" && printf '%s: Build successful.\n' "$0" + + ls -lA build/ + + make clean + printf '\n' +done diff --git a/tests/posix-compat.sh b/tests/posix-compat.sh new file mode 100755 index 0000000..2378a01 --- /dev/null +++ b/tests/posix-compat.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# Copyright (c) 2023 Emma Tebibyte +# SPDX-License-Identifier: FSFAP +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice and this +# notice are preserved. This file is offered as-is, without any warranty. + +set -e + +if ! ls GNUmakefile >/dev/null 2>&1 +then + printf '%s: Run this script in the root of the project.\n' "$0" 1>&2 + exit 1 +fi + +printf "Starting POSIX compatibility testing.\n" + +for utility in tests/posix/*; do + printf '%s: %s: Testing utility.\n' "$0" "$utility" + "$utility" + printf '\n' +done diff --git a/tests/posix/test b/tests/posix/test new file mode 100755 index 0000000..e1a44bb --- /dev/null +++ b/tests/posix/test @@ -0,0 +1,19 @@ +#!/bin/sh + +# Copyright (c) 2023 DTB +# SPDX-License-Identifier: FSFAP +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice and this +# notice are preserved. This file is offered as-is, without any warranty. + +set -e + +i=1 +while test -e "$i".test; do + printf '%s: %s: Running test...\n' "$0" "$i".test + ./"$i".test >"$i".result + diff 1.expected 1.result + printf '%s: %s: Test passed.\n' "$0" "$i".test + i="$(printf '%s + %s\n' 1 "$i" | bc)" +done