[net.unix-wizards] Job Control Wishlist

mrose.uci-750a%rand-relay@sri-unix.UUCP (10/17/83)

From:  Marshall Rose <mrose.uci-750a@rand-relay>

    We're running BSD 4.1a (yes, I know 4.2 is here, give us time), and
    there are times that I'd like to take a job and re-direct its i/o
    somewhere else.  For instance, it would be nice to be able to tell
    csh

	%1 > file &

    to direct the output of job 1 to the file and put it in the
    background.  I often have the inclination to do this when I start a
    command which I think is going to a) run fast or b) produce little
    output, and I don't re-direct the output.  Since I run with ``stty
    tostop'' set to prevent my screen from getting clobbered, this
    means that I usually interrupt the command and start it again, with
    re-direction in the background.  It could very well be that this
    problem is just an artifact of my style, so you may not find this
    very useful to you.

    In any event 4.1BSD won't let you do this, and I think it unlikely
    that 4.2 has it.  If 4.2 does have this, then don't read the rest
    of this message, just send me a message saying so.

    If not, it seems to me we need a dup3() call.  The call

	dup3(pid1, fd1, pid2, fd2)

    would perform a dup2() from pid1's fd1 to pid2's fd2.  Obviously,
    pid1 and pid2 must exist (you could default a 0 for your
    process-id); the process executing dup3() must have the same
    effective user-ids as pid1 and pid2; fd1 must be a valid
    file-descriptor for pid1; etc., etc.  Hence dup2(fd1, fd2) is
    really dup3(0, fd1, 0, fd2).

    Has anyone thought about this, or actually implemented this
    (optimistic, aren't I?) or something similar?

/mtr

    PS - the possibilities are limitless.  Think of all the times you
    wanted to have something go to more after you invoked it.  With
    dup3() you could use

	%1 | more

    and *poof*: csh would fork, the child would do the dup3() required
    to bind %1's stdout to its stdin, and then exec() more.  Wow.

mike%brl-vgr@sri-unix.UUCP (10/17/83)

From:  Mike Muuss <mike@brl-vgr>

Marshal -
	As far as I can tell so far, 4.2 does not have your wished-for
dup3() system call, but it *does* have the ability to "hand off"
a file descriptor to another process, using a special PF_UNIX "messsage".
Using that capability and some library routines to handle interpreting
a special signal, you actually COULD build a dup3() facility
which would work as long as the target of the dup3 was willing to cooperate.
Neat, huh?
	-Mike

rehmi@umcp-cs.UUCP (10/19/83)

In the spirit of Bob Brown's setuid program, I cons'd up a setf program
which'll open a file and swap pointers to it with some other process. It
needs some cleaning up, but...

=================================
#include <stdio.h>
#include <signal.h>
#include <nlist.h>
#include <pwd.h>
#include <sys/pte.h>
#include <sys/param.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/vm.h>
#include <sys/file.h>
#include <sys/inode.h>

/* setf - change the file descriptors under a running process
 *        and don't even tell it. Abuse with moderate caution.
 * Khron the Elder (rehmi@{umd-csd,umcp-cs}) 27 Jan 83
 *
 * inspired and derived partially from Bob Brown's setuid (rlb@purdue)
 */

#define NAMELIST  "/vmunix"
#define KERNEL      "/dev/kmem"
#define MEMORY      "/dev/mem"
#define SWAP        "/dev/drum"
#define ROOT 0          /* root user id */

int Memory, Kernel, Swap;

struct nlist Names[] = {
    { "_proc" },
#define X_PROC      0
    { "_nproc" },
#define X_NPROC     1
    { "_Usrptmap" },
#define X_USRPTMAP  2
    { "_usrpt" },
#define X_USRPT     3
    { 0 } };

struct proc proc, *Procptr;
int     nproc;
struct pte *Usrptma, *usrpt;

union myuser {
    struct user user;
    char upages[UPAGES][NBPG];
} User;
#define U User.user

#ifdef DEBUG
#	define DEBUGp 1
#else
#	define DEBUGp 0
#endif

int     NoWrite;
caddr_t procp;

getval(fd, addr, buf, n)
int fd, addr, n;
char *buf;
{
    lseek(fd, addr, 0);
    return(read(fd, buf, n));
}

putval(fd, addr, buf, n)
int fd, addr, n;
char *buf;
{
    lseek(fd, addr, 0);
#ifndef NOWRITE
    return(write(fd, buf, n));
#else
    return(n);
#endif
}

getu(user, mproc)
union myuser *user;
struct proc *mproc;
{
    struct pte apte;
    int pad1;    /* avoid hardware botch */
    struct pte arguutl[UPAGES+CLSIZE];
    int pad2;    /* avoid hardware botch */
    register int i;
    int ncl, size;

    size = sizeof (struct user);
    if ((mproc->p_flag & SLOAD) == 0) {
        if (getval(Swap, ctob(mproc->p_swaddr),
               &user->user, size) != size) {
            fprintf(stderr, "can't read u for pid %d from %s\n",
                mproc->p_pid, SWAP);
            return (0);
        }
        return (1);
    }
    if(getval(Kernel,
          (int)&Usrptma[btokmx(mproc->p_p0br) + mproc->p_szpt - 1],
          &apte, sizeof apte)!=sizeof(apte)) {
        printf("can't read indir pte to get u for pid %d from %s\n",
               mproc->p_pid, KERNEL);
        return (0);
    }
           
    if (getval(Memory,
        ctob(apte.pg_pfnum+1)-(UPAGES+CLSIZE)*sizeof (struct pte),
        arguutl, sizeof arguutl) != sizeof arguutl) {
        printf("can't read page table for u of pid %d from %s\n",
            mproc->p_pid, MEMORY);
        return (0);
    }
        
    ncl = (size + NBPG*CLSIZE - 1) / (NBPG*CLSIZE);
    while (--ncl >= 0) {
        i = ncl * CLSIZE;
        if (getval(Memory,
               ctob(arguutl[CLSIZE+i].pg_pfnum),
               user->upages[i], CLSIZE*NBPG)!=CLSIZE*NBPG) {
            printf("can't read page %x of u of pid %d from %s\n",
                arguutl[CLSIZE+i].pg_pfnum,
                mproc->p_pid, MEMORY);
            return(0);
        }
    }
    return (1);
}

putu(user, mproc, member, msize)
union myuser *user;
struct proc *mproc;
char *member;
int msize;
{
    struct pte apte;
    int pad1;    /* avoid hardware botch */
    struct pte arguutl[UPAGES+CLSIZE];
    int pad2;    /* avoid hardware botch */
    register int i;
    int ncl, size;

    size = sizeof (struct user);
#ifdef DEBUG
    printf("put:uid %d gid %d ruid %d rgid %d\n", user->user.u_uid,
           user->user.u_gid, user->user.u_ruid, user->user.u_rgid);
#endif
    
    if ((mproc->p_flag & SLOAD) == 0) {
        if (getval(Swap, ctob(mproc->p_swaddr),
               &user->user, size) != size) {
            fprintf(stderr, "can't read u for pid %d from %s\n",
                mproc->p_pid, SWAP);
            return (0);
        }
        return (1);
    }
    if(getval(Kernel,
          (int)&Usrptma[btokmx(mproc->p_p0br) + mproc->p_szpt - 1],
          &apte, sizeof apte)!=sizeof(apte)) {
        printf("can't read indir pte to get u for pid %d from %s\n",
               mproc->p_pid, KERNEL);
        return (0);
    }

    if (getval(Memory,
        ctob(apte.pg_pfnum+1)-(UPAGES+CLSIZE)*sizeof (struct pte),
        arguutl, sizeof arguutl) != sizeof arguutl) {
        printf("can't read page table for u of pid %d from %s\n",
            mproc->p_pid, MEMORY);
        return (0);
    }

    ncl = (size + NBPG*CLSIZE - 1) / (NBPG*CLSIZE);
    while (--ncl >= 0) {
        i = ncl * CLSIZE;
#ifdef DEBUG
	printf("i*NBPG=%x member=%x user=%x memb-user=%x msize=%x\n",
	       i*NBPG, member, user, (int)member-(int)user, msize);
#endif
	if((i+1)*NBPG > ((int)member-(int)user) &&
	   i*NBPG <= ((int)member-(int)user+msize)) {
	    int foo, bar;
	    
	    bar=MAX(0, (int)member-(int)user-(i*NBPG));
	    foo=MIN(CLSIZE*NBPG, (int)member-(int)user+msize-(i*NBPG))-bar;
	    
#ifdef DEBUG
	    printf("foo=%04x, bar=%04x, iteration %d, i=%04x\n",
	           foo, bar, ncl, i);
#endif
            if (putval(Memory,
                   ctob(arguutl[CLSIZE+i].pg_pfnum)+bar,
                   (user->upages[i])+bar, foo)!=foo) {
                printf("can't write page %x of u of pid %d from %s\n",
                    arguutl[CLSIZE+i].pg_pfnum,
                    mproc->p_pid, MEMORY);
                return(0);
		   }
	}
    }
    return (1);
}

getw(loc)
off_t loc;
{
    long word;

    lseek(Kernel, loc, 0);
    if (read(Kernel, &word, sizeof word) != sizeof word)
        printf("error reading kmem at %x\n", loc);
    return (word);
}

getprocu(pid)
int pid;
{
    register int i, j, found, ncl;
    int newflag;

    Usrptma=(struct pte *)Names[X_USRPTMAP].n_value;
    usrpt=(struct pte *)Names[X_USRPT].n_value;
    getval(Kernel, Names[X_PROC].n_value, &procp, sizeof procp);
    getval(Kernel, Names[X_NPROC].n_value, &nproc, sizeof nproc);
    found = 0;

    lseek(Kernel, (char *)procp, 0);
    for(i = 0; i < nproc; i++){
        if(read(Kernel, &proc, sizeof proc) != sizeof proc){
            perror("Kernel read");
            exit(1);
        }
        Procptr = &proc;
        if(found=(pid == Procptr->p_pid))
            break;
    }

    if(!found){
        fprintf(stderr, "Failed to find pid %d\n", pid);
        exit(1);
    }

    getu(&U, Procptr);
}
/*
 *  Assignproc - place a value in the proc table for a process
 *
 *    Inputs:   Procptr (global) ... ptr to local copy of proc entry.
 *          Proc    (global) ... local copy of entire proc table.
 *          praddr  ............ address of field in proc to change.
 *          value .............. pointer to new value to assign.
 */
assignproc (praddr, value, length)
caddr_t praddr, length;
char *value;
{
    caddr_t seekto;

    seekto = (caddr_t)praddr - (caddr_t)Procptr + (caddr_t)procp;
    lseek(Kernel, seekto, 0);
#ifndef NOWRITE
    if ( write(Kernel, value, length) < 0) {
        perror("Kernel write");
        exit(1);
    }
#endif
}

/*
 *  openfiles - Open System Files: Paging device (Swap)
 *                 Kernel address space (Kernel)
 *                 Physical memory (Memory)
 *
 * Exits if any error
 */
openfiles() /* results are global variables */
{
#ifdef NOWRITE
#define Mode 0
#else
#define Mode 2
#endif

    if((Kernel=open(KERNEL,Mode))<0) {
        perror("Kernel access");
        exit(1);
    }
    if((Memory=open(MEMORY,Mode))<0) {
        perror("Memory access");
        exit(1);
    }
    if((Swap=open(SWAP,Mode))<0) {
        perror("Swap access");
        exit(1);
    }
}

struct nlist nlfoo[] = {{"_u"}, 0};

main(argc, argv)
char argc, **argv;
{
    int kmemfd, fd, foo, j;
    struct user u;
    struct file f, *t;
    struct inode i;
    
    if(argv[1][0]=='-') {
	NoWrite=1;
	argv++;
    }
    
    if(!NoWrite && argc<4) {
	fprintf(stderr, "usage: %s [-] <pid> <file> <fd>\n", argv[0]);
	exit(1);
    }
    
    if(getuid()) exit(1);
    
    openfiles();
    nlist("/vmunix", nlfoo);
    nlist("/vmunix", Names);
    
    setuid(getuid());
    
    fd=open(argv[2], 2);
    if(fd<0) fd=creat(argv[2], 0644);
    if(fd<0) perror(argv[2]);
    
    lseek(Kernel, nlfoo[0].n_value, 0);
    read(Kernel, &u, sizeof u);
    
    
    getprocu(atoi(argv[1]));
    foo=atoi(argv[3]);
    
    if(DEBUGp || NoWrite) {
	printf("name %s old f[%d] %x new f[%d] %x\n",
               U.u_comm, foo, U.u_ofile[foo], fd, u.u_ofile[fd]);
        for(j=0;j<NOFILE;j++)
	    printf("u.u_ofile[%2d] = %08x\tU.u_ofile[%2d] = %08x\n",
	           j, u.u_ofile[j], j, U.u_ofile[j]);
    }

    t=U.u_ofile[foo];
    U.u_ofile[foo]=u.u_ofile[fd];
    u.u_ofile[fd]=t;
    
    if(!NoWrite) {
	putu(&U, Procptr, &U.u_ofile[foo], sizeof U.u_ofile[0]);
	putval(Kernel, nlfoo[0].n_value, &u, sizeof u);
    }
}
====================================

				Enjoy(!),
				 -rehmi
-- 

By the fork, spoon, and exec of The Basfour.

Arpa:   rehmi.umcp-cs@csnet-relay
Uucp:...!harpo!seismo!umcp-cs!rehmi
     ...!{allegra,brl-bmd}!umcp-cs!rehmi

aaw@pyuxss.UUCP (10/25/83)

I have had the same problem and considered the sandwich technique of
implementing it. That is:

	inputControl | theCommand | outputControl

inputControl and outputControl would of course have to be a
replacement shell, to limit proliferation of processes and
interprocess chatter. Consider the consequences: you could have
several 'background' processes requesting I/O and the shell
(=inputControl) should be able to sort it out, e.g. prompt

theCommand: >

to say 'theCommand' wants input. Also the major plus is it would allow
windowing with several forground processes, due to the shells sorting
out the /dev/tty reads/writes
				Aaron Werman pyuxss!aaw