ident: Clean up

* Use libcasper instead of reinventing it.

* Add long option support.

* Drop pointless use of temporary locales.

* Consistently check for stdio errors.

* Clean up the code.

* Clean up and expand the tests.

MFC after:	1 week
Reviewed by:	ngie
Differential Revision:	https://reviews.freebsd.org/D56505
This commit is contained in:
Dag-Erling Smørgrav
2026-04-22 10:19:35 +02:00
parent 3d00db6b8b
commit 394f6b1b0a
4 changed files with 172 additions and 85 deletions
+6
View File
@@ -2,6 +2,12 @@
PROG= ident
.if ${MK_CASPER} != "no"
LIBADD+= casper
LIBADD+= cap_fileargs
CFLAGS+= -DWITH_CASPER
.endif
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
+7 -4
View File
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd July 25, 2015
.Dd April 18, 2026
.Dt IDENT 1
.Os
.Sh NAME
@@ -52,15 +52,18 @@ and a space.
.Pp
These options are supported:
.Bl -tag -width "XXX"
.It Fl q
.It Fl q , Fl -quiet
Quiet mode: suppress warnings if no pattern found.
.It Fl V
.It Fl V , Fl -version
Do nothing, added for compatibility with GNU ident.
.El
.Sh EXIT STATUS
.Ex -std ident
.Sh AUTHORS
.An -nosplit
This version of the
.Nm
utility was written by
.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org .
.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org
and
.An Dag-Erling Sm\(/orrgav Aq Mt des@FreeBSD.org .
+75 -71
View File
@@ -1,4 +1,5 @@
/*-
* Copyright (c) 2026 Dag-Erling Smørgrav <des@FreeBSD.org>
* Copyright (c) 2015-2021 Baptiste Daroussin <bapt@FreeBSD.org>
* Copyright (c) 2015 Xin LI <delphij@FreeBSD.org>
*
@@ -24,20 +25,24 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/capsicum.h>
#include <sys/types.h>
#include <sys/capsicum.h>
#include <sys/sbuf.h>
#include <capsicum_helpers.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xlocale.h>
#include <libcasper.h>
#include <casper/cap_fileargs.h>
typedef enum {
/* state condition to transit to next state */
@@ -56,20 +61,20 @@ scan(FILE *fp, const char *name, bool quiet)
bool hasid = false;
bool subversion = false;
analyzer_states state = INIT;
FILE* buffp;
FILE *buffp;
char *buf;
size_t sz;
locale_t l;
l = newlocale(LC_ALL_MASK, "C", NULL);
sz = 0;
buf = NULL;
buffp = open_memstream(&buf, &sz);
if (buffp == NULL)
err(EXIT_FAILURE, "open_memstream()");
if ((buffp = open_memstream(&buf, &sz)) == NULL)
goto bufferr;
if (name != NULL)
if (name != NULL) {
printf("%s:\n", name);
if (fflush(stdout) == EOF)
err(EXIT_FAILURE, "stdout");
}
while ((c = fgetc(fp)) != EOF) {
switch (state) {
@@ -83,13 +88,14 @@ scan(FILE *fp, const char *name, bool quiet)
}
break;
case DELIM_SEEN:
if (isalpha_l(c, l)) {
if (isalpha(c)) {
/* Transit to KEYWORD if we see letter */
if (buf != NULL)
memset(buf, 0, sz);
rewind(buffp);
fputc('$', buffp);
fputc(c, buffp);
if (fseek(buffp, 0, SEEK_SET) != 0 ||
fputc('$', buffp) == EOF ||
fputc(c, buffp) == EOF)
goto bufferr;
state = KEYWORD;
continue;
@@ -102,9 +108,10 @@ scan(FILE *fp, const char *name, bool quiet)
}
break;
case KEYWORD:
fputc(c, buffp);
if (fputc(c, buffp) == EOF)
goto bufferr;
if (isalpha_l(c, l)) {
if (isalpha(c)) {
/*
* Stay in KEYWORD if additional letter is seen
*/
@@ -132,7 +139,8 @@ scan(FILE *fp, const char *name, bool quiet)
break;
case PUNC_SEEN:
case PUNC_SEEN_SVN:
fputc(c, buffp);
if (fputc(c, buffp) == EOF)
goto bufferr;
switch (c) {
case ':':
@@ -166,13 +174,15 @@ scan(FILE *fp, const char *name, bool quiet)
}
break;
case TEXT:
fputc(c, buffp);
if (fputc(c, buffp) == EOF)
goto bufferr;
if (iscntrl_l(c, l)) {
if (iscntrl(c)) {
/* Control characters are not allowed in this state */
state = INIT;
} else if (c == '$') {
fflush(buffp);
if (fflush(buffp) == EOF)
goto bufferr;
/*
* valid ident should end with a space.
*
@@ -182,9 +192,11 @@ scan(FILE *fp, const char *name, bool quiet)
* subversion mode. No length check is enforced
* because GNU RCS ident(1) does not do it either.
*/
c = buf[strlen(buf) -2 ];
c = buf[strlen(buf) - 2];
if (c == ' ' || (subversion && c == '#')) {
printf(" %s\n", buf);
if (fflush(stdout) == EOF)
err(EXIT_FAILURE, "stdout");
hasid = true;
}
state = INIT;
@@ -193,32 +205,49 @@ scan(FILE *fp, const char *name, bool quiet)
break;
}
}
fclose(buffp);
if (fclose(buffp) == EOF)
goto bufferr;
free(buf);
freelocale(l);
if (!hasid) {
if (!quiet)
if (!quiet) {
fprintf(stderr, "%s warning: no id keywords in %s\n",
getprogname(), name ? name : "standard input");
}
return (EXIT_FAILURE);
}
return (EXIT_SUCCESS);
bufferr:
err(EXIT_FAILURE, "buffer");
}
static void
usage(void)
{
fprintf(stderr, "usage: %s [-q] [-V] [file...]", getprogname());
exit(EXIT_FAILURE);
}
static struct option longopts[] = {
{ "quiet", no_argument, NULL, 'q' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
int
main(int argc, char **argv)
{
fileargs_t *fa;
cap_rights_t rights;
bool quiet = false;
int ch, i, *fds, fd;
int ret = EXIT_SUCCESS;
size_t nfds;
int i, opt, ret;
FILE *fp;
while ((ch = getopt(argc, argv, "qV")) != -1) {
switch (ch) {
setlocale(LC_CTYPE, "C");
while ((opt = getopt_long(argc, argv, "+qV", longopts, NULL)) != -1) {
switch (opt) {
case 'q':
quiet = true;
break;
@@ -226,61 +255,36 @@ main(int argc, char **argv)
/* Do nothing, compat with GNU rcs's ident */
return (EXIT_SUCCESS);
default:
errx(EXIT_FAILURE, "usage: %s [-q] [-V] [file...]",
getprogname());
usage();
}
}
argc -= optind;
argv += optind;
if (caph_limit_stdio() < 0)
err(EXIT_FAILURE, "unable to limit stdio");
cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_FCNTL);
fa = fileargs_init(argc, argv, O_RDONLY, 0, &rights, FA_OPEN);
if (fa == NULL)
err(EXIT_FAILURE, "Unable to initialize casper");
caph_cache_catpages();
if (caph_limit_stdio() != 0)
err(EXIT_FAILURE, "Unable to limit stdio");
if (caph_enter_casper() != 0)
err(EXIT_FAILURE, "Unable to enter capability mode");
if (argc == 0) {
nfds = 1;
fds = malloc(sizeof(*fds));
if (fds == NULL)
err(EXIT_FAILURE, "unable to allocate fds array");
fds[0] = STDIN_FILENO;
ret = scan(stdin, NULL, quiet);
} else {
nfds = argc;
fds = malloc(sizeof(*fds) * nfds);
if (fds == NULL)
err(EXIT_FAILURE, "unable to allocate fds array");
ret = EXIT_SUCCESS;
for (i = 0; i < argc; i++) {
fds[i] = fd = open(argv[i], O_RDONLY);
if (fd < 0) {
warn("%s", argv[i]);
if ((fp = fileargs_fopen(fa, argv[i], "r")) == NULL)
err(EXIT_FAILURE, "%s", argv[i]);
if (scan(fp, argv[i], quiet) != EXIT_SUCCESS)
ret = EXIT_FAILURE;
continue;
}
if (caph_limit_stream(fd, CAPH_READ) < 0)
err(EXIT_FAILURE,
"unable to limit fcntls/rights for %s",
argv[i]);
(void)fclose(fp);
}
}
/* Enter Capsicum sandbox. */
if (caph_enter() < 0)
err(EXIT_FAILURE, "unable to enter capability mode");
for (i = 0; i < (int)nfds; i++) {
if (fds[i] < 0)
continue;
fp = fdopen(fds[i], "r");
if (fp == NULL) {
warn("%s", argv[i]);
ret = EXIT_FAILURE;
continue;
}
if (scan(fp, argc == 0 ? NULL : argv[i], quiet) != EXIT_SUCCESS)
ret = EXIT_FAILURE;
fclose(fp);
}
fileargs_free(fa);
return (ret);
}
+84 -10
View File
@@ -1,15 +1,89 @@
#
# Copyright (c) 2026 Dag-Erling Smørgrav <des@FreeBSD.org>
#
# SPDX-License-Identifier: BSD-2-Clause
#
atf_test_case ident
ident_body() {
atf_check -o file:$(atf_get_srcdir)/test.out \
ident < $(atf_get_srcdir)/test.in
atf_check -o match:'Foo.*' -s exit:1 \
-e inline:"ident warning: no id keywords in $(atf_get_srcdir)/testnoid\n" \
ident $(atf_get_srcdir)/test.in $(atf_get_srcdir)/testnoid
atf_check -o match:'Foo.*' -s exit:1 \
ident -q $(atf_get_srcdir)/test.in $(atf_get_srcdir)/testnoid
atf_test_case stdin
stdin_head()
{
atf_set "descr" "Read from stdin"
}
stdin_body()
{
local dir=$(atf_get_srcdir)
atf_check -o file:"${dir}/test.out" \
ident < "${dir}/test.in"
}
atf_test_case file
file_head()
{
atf_set "descr" "Read from a file"
}
file_body()
{
local dir=$(atf_get_srcdir)
echo "${dir}/test.in:" >out
cat "${dir}/test.out" >>out
atf_check -o file:out ident "${dir}/test.in"
}
atf_test_case noid
noid_head()
{
atf_set "descr" "No id keywords in input"
}
noid_body()
{
local dir=$(atf_get_srcdir)
atf_check \
-s exit:1 \
-o inline:"${dir}/testnoid:\n" \
-e inline:"ident warning: no id keywords in ${dir}/testnoid\n" \
ident "${dir}/testnoid"
}
atf_test_case multi
multi_head()
{
atf_set "descr" "Multiple inputs"
}
multi_body()
{
local dir=$(atf_get_srcdir)
echo "${dir}/test.in:" >out
cat "${dir}/test.out" >>out
echo "${dir}/testnoid:" >>out
atf_check \
-s exit:1 \
-o file:out \
-e inline:"ident warning: no id keywords in ${dir}/testnoid\n" \
ident "${file}"
}
atf_test_case stdout
stdout_head()
{
atf_set "descr" "Failure to write to stdout"
}
stdout_body()
{
local dir=$(atf_get_srcdir)
(
trap "" PIPE
sleep 1
ident "${dir}"/test.in 2>stderr
echo $? >result
) | true
atf_check -o inline:"1\n" cat result
atf_check -o match:"stdout" cat stderr
}
atf_init_test_cases()
{
atf_add_test_case ident
atf_add_test_case stdin
atf_add_test_case file
atf_add_test_case noid
atf_add_test_case stdout
}