zsh-workers
 help / color / mirror / code / Atom feed
From: Oliver Kiddle <okiddle@yahoo.co.uk>
To: zsh-workers@zsh.org
Subject: PATCH: sysopen (was Re: '>>' does not create file if set -C (noclobber) is active)
Date: Thu, 23 Jul 2015 04:56:58 +0200	[thread overview]
Message-ID: <23516.1437620218@thecus.kiddle.eu> (raw)
In-Reply-To: <150628113814.ZM1638@torch.brasslantern.com>

On 28 Jun, Bart wrote:
> Therefore the sysopen suggestion is probably the most appropriate one.

The following patch adds that to the system module along with sysseek
and systell. systell is in the form of a math function which I think
is less awkward to use than a builtin.

I've used -u as the option for indicating the file descriptor because it
is consistent with print and read. You have to be a Fortran programmer
for -u to make sense (unit) so it isn't as helpful now as it was when
ksh was written.

-r/-w/-a are provided for read/write/append as these are common. I
probably would have done -c for create except there is both O_CREAT
and O_EXCL. Permissions for created files is specified with -m and an
octal number. Options after -o are case-insensitive with an initial
O_ ignored. For O_CLOEXEC, there is an fcntl based implementation for
Solaris. I didn't include all possible options only those that I tested
and could think of cases where they might be useful. Adding more
wouldn't be hard. O_NOCTTY is included unconditionally.

Oliver

diff --git a/Doc/Zsh/mod_system.yo b/Doc/Zsh/mod_system.yo
index 7f9c011..e4d4c31 100644
--- a/Doc/Zsh/mod_system.yo
+++ b/Doc/Zsh/mod_system.yo
@@ -28,6 +28,47 @@ system's range), a return status of 1 indicates an error in the
 parameters, and a return status of 2 indicates the error name was
 not recognised (no message is printed for this).
 )
+findex(sysopen)
+redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ ))ifnztexi(        )))
+xitem(tt(sysopen) [ tt(-arw) ] [ tt(-m) var(permissions) ] [ tt(-o) var(options) ])
+item(SPACES()[ tt(-u) var(fd) ] var(file))(
+This command opens a file. The tt(-r), tt(-w) and tt(-a) flags indicate
+whether the file should be opened for reading, writing and appending,
+respectively. The tt(-m) option allows the initial permissions to use when
+creating a file to be specified in octal form.  The file descriptor is
+specified with -u. Either an explicit file descriptor in the range 0 to 9 can
+be specified or a variable name can be given to which the file descriptor
+number will be assigned.
+
+The tt(-o) option allows various system specific options to be
+specified as a comma-separated list. The following is a list of possible
+options. Note that, depending on the system, some may not be available.
+startitem()
+item(tt(cloexec))(
+mark file to be closed when other programs are executed
+)
+xitem(tt(create))
+item(tt(creat))(
+create file if it does not exist
+)
+item(tt(excl))(
+create file, error if it already exists
+)
+item(tt(noatime))(
+suppress updating of the file atime
+)
+item(tt(nofollow))(
+fail if var(file) is a symbolic link
+)
+item(tt(sync))(
+request that writes wait until data has been physically written
+)
+xitem(tt(truncate))
+item(tt(trunc))(
+truncate file to size 0
+)
+enditem()
+)
 findex(sysread)
 redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ ))ifnztexi(        )))
 xitem(tt(sysread )[ tt(-c) var(countvar) ] [ tt(-i) var(infd) ] [ tt(-o) var(outfd) ])
@@ -89,6 +130,14 @@ usual rules; no write to var(outfd) is attempted.
 )
 enditem()
 )
+item(tt(sysseek) [ tt(-u) var(fd) ] [ tt(-w) tt(start)|tt(end)|tt(current) ] var(offset))(
+The current file position at which future reads and writes will take place is
+adjusted to the specified byte offset. The var(offset) is evaluated as a math
+expression. The tt(-u) option allows the file descriptor to be specified. By
+default the offset is specified relative to the start or the file but, with the
+tt(-w) option, it is possible to specify that the offset should be relative to
+the current position or the end of the file.
+)
 item(tt(syswrite) [ tt(-c) var(countvar) ] [ tt(-o) var(outfd) ] var(data))(
 The data (a single string of bytes) are written to the file descriptor
 var(outfd), or 1 if that is not given, using the tt(write) system call.
@@ -161,6 +210,15 @@ version of the shell before it was implemented).
 )
 enditem()
 
+subsect(Math Functions)
+
+startitem()
+item(tt(systell+LPAR()var(fd)RPAR()))(
+The systell math function returns the current file position for the file
+descriptor passed as an argument.
+)
+enditem()
+
 subsect(Parameters)
 
 startitem()
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index f6a21d1..a1ed33a 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -279,6 +279,183 @@ bin_syswrite(char *nam, char **args, Options ops, UNUSED(int func))
 }
 
 
+static struct { char *name; int oflag; } openopts[] = {
+#ifdef O_CLOEXEC
+    { "cloexec", O_CLOEXEC },
+#else
+# ifdef FD_CLOEXEC
+    { "cloexec", 0  }, /* this needs to be first in the table */
+# endif
+#endif
+#ifdef O_NOFOLLOW
+    { "nofollow", O_NOFOLLOW },
+#endif
+#ifdef O_SYNC
+    { "sync", O_SYNC },
+#endif
+#ifdef O_NOATIME
+    { "noatime", O_NOATIME },
+#endif
+    { "excl", O_EXCL | O_CREAT },
+    { "creat", O_CREAT },
+    { "create", O_CREAT },
+    { "truncate", O_TRUNC },
+    { "trunc", O_TRUNC }
+};
+
+/**/
+static int
+bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    int read = OPT_ISSET(ops, 'r');
+    int write = OPT_ISSET(ops, 'w');
+    int append = OPT_ISSET(ops, 'a') ? O_APPEND : 0;
+    int flags = O_NOCTTY | append | ((append || write) ?
+	(read ? O_RDWR : O_WRONLY) :
+	(!append || read) ? O_RDONLY : O_WRONLY);
+    char *opt, *ptr, *nextopt, *fdvar;
+    int o, fd, explicit = -1;
+    mode_t perms = 0666;
+#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
+    int fdflags;
+#endif
+
+    if (!OPT_ISSET(ops, 'u')) {
+	zwarnnam(nam, "file descriptor not specified");
+	return 1;
+    }
+
+    /* file descriptor, either 0-9 or a variable name */
+    fdvar = OPT_ARG(ops, 'u');
+    if (idigit(*fdvar) && !fdvar[1]) {
+	explicit = atoi(fdvar);
+    } else if (!isident(fdvar)) {
+	zwarnnam(nam, "not an identifier: %s", fdvar);
+	return 1;
+    }
+
+    /* open options */
+    if (OPT_ISSET(ops, 'o')) {
+	opt = OPT_ARG(ops, 'o');
+	while (opt) {
+	    if (!strncasecmp(opt, "O_", 2)) /* ignore initial O_ */
+		opt += 2;
+	    if ((nextopt = strchr(opt, ',')))
+		*nextopt++ = '\0';
+	    for (o = sizeof(openopts)/sizeof(*openopts) - 1; o >= 0 &&
+		strcasecmp(openopts[o].name, opt); o--) {}
+	    if (o < 0) {
+		zwarnnam(nam, "unsupported option: %s\n", opt);
+		return 1;
+	    }
+#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
+	    if (!openopts[o].oflag)
+		fdflags = FD_CLOEXEC;
+#endif
+	    flags |= openopts[o].oflag;
+	    opt = nextopt;
+	}
+    }
+
+    /* -m: permissions or mode for created files */
+    if (OPT_ISSET(ops, 'm')) {
+	ptr = opt = OPT_ARG(ops, 'm');
+	while (*ptr >= '0' && *ptr <= '7') ptr++;
+	if (*ptr || ptr - opt < 3) {
+	    zwarnnam(nam, "invalid mode %s", opt);
+	    return 1;
+	}
+	perms = zstrtol(opt, 0, 8); /* octal number */
+    }
+
+    if (flags & O_CREAT)
+	fd = open(*args, flags, perms);
+    else
+	fd = open(*args, flags);
+
+    if (fd == -1) {
+	zwarnnam(nam, "can't open file %s: %e", *args, errno);
+	return 1;
+    }
+    fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd);
+    if (fd == -1) {
+	zwarnnam(nam, "can't open file %s", *args);
+	return 1;
+    }
+
+#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC)
+    if (fdflags)
+	fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+    if (explicit == -1) {
+	fdtable[fd] = FDT_EXTERNAL;
+	setiparam(fdvar, fd);
+	/* if setting the variable failed, close fd to avoid leak */
+	if (errflag)
+	    zclose(fd);
+    }
+
+    return 0;
+}
+
+
+/*
+ * Return values of bin_sysseek:
+ *	0	Success
+ *	1	Error in parameters to command
+ *	2	Error on seek, ERRNO set by system
+ */
+
+/**/
+static int
+bin_sysseek(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    int w = SEEK_SET, fd = 0;
+    char *whence;
+    off_t pos;
+
+    /* -u:  file descriptor if not stdin */
+    if (OPT_ISSET(ops, 'u')) {
+	fd = getposint(OPT_ARG(ops, 'u'), nam);
+	if (fd < 0)
+	    return 1;
+    }
+
+    /* -w:  whence - starting point of seek */
+    if (OPT_ISSET(ops, 'w')) {
+	whence = OPT_ARG(ops, 'w');
+        if (!(strcasecmp(whence, "current") && strcmp(whence, "1")))
+	    w = SEEK_CUR;
+        else if (!(strcasecmp(whence, "end") && strcmp(whence, "2")))
+	    w = SEEK_END;
+	else if (strcasecmp(whence, "start") && strcmp(whence, "0")) {
+	    zwarnnam(nam, "unknown argument to -w: %s", whence);
+	    return 1;
+	}
+    }
+
+    pos = (off_t)mathevali(*args);
+    return (lseek(fd, pos, w) == -1) ? 2 : 0;
+}
+
+/**/
+static mnumber
+math_systell(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id))
+{
+    int fd = (argv->type == MN_INTEGER) ? argv->u.l : (int)argv->u.d;
+    mnumber ret;
+    ret.type = MN_INTEGER;
+    ret.u.l = 0;
+
+    if (fd < 0) {
+	zerr("file descriptor out of range");
+	return ret;
+    }
+    ret.u.l = lseek(fd, 0, SEEK_CUR);
+    return ret;
+}
+
+
 /*
  * Return values of bin_syserror:
  *	0	Successfully processed error
@@ -542,6 +719,8 @@ static struct builtin bintab[] = {
     BUILTIN("syserror", 0, bin_syserror, 0, 1, 0, "e:p:", NULL),
     BUILTIN("sysread", 0, bin_sysread, 0, 1, 0, "c:i:o:s:t:", NULL),
     BUILTIN("syswrite", 0, bin_syswrite, 1, 1, 0, "c:o:", NULL),
+    BUILTIN("sysopen", 0, bin_sysopen, 1, 1, 0, "rwau:o:m:", NULL),
+    BUILTIN("sysseek", 0, bin_sysseek, 1, 1, 0, "u:w:", NULL),
     BUILTIN("zsystem", 0, bin_zsystem, 1, -1, 0, NULL, NULL)
 };
 
@@ -611,6 +790,9 @@ scanpmsysparams(UNUSED(HashTable ht), ScanFunc func, int flags)
     func(&spm.node, flags);
 }
 
+static struct mathfunc mftab[] = {
+    NUMMATHFUNC("systell", math_systell, 1, 1, 0)
+};
 
 static struct paramdef partab[] = {
     SPECIALPMDEF("errnos", PM_ARRAY|PM_READONLY,
@@ -622,7 +804,7 @@ static struct paramdef partab[] = {
 static struct features module_features = {
     bintab, sizeof(bintab)/sizeof(*bintab),
     NULL, 0,
-    NULL, 0,
+    mftab, sizeof(mftab)/sizeof(*mftab),
     partab, sizeof(partab)/sizeof(*partab),
     0
 };
diff --git a/Src/Modules/system.mdd b/Src/Modules/system.mdd
index 46f02d1..eed0c1b 100644
--- a/Src/Modules/system.mdd
+++ b/Src/Modules/system.mdd
@@ -2,7 +2,7 @@ name=zsh/system
 link=dynamic
 load=no
 
-autofeatures="b:sysread b:syswrite b:syserror p:errnos"
+autofeatures="b:sysread b:syswrite b:sysopen b:sysseek b:syserror p:errnos f:systell"
 
 objects="system.o errnames.o"
 


  parent reply	other threads:[~2015-07-23  3:03 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-25  1:02 '>>' does not create file if set -C (noclobber) is active Martijn Dekker
2015-06-25  1:49 ` Bart Schaefer
2015-06-25  2:22   ` Martijn Dekker
2015-06-25  7:30     ` Bart Schaefer
2015-06-25 14:04       ` Stephane Chazelas
2015-06-25 16:00         ` Bart Schaefer
2015-06-25 19:20           ` Chet Ramey
2015-06-26 14:14       ` Martijn Dekker
2015-06-26 20:15         ` Bart Schaefer
2015-06-27  1:54           ` Martijn Dekker
2015-06-27  3:38             ` Bart Schaefer
2015-06-27 17:02         ` Peter Stephenson
2015-06-28  0:02           ` Martijn Dekker
2015-06-28  0:46             ` Martijn Dekker
2015-06-28  7:48             ` Stephane Chazelas
2015-06-28  9:15               ` Oliver Kiddle
2015-06-28 14:00                 ` Stephane Chazelas
2015-06-28 18:38                   ` Bart Schaefer
2015-06-28 19:30                     ` Stephane Chazelas
2015-07-23  2:56                     ` Oliver Kiddle [this message]
2015-07-24 10:57                       ` PATCH: sysopen (was Re: '>>' does not create file if set -C (noclobber) is active) Peter Stephenson
2015-07-24 11:55                         ` Peter Stephenson
2015-07-30 11:05                           ` Mikael Magnusson
2015-07-31 12:41                         ` Oliver Kiddle
2015-07-31 14:02                           ` Mikael Magnusson
2015-06-28 17:19             ` '>>' does not create file if set -C (noclobber) is active Peter Stephenson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=23516.1437620218@thecus.kiddle.eu \
    --to=okiddle@yahoo.co.uk \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).