aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2021-02-16 13:36:19 +0200
committerSergey Poznyakoff <gray@gnu.org>2021-02-16 13:41:49 +0200
commitb16803d6a9e7e403c4d92350fa16464d348509dc (patch)
tree53665db5171ac41b164431b219d7d373398e6161
parent78a90ad8afc3dedb1ad8d721875e770221c61c56 (diff)
downloadrush-b16803d6a9e7e403c4d92350fa16464d348509dc.tar.gz
rush-b16803d6a9e7e403c4d92350fa16464d348509dc.tar.bz2
Implement file type and ownership tests
* NEWS: Document changes. * configure.ac: Version 2.1.90. Raise autoconf and automake requirements. * doc/rush.texi: Document changes. * src/cfgram.y: New rule for file system tests. * src/cflex.l: New rule for the TEST token. * src/rush.c (groupmember): Change type of the first argument. (rush_access,eval_fstest): New functions. (test_eval): Process the test_fstest node type. * src/rush.h (test_fstest): New node type. (fstest_op): New enum. (test_node): New member: fstest * tests/.gitignore: Add mksock * tests/mksock.c: New file. * tests/fstest.at: New test. * tests/Makefile.am: Add new test. * tests/testsuite.at: Likewise.
-rw-r--r--NEWS15
-rw-r--r--configure.ac6
-rw-r--r--doc/rush.texi87
-rw-r--r--src/cfgram.y8
-rw-r--r--src/cflex.l2
-rw-r--r--src/rush.c100
-rw-r--r--src/rush.h27
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/fstest.at216
-rw-r--r--tests/mksock.c29
-rw-r--r--tests/testsuite.at3
12 files changed, 489 insertions, 9 deletions
diff --git a/NEWS b/NEWS
index a23968d..9ab1e1d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,8 +1,21 @@
-GNU Rush NEWS -- history of user-visible changes. 2019-07-12
+GNU Rush NEWS -- history of user-visible changes. 2021-02-16
See the end of file for copying conditions.
Please send bug reports to <bug-rush@gnu.org.ua>
+Version 2.1 .90 (git)
+
+* File system tests
+
+New predicates, designed after options to the "test" system command,
+check for file types. modes and ownership. For example
+
+ rule
+ match -d /var/lock/sd
+
+will match if /var/lock/sd exists on diks and is a directory
+
+
Version 2.1, 2019-07-12
* Include updated wordsplit version (v1.0-7-g6ccb9ad)
diff --git a/configure.ac b/configure.ac
index 212c921..ee6adcf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -14,12 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with GNU Rush. If not, see <http://www.gnu.org/licenses/>.
-AC_PREREQ(2.63)
-AC_INIT([GNU rush], [2.1], [bug-rush@gnu.org])
+AC_PREREQ(2.64)
+AC_INIT([GNU rush], [2.1.90], [bug-rush@gnu.org])
AC_CONFIG_SRCDIR([src/rush.c])
AC_CONFIG_HEADER([config.h])
AC_CONFIG_AUX_DIR([build-aux])
-AM_INIT_AUTOMAKE([1.11.1 gnits tar-ustar dist-bzip2 dist-xz std-options])
+AM_INIT_AUTOMAKE([1.15 gnits tar-ustar dist-bzip2 dist-xz std-options])
# Enable silent rules by default:
AM_SILENT_RULES([yes])
diff --git a/doc/rush.texi b/doc/rush.texi
index 9116c8c..04c6557 100644
--- a/doc/rush.texi
+++ b/doc/rush.texi
@@ -142,6 +142,7 @@ Matching Conditions
* Comparisons::
* Membership operators::
+* File system tests::
* Boolean expressions::
Modifying variables
@@ -1098,6 +1099,7 @@ A @dfn{simple expression} is either a comparison or membership test.
@menu
* Comparisons::
* Membership operators::
+* File system tests::
* Boolean expressions::
@end menu
@@ -1223,6 +1225,91 @@ in whitespace delimited @var{list}. Members of @var{list} are group
names or GIDs.
@end table
+@node File system tests
+@subsubsection File system tests
+@cindex file type, checking
+@cindex checking file type
+@cindex file ownership, checking
+@cindex checking file ownership
+ File system tests check file types and ownership. They are similar
+to options to @command{test} shell command:
+
+@table @code
+@cindex -b, file system test
+@item -b @var{file}
+@var{file} exists and is block special
+
+@cindex -c, file system test
+@item -c @var{file}
+@var{file} exists and is character special
+
+@cindex -d, file system test
+@item -d @var{file}
+@var{file} exists and is a directory
+
+@cindex -e, file system test
+@item -e @var{file}
+@var{file} exists
+
+@cindex -f, file system test
+@item -f @var{file}
+@var{file} exists and is a regular file
+
+@cindex -g, file system test
+@item -g @var{file}
+@var{file} exists and is set-group-ID
+
+@cindex -G, file system test
+@item -G @var{file}
+@var{file} exists and is owned by the primary group of the current user.
+
+@cindex -h, file system test
+@cindex -L, file system test
+@item -h @var{file}
+@itemx -L @var{file}
+@var{file} exists and is a symbolic link
+
+@cindex -k, file system test
+@item -k @var{file}
+@var{file} exists and has its sticky bit set
+
+@cindex -L, file system test
+@item -L @var{file}
+@var{file} exists and is a symbolic link (same as -h)
+
+@cindex -O, file system test
+@item -O @var{file}
+@var{file} exists and is owned by the current user
+
+@cindex -p, file system test
+@item -p @var{file}
+@var{file} exists and is a named pipe
+
+@cindex -r, file system test
+@item -r @var{file}
+@var{file} exists and read permission is granted
+
+@cindex -s, file system test
+@item -s @var{file}
+@var{file} exists and has a size greater than zero
+
+@cindex -S, file system test
+@item -S @var{file}
+@var{file} exists and is a socket
+
+@cindex -u, file system test
+@item -u @var{file}
+@var{file} exists and its set-user-ID bit is set
+
+@cindex -w, file system test
+@item -w @var{file}
+@var{file} exists and write permission is granted
+
+@cindex -x, file system test
+@item -x @var{file}
+@var{file} exists and execute (or search) permission is granted
+@end table
+
@node Boolean expressions
@subsubsection Boolean expressions
Simple expressions can be combined into complex conditions using
diff --git a/src/cfgram.y b/src/cfgram.y
index 9cca689..fb7e757 100644
--- a/src/cfgram.y
+++ b/src/cfgram.y
@@ -64,6 +64,7 @@ static struct transform_node *new_set_node(enum transform_node_type type,
struct argval *tail;
} arglist;
struct { unsigned major, minor; } version;
+ int fstest;
}
%token <str> STRING "string"
@@ -108,6 +109,7 @@ static struct transform_node *new_set_node(enum transform_node_type type,
%token NM "!~"
%token IN "in"
%token GROUP "group"
+%token <fstest> TEST "test"
%left OR
%left AND
@@ -396,6 +398,12 @@ expr : string '~' regex
$$ = new_test_node(test_group);
$$->v.groups = $2.argv;
}
+ | TEST string
+ {
+ $$ = new_test_node(test_fstest);
+ $$->v.fstest.op = $1;
+ $$->v.fstest.arg = $2;
+ }
;
literal : IDENT
diff --git a/src/cflex.l b/src/cflex.l
index 8648697..cafe1d4 100644
--- a/src/cflex.l
+++ b/src/cflex.l
@@ -368,6 +368,8 @@ static int noinc(void);
"!~" return tok(NM);
"in" return tok(IN);
"group" return tok(GROUP);
+-[bcdefgGkOprsSuwx] { yylval.fstest = yytext[1]; return tok(TEST); }
+-[hL] { yylval.fstest = fs_symlink; return tok(TEST); }
[+-]?[0-9]+ { yylval.num.strval = xstrdup(yytext);
yylval.num.intval = atoi(yytext);
return tok(NUMBER); }
diff --git a/src/rush.c b/src/rush.c
index e61c539..2f4c42c 100644
--- a/src/rush.c
+++ b/src/rush.c
@@ -346,9 +346,8 @@ eval_in(struct test_node *node,
}
static rush_bool_t
-groupmember(char const *gname, struct passwd const *pw)
+groupmember(struct group *grp, struct passwd const *pw)
{
- struct group *grp = getgrnam(gname);
if (grp) {
char **p;
@@ -370,12 +369,104 @@ eval_member(struct test_node *node,
size_t i;
for (i = 0; node->v.groups[i]; i++) {
- if (groupmember(node->v.groups[i], req->pw))
+ if (groupmember(getgrnam(node->v.groups[i]), req->pw))
return rush_true;
}
return rush_false;
}
+static rush_bool_t
+rush_access(struct stat *st, struct rush_request *req, int bit)
+{
+ if (st->st_mode & bit)
+ return rush_true;
+
+ bit <<= 3;
+ if ((st->st_mode & bit) && groupmember(getgrgid(st->st_gid), req->pw))
+ return rush_true;
+
+ bit <<= 3;
+ if ((st->st_mode & bit) && st->st_uid == req->pw->pw_uid)
+ return rush_true;
+
+ return rush_false;
+}
+
+static rush_bool_t
+eval_fstest(struct test_node *node,
+ struct rush_rule *rule, struct rush_request *req)
+{
+ struct stat st;
+
+ if (((node->v.fstest.op == fs_symlink) ? lstat : stat)(node->v.fstest.arg, &st)) {
+ if (errno == ENOENT)
+ return rush_false;
+ die(system_error, &req->i18n,
+ _("%s:%d: %s: can't stat '%s': %s"),
+ rule->file, rule->line, rule->tag,
+ node->v.fstest.arg,
+ strerror(errno));
+ }
+
+ switch (node->v.fstest.op) {
+ case fs_block_special:
+ return S_ISBLK(st.st_mode);
+
+ case fs_char_special:
+ return S_ISCHR(st.st_mode);
+
+ case fs_directory:
+ return S_ISDIR(st.st_mode);
+
+ case fs_exists:
+ return rush_true;
+
+ case fs_regular:
+ return S_ISREG(st.st_mode);
+
+ case fs_set_gid:
+ return st.st_mode & S_ISGID;
+
+ case fs_owner_egid:
+ return st.st_gid == req->pw->pw_gid;
+
+ case fs_symlink:
+ return S_ISLNK(st.st_mode);
+
+ case fs_sticky:
+ return st.st_mode & S_ISVTX;
+
+ case fs_owner_euid:
+ return st.st_uid == req->pw->pw_uid;
+
+ case fs_pipe:
+ return S_ISFIFO(st.st_mode);
+
+ case fs_readable:
+ return rush_access(&st, req, 4);
+
+ case fs_size:
+ return st.st_size > 0;
+
+ case fs_socket:
+ return S_ISSOCK(st.st_mode);
+
+ case fs_set_uid:
+ return st.st_mode & S_ISUID;
+
+ case fs_writable:
+ return rush_access(&st, req, 2);
+
+ case fs_executable:
+ return rush_access(&st, req, 1);
+
+ default:
+ die(system_error, NULL,
+ _("INTERNAL ERROR at %s:%d: unrecognized operation %d"),
+ __FILE__, __LINE__, node->v.fstest.op);
+ }
+}
+
rush_bool_t
test_eval(struct test_node *node,
struct rush_rule *rule, struct rush_request *req)
@@ -406,6 +497,9 @@ test_eval(struct test_node *node,
case test_not:
return ! test_eval(node->v.arg[0], rule, req);
+ case test_fstest:
+ return eval_fstest(node, rule, req);
+
default:
die(system_error, NULL,
_("INTERNAL ERROR at %s:%d: unrecognized node type %d"),
diff --git a/src/rush.h b/src/rush.h
index f746dc9..f3a70d6 100644
--- a/src/rush.h
+++ b/src/rush.h
@@ -149,7 +149,8 @@ enum test_type {
test_group,
test_and,
test_or,
- test_not
+ test_not,
+ test_fstest
};
enum cmp_op {
@@ -163,6 +164,26 @@ enum cmp_op {
cmp_in
};
+enum fstest_op {
+ fs_block_special = 'b',
+ fs_char_special = 'c',
+ fs_directory = 'd',
+ fs_exists = 'e',
+ fs_regular = 'f',
+ fs_set_gid = 'g',
+ fs_owner_egid = 'G',
+ fs_symlink = 'h',
+ fs_sticky = 'k',
+ fs_owner_euid = 'O',
+ fs_pipe = 'p',
+ fs_readable = 'r',
+ fs_size = 's',
+ fs_socket = 'S',
+ fs_set_uid = 'u',
+ fs_writable = 'w',
+ fs_executable = 'x'
+};
+
typedef unsigned long rush_num_t;
struct test_node {
@@ -180,6 +201,10 @@ struct test_node {
} cmp;
char **groups;
struct test_node *arg[2];
+ struct {
+ int op;
+ char *arg;
+ } fstest;
} v;
};
diff --git a/tests/.gitignore b/tests/.gitignore
index a5a7d62..1ce0ab2 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -4,4 +4,5 @@ package.m4
testsuite
testsuite.dir
testsuite.log
+mksock
myid
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a5166a7..daff4d0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -79,6 +79,7 @@ TESTSUITE_AT = \
insert.at\
remopt.at\
lex.at\
+ fstest.at\
legacy/argc.at\
legacy/backref.at\
legacy/chdir.at\
@@ -117,4 +118,5 @@ clean-local:
check-local: atconfig atlocal $(TESTSUITE)
@$(SHELL) $(TESTSUITE)
-noinst_PROGRAMS=myid
+noinst_PROGRAMS=myid mksock
+
diff --git a/tests/fstest.at b/tests/fstest.at
new file mode 100644
index 0000000..8fbc111
--- /dev/null
+++ b/tests/fstest.at
@@ -0,0 +1,216 @@
+# This file is part of GNU Rush.
+# Copyright (C) 2021 Sergey Poznyakoff
+#
+# GNU Rush is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Rush is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Rush. If not, see <http://www.gnu.org/licenses/>.
+
+m4_pushdef([AT_FS_SUCCESS],
+[AT_CHECK([$1])
+AT_RUSH_TEST([
+rush 2.0
+rule
+ match $2
+ set SUCCESS = true
+],
+[vars],
+[cmd],
+[0],
+[{
+ "vars":{
+ "SUCCESS":"true"
+ }
+}
+],
+[])])
+
+m4_pushdef([AT_FS_FAILURE],
+[AT_CHECK([$1])
+AT_RUSH_TEST([
+rush 2.0
+rule
+ match $2
+ set SUCCESS = true
+],
+[vars],
+[cmd],
+[1],
+[],
+[rush: Error: no matching rule for "cmd", user $MY_USER
+])])
+
+AT_SETUP([basic filesystem tests])
+AT_KEYWORDS([match fstest])
+
+# ##############################
+# test -e
+# ##############################
+
+AT_FS_FAILURE([rm -f file],[-e file])
+AT_FS_SUCCESS([touch file],[-e file])
+
+# ##############################
+# test -f
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-f file])
+AT_FS_SUCCESS([touch file],[-f file])
+AT_FS_FAILURE([rm -f file && mkdir file],[-f file])
+
+# ##############################
+# test -d
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-d file])
+AT_FS_FAILURE([touch file],[-d file])
+AT_FS_SUCCESS([rm -f file && mkdir file],[-d file])
+
+# ##############################
+# test -s
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-s file])
+AT_FS_FAILURE([touch file],[-s file])
+AT_FS_SUCCESS([echo "test" > file],[-s file])
+
+# ##############################
+# test -r
+# ##############################
+
+AT_CHECK([rm -rf file && touch file])
+AT_FS_SUCCESS([chmod u=r file],[-r file])
+AT_FS_SUCCESS([chmod g=r file],[-r file])
+AT_FS_SUCCESS([chmod o=r file],[-r file])
+AT_FS_SUCCESS([chmod a=r file],[-r file])
+AT_FS_FAILURE([chmod -r file],[-r file])
+
+# ##############################
+# test -w
+# ##############################
+
+AT_FS_SUCCESS([chmod u=w file],[-w file])
+AT_FS_SUCCESS([chmod g=w file],[-w file])
+AT_FS_SUCCESS([chmod o=w file],[-w file])
+AT_FS_SUCCESS([chmod a=w file],[-w file])
+AT_FS_FAILURE([chmod 444 file],[-w file])
+
+# ##############################
+# test -x
+# ##############################
+
+AT_FS_SUCCESS([chmod u=x file],[-x file])
+AT_FS_SUCCESS([chmod g=x file],[-x file])
+AT_FS_SUCCESS([chmod o=x file],[-x file])
+AT_FS_SUCCESS([chmod a=x file],[-x file])
+AT_FS_FAILURE([chmod 644 file],[-x file])
+
+AT_CLEANUP
+
+AT_SETUP([match -h])
+AT_KEYWORDS([match fstest fs_symlink])
+# ##############################
+# test -h
+# ##############################
+AT_FS_FAILURE([rm -rf file symlink],[-h symlink])
+AT_FS_FAILURE([touch file],[-h file])
+AT_FS_SUCCESS([ln -sf file symlink || AT_SKIP_TEST],[-h symlink])
+AT_FS_SUCCESS([true],[-L symlink])
+AT_CLEANUP
+
+AT_SETUP([match -p])
+AT_KEYWORDS([match fstest fs_pipe])
+# ##############################
+# test -p
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-p file])
+AT_FS_SUCCESS([mkfifo file || AT_SKIP_TEST],[-p file])
+AT_CLEANUP
+
+AT_SETUP([match -k])
+AT_KEYWORDS([match fstest fs_sticky])
+# ##############################
+# test -k
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-k file])
+AT_FS_SUCCESS([touch file && chmod +t file || AT_SKIP_TEST],[-k file])
+AT_CLEANUP
+
+AT_SETUP([match -c])
+AT_KEYWORDS([match fstest fs_char_special])
+# ##############################
+# test -c
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-c file])
+AT_FS_FAILURE([touch file],[-c file])
+AT_FS_SUCCESS([test -c /dev/tty || AT_SKIP_TEST],[-c /dev/tty])
+AT_CLEANUP
+
+AT_SETUP([match -b])
+AT_KEYWORDS([match fstest fs_block_special])
+AT_FS_FAILURE([rm -rf file],[-b file])
+AT_FS_FAILURE([touch file],[-b file])
+AT_FS_SUCCESS([mknod file b 1 1 || AT_SKIP_TEST],[-b file])
+AT_CLEANUP
+
+AT_SETUP([match -S])
+AT_KEYWORDS([match fstest fs_socket])
+# ##############################
+# test -c
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-S file])
+AT_FS_FAILURE([touch file],[-S file])
+AT_FS_SUCCESS([mksock socket || AT_SKIP_TEST],[-S socket])
+AT_CLEANUP
+
+AT_SETUP([match -O])
+AT_KEYWORDS([match fstest fs_owner_euid])
+# ##############################
+# test -O
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-O file])
+AT_FS_SUCCESS([touch file],[-O file])
+AT_CLEANUP
+
+AT_SETUP([match -G])
+AT_KEYWORDS([match fstest fs_owner_egid])
+# ##############################
+# test -G
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-G file])
+AT_FS_SUCCESS([touch file],[-G file])
+AT_CLEANUP
+
+AT_SETUP([match -u])
+AT_KEYWORDS([match fstest fs_set_uid])
+# ##############################
+# test -u
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-u file])
+AT_FS_FAILURE([touch file],[-u file])
+AT_FS_SUCCESS([chmod u+s file || AT_SKIP_TEST],[-u file])
+AT_CLEANUP
+
+AT_SETUP([match -g])
+AT_KEYWORDS([match fstest fs_set_gid])
+# ##############################
+# test -g
+# ##############################
+AT_FS_FAILURE([rm -rf file],[-g file])
+AT_FS_FAILURE([touch file],[-g file])
+AT_FS_SUCCESS([chmod g+s file || AT_SKIP_TEST],[-g file])
+AT_CLEANUP
+
+# ##############################
+# Cleanup
+# ##############################
+
+m4_popdef([AT_FS_FAILURE])
+m4_popdef([AT_FS_SUCCESS])
+
+
diff --git a/tests/mksock.c b/tests/mksock.c
new file mode 100644
index 0000000..64e82b5
--- /dev/null
+++ b/tests/mksock.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <assert.h>
+
+int
+main(int argc, char **argv)
+{
+ struct sockaddr_un s;
+ int fd;
+
+ assert(argc == 2);
+ s.sun_family = AF_UNIX;
+ assert(strlen(argv[1]) < sizeof(s.sun_path));
+ strcpy(s.sun_path, argv[1]);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ perror("socket");
+ return 1;
+ }
+ if (bind(fd, (struct sockaddr*)&s, sizeof(s))) {
+ perror("bind");
+ return 1;
+ }
+ return 0;
+}
+
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 3df57de..421ee29 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -16,6 +16,8 @@
m4_version_prereq([2.52g])
+m4_define([AT_SKIP_TEST],[exit 77])
+
dnl m4_run_rush([KW], [CMD],[EC],[STDOUT],[STDERR],...)
m4_define([m4_run_rush],[m4_ifval([$2],[dnl
echo "; m4_bpatsubst($2,",\\")"
@@ -75,6 +77,7 @@ m4_include([user.at])
m4_include([in.at])
m4_include([gid.at])
m4_include([group.at])
+m4_include([fstest.at])
AT_BANNER([Complex Conditions])
m4_include([and.at])

Return to:

Send suggestions and report system problems to the System administrator.