Coding Challenge #2! (114)

54 Name: #!/usr/bin/anon 2006-02-08 02:18 ID:Heaven

Presenting "splice", a useful UNIX command (probably requires a 4.4BSD-based system):

/*
* splice is a program intended to be placed in a pipeline. It reads all
* input from stdin, opens your favorite text editor ($VISUAL, $EDITOR,
* or vi) on that input, then prints your manually changed version of
* the text on stdout.
*
* This is done by forking and reopening stdin and stdout as /dev/tty
* before invoking the text editor.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <paths.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#define MAX_EDITOR_ARGS 30
char *editor_args[MAX_EDITOR_ARGS+2]; /* 1 for filename arg, 1 for NULL */
int nargs = 0;

char tmppath[] = "/tmp/tmp.XXXXXXXXXX";
int fd = -1;

char *strndup(const char *src, size_t len)
{
char *s;
s = malloc(len+1);
if (s == NULL) err(1, NULL);
memcpy(s, src, len);
s[len] = '\0';
return s;
}

void string_to_args(const char *str)
{
const char *p = str, *space;

for (;;)
{
if (nargs == MAX_EDITOR_ARGS)
errx(1, "too many editor arguments");

while(*p == ' ')
p++;
if (*p == '\0')
break;

space = strchr(p, ' ');
if (space == NULL)
space = &p[strlen(p)];
editor_args[nargs++] = strndup(p, space - p);
p = space;
}
editor_args[nargs] = NULL;
}

void save_stdin(void)
{
char buf[BUFSIZ];
int bytesread;

fd = mkstemp(tmppath);
if (fd == -1)
err(1, "cannot open temporary file");

while ((bytesread = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
write(fd, buf, bytesread);
close(fd);
}

void replicate_tmpfile(void)
{
char buf[BUFSIZ];
int bytesread;

fd = open(tmppath, O_RDONLY|O_NOFOLLOW, 0);
if (fd == -1)
err(1, "cannot reopen temporary file");

while ((bytesread = read(fd, buf, BUFSIZ)) > 0)
write(STDOUT_FILENO, buf, bytesread);
close(fd);
}

void unlink_tmpfile(void)
{
if (unlink(tmppath) == -1)
warn("cannot unlink temporary file");
}

void setup_editor_args(void)
{
const char *edname;

edname = getenv("VISUAL");
if (edname == NULL) edname = getenv("EDITOR");
if (edname == NULL) edname = "vi";

/*
* Split up the editor command into arguments,
* e.g. "mg" and "-n".
*/
string_to_args(edname);
}

int start_editor(void)
{
pid_t pid;
int status;

/* To the editor arguments, add the temporary filename to edit. */
editor_args[nargs++] = tmppath;
editor_args[nargs] = NULL;

switch (vfork())
{
case -1:
unlink_tmpfile();
err(1, "vfork");
case 0:
/* Reopen the terminal as stdin and stdout. */
fd = open(_PATH_TTY, O_RDWR);
if (fd == -1)
{
warn("cannot open " _PATH_TTY);
_exit(1);
}
if (dup2(fd, STDIN_FILENO) == -1)
{
warn("can't dup2 to stdin");
_exit(1);
}
if (dup2(fd, STDOUT_FILENO) == -1)
{
warn("can't dup2 to stdout");
_exit(1);
}
close(fd);

execvp(editor_args[0], editor_args);
/* execvp only returns if there is an error. */
warn("%s", editor_args[0]);
_exit(1);
}

pid = wait(&status);
if (pid == -1)
err(1, "wait");
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}

int main(void)
{
int ret;

setup_editor_args();
save_stdin();
ret = start_editor();
if (ret != 0)
warnx("non-zero return value from editor");
replicate_tmpfile();

unlink_tmpfile();
return ret;
}
Name: Link:
Leave these fields empty (spam trap):
More options...
Verification: