[comp.sys.hp] allocate/deallocate command wanted

rclark@speclab.bgp-usgs.gov (Roger N. Clark) (08/04/89)

I've asked this question some months ago, but it seems appropriate
to ask it again.

What to do about allocating tape drives, plotters and other devices
that user programs must communicate to directly?

For example, say I want to do a tar of all my stuff.  I just put on
a tape, and away I go.  What happens if somone else writes to the
tape while I am writing?  What happens if my job has just finished,
and someone else starts writing--they write on my tape!  All kinds
of bad things can happen: someone could read a record on my tape,
and if my job was about to write, it could write in the wrong place
(making me write over my own data), or someone could read sensitive
information.

Unix needs an allocate/deallocate command for devices.  Anyone know
of a public domain version they could post?  For HP and other
companies selling to business, isn't it a little embarrassing to not
have such a simple security feature?  Are there any plans in the
Unix standardization efforts to make such a command?

Roger N. Clark
..!speclab!rclark

rer@hpfcdc.HP.COM (Rob Robason) (08/08/89)

> What happens if somone else writes to the tape while I am writing?
> Roger N. Clark

It seems like a spooling problem:  unfortunately the logical command
name (mt) is already taken.  I know of no solution to this problem
in any standards, though I agree it is a legitimate question.  One thought
that comes to mind is a "lock and do" command that would lock a specific
file (or group of files) and exec a shell to do the desired action.  In
the simple case it might look something like:

	NAME
	lad - lock and do command

	SYNOPSIS
	lad -f pathname ... command_string

	DESCRIPTION
	lad places an enforcement mode lock on pathname, then spawns a
	child process to execute command_string.  Upon completion of the
	child process, the lock is released.

This could be done simply with HP-UX Enforcement locking.
Unfortunately, that is not very universal yet.  I think that /usr/group
has proposed Manditory (Enforcement) locking, which would solve the
problem.

In HP-UX, the lad command would simply open, fchmod and fcntl 'pathname'
to create an enforcement lock, then call system('command_string'), then
fchmod and close 'pathname' to clean up.

There are some picky-uny user problems like getting the quoting right on
command_string to accept pipes, redirection and shell variable
expansion, but that could be handled with good documentation.  Another
area of care would be using WEXITSTATUS to inspect the return value of
system(3) to let the command exit status ripple back to the user.

Until the /usr/group proposal finds its way into an industry standard,
though, no such solution is likely to be very portable.  Any other
solution relies on cooperation between processes to observe advisory
locks, which is better than nothing, but far from bullet (or idiot)
proof.

Rob Robason

mcb@hpfcdc.HP.COM (Michael Berry) (08/08/89)

Re:  What happens if somone else writes to the tape while I am writing?
     (Roger N. Clark)

>	NAME
>	lad - lock and do command

>	SYNOPSIS
>	lad -f pathname ... command_string

> This could be done simply with HP-UX Enforcement locking.
> Unfortunately, that is not very universal yet.  I think that /usr/group
> has proposed Manditory (Enforcement) locking, which would solve the
> problem.

I might add that cooperating applications (users, whatever) could solve this
without needing enforcement mode locking.  Simple lockf(2) would suffice.

Michael Berry   ARPA:mcb%hpfcde@hplabs.HP.COM   UUCP:hplabs!hpfcla!mcb

hesh@lll-crg.llnl.gov (Chris Steinbroner) (08/09/89)

rer@hpfcdc.HP.COM (Rob Robason) writes:
>> What happens if somone else writes to the tape while I am writing?
>> Roger N. Clark
>
>It seems like a spooling problem:  unfortunately the logical command
>name (mt) is already taken.  I know of no solution to this problem
>in any standards, though I agree it is a legitimate question.  One thought
>that comes to mind is a "lock and do" command that would lock a specific
>file (or group of files) and exec a shell to do the desired action.

i think the solution is easier than
that which rob proposed.  at a previous
place of employment, a command called
"tape" took a single argument which was
the name of the user to which the tape
drive(s) should be assigned (owned).
the modes of the drive are 600, so
that prevents general users from writing
to the tape drive.  when the user is done
with the drive, it is assigned to root.

of course, this will not prevent root
users from accessing the drive; but
you simply have to address this problem
with a policy: no user will access the
drive unless it is assigned to her.

this worked well in this environment,
especially considering the fact that
it allowed the administrators to "charge"
(yes, accounting) the user for the
use of the drive.

-- hesh

paul@hpldola.HP.COM (Paul Bame) (08/09/89)

Check out "A Distributed Resource Allocator for Unix Systems" by
Griffin G. Smith, Jr., AT&T Bell Labs in the recent (Baltimore)
summer USENIX Proceedings.

		--Paul Bame

Food for thought: Un*x has allocated one resource quite well for several
years - the tty.

dunlap@apl-em.UUCP (John Dunlap) (08/09/89)

>What happens if somone else writes to the tape while I am writing?  
>What happens if my job has just finished,
>and someone else starts writing--they write on my tape!

One can unload the tape as soon as the writing is finished.
I usually have a three line script that 
	1. writes the tape
	2. reads the tape to make a table listing
	3. unloads the tape

I wonder what does happen when two programs write to the same tape
simulateously.  To my knowledge we have not had that happen in 5 years
on three machines.

We run the plotters on the spooler with the device file owned by
the "lp" user.

Masscomp does have an allocate for the tape drive.

John
dunlap@apl-em.apl.washington.edu
hpfcse!hpubvwa!apl-em!dunlap

rclark@speclab.bgp-usgs.gov (Roger N. Clark) (08/10/89)

>i think the solution is easier than
>that which rob proposed.  at a previous
>place of employment, a command called
>"tape" took a single argument which was
>the name of the user to which the tape
>drive(s) should be assigned (owned).
>the modes of the drive are 600, so
>that prevents general users from writing
>to the tape drive.  when the user is done
>with the drive, it is assigned to root.
>
>-- hesh
>----------

I agree, and this is exactly what I was thinking of doing:  write a
little c program called "allocate" that would change the mode
and owner of the device (all versions, like /dev/rmt/0m, /dev/mt/0m, ...)
and creat a lock file somewhere to indicate the device is currently
allocated to a user.

Then make a deallocate command that would chmod and owner back
to root, and delets the lock file.  Sticky issues, are if the user
logs out and leaves the lock--run deallocate from .logout (csh)...
You would also need to check if a batch or at process was going to
run and use the tape!  Not so easy!  We could start out simply and
ignore the batch/at case.

The creation of a lock file would allow root to also use it, but
wouldn't prevent two root users from writing to the same device.

I posted the question thinking that someone would have solved the
problem by now and public domain source would exist.  If anyone out
there has such a thing, please let me know.  I haven't been able to
find anything in the usenet archives.  If it doesn't exist, I
will work on it over the next few months and post it when complete.

What would be involved to bring this sort of thing up to the
standards organizations?  How about HP?

Roger Clark

wayne@dsndata.uucp (Wayne Schlitt) (08/14/89)

if i remeber correctly there is a package that does this very thing.
check out the "with" command in comp.sources.unix volume 13.  it works
for floppies, tapes and any other device/file that you want controlled
access to.

(comp.sources.unix archives are available on uunet and many other
places...  let me know if you need help getting it.  if it is not too
long i can probably send you a copy of it...)


-wayne

mcf@statware.UUCP (Mathieu Federspiel) (08/18/89)

In article <210024@speclab.bgp-usgs.gov> rclark@speclab.bgp-usgs.gov (Roger N. Clark) writes:
>
>Unix needs an allocate/deallocate command for devices.  Anyone know
>of a public domain version they could post?  For HP and other

  This group has helped me with several pieces of software, so here
is my thanks.  I was asked to write the following programs for our
tape drives several years ago.  They use the method mentioned by
several others about using the permissions on the device files to
restrict access to the tape drive.  Enjoy!


#---------------------------------- cut here ----------------------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by mcf at statware on Thu Aug 17 15:42:15 1989
#
# This archive contains:
#	tmount.c	tumount.c	tmount.1	
#
# Error checking via wc(1) will be performed.

LANG=""; export LANG

echo x - tmount.c
cat >tmount.c <<'@EOF'
/*
   tmount: mount (reserve) tape drive for user

   By:  Mathieu Federspiel, mcf@statware
   October 1987
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#define LOCKF "/usr/spool/uucp/LCK..mt\0"
#define DEVF "/dev/MT\0"
#define MAXDEVS 20
void exit();
char *progname;

main(argc, argv)
   int argc;
   char *argv[];
{
   FILE *flock, *efopen();
   int tmpi, ndevs, errflg=0;
   char *devname[MAXDEVS], *DEVFILE, *LOCKFILE;
   char *tapenr="1";
   int thisuser, thisgroup;
   struct stat statbuf;
   struct passwd *getpwuid(), pwbuf;
   extern int optind;
   extern char *optarg;

/* User help section */

   progname=argv[0];
   /* get options */
   while ((tmpi = getopt(argc, argv, "t:")) != EOF)
      switch (tmpi) {
         case 't':
            tapenr = optarg;
            break;
         case '?':
            errflg++;
      }
   if (errflg) {
      (void)printf("Usage: tmount [-t tape_number]\n");
      exit(1);
   }
   /* build DEVFILE and LOCKFILE from tapenr; default 1 */
   if ((DEVFILE=(char *)malloc((unsigned)50)) == NULL) {
      (void)printf("Malloc failed: DEVFILE\n");
      exit(1);
   }
   if ((LOCKFILE=(char *)malloc((unsigned)50)) == NULL) {
      (void)printf("Malloc failed: LOCKFILE\n");
      exit(1);
   }
   DEVFILE=strcat(DEVF,tapenr);
   LOCKFILE=strcat(LOCKF,tapenr);
   /* allocate space for device file names and get them */
   for (tmpi=0;tmpi<MAXDEVS;tmpi++) {
      if ((devname[tmpi]=(char *)malloc((unsigned)50)) == NULL) {
         (void)printf("Malloc failed: tmpi = %d\n",tmpi);
         exit(1);
      }
   }
   if ((ndevs=getdevs(devname,DEVFILE)) == 0) {
      (void)printf("Getdevs failed.\n");
      exit(1);
   }

/* Set UID */

   thisuser=getuid();
   thisgroup=getgid();
   if ((tmpi=setuid(0)) != 0) {
      (void)printf("Set UID failed.\n");
      exit(tmpi);
   }

/* Check lock file, set or exit */

   if (stat(LOCKFILE,&statbuf) != 0) {	/* failed, test why */
      if (errno == 2) {
         flock=efopen(LOCKFILE,"w");
         (void)fclose(flock);
         if (chown(LOCKFILE,thisuser,thisgroup) != 0) {
            (void)printf("Lock chown failed, errno = %d.\n",errno);
            exit(1);
         }
         if (chmod(LOCKFILE,0644) != 0) {
            (void)printf("Lock chmod failed, errno = %d.\n",errno);
            exit(1);
         }
         (void)printf("Lock file created; tape drive mounted.\n");
         for (tmpi=0;tmpi<ndevs;tmpi++) {
            if (chown(devname[tmpi],thisuser,thisgroup) != 0) {
               (void)printf("Dev chown failed, errno = %d.\n",errno);
               exit(1);
            }
         }
      } else {
         (void)printf("Stat failed, errno = %d.\n",errno);
         exit(1);
      }
   } else {				/* passed, file must exist */
      pwbuf = *getpwuid(statbuf.st_uid);
      (void)printf("Tape drive mounted for uid %d, %s.\n",
         statbuf.st_uid, pwbuf.pw_name);
      exit(1);
   }

   exit(0);
}

FILE *efopen(file, mode)	/* fopen file, die if cannot */
   char *file, *mode;
{
   FILE *fp, *fopen();
   extern char *progname;

   if ((fp = fopen(file, mode)) != NULL)
      return fp;
   (void)fprintf(stderr, "%s: can't open file %s mode %s\n",
      progname, file, mode);
   exit(1);
}

int getdevs(array,namefile)	/* get device file names */
   char *array[MAXDEVS], *namefile;
{
   FILE *fdevs, *efopen();
   int tmpi, nullnl(), nfiles=0;

   fdevs = efopen(namefile,"r");
   for (tmpi=0;tmpi<MAXDEVS;tmpi++) {
      if ((array[tmpi]=fgets(array[tmpi],50,fdevs)) != NULL) {
         (void)nullnl(array[tmpi]);
         nfiles++;
      } else {
         tmpi=MAXDEVS+1;
      }
   }
   if (tmpi == MAXDEVS) (void)printf("Warning: max device files reached.\n");
   if (fclose(fdevs) != 0) {
      (void)printf("Fclose on dev file failed.");
      exit(1);
   }
   return nfiles;
}

int nullnl(ptr)			/* null new-line characters */
   char *ptr;
{
   int tmpi=0;

   while (*ptr != '\0') {
      if (*ptr == '\n') {
         *ptr='\0';
         tmpi++;
      }
      ptr++;
   }
   return tmpi;
}
@EOF
if test "`wc -lwc <tmount.c`" != '    166    446   4109'
then
	echo ERROR: wc results of tmount.c are `wc -lwc <tmount.c` should be     166    446   4109
fi

chmod 644 tmount.c

echo x - tumount.c
cat >tumount.c <<'@EOF'
/*
   tumount: unmount (free) tape drive for other users

   By:  Mathieu Federspiel, mcf@statware
   October 1987
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mtio.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <fcntl.h>
#define LOCKF "/usr/spool/uucp/LCK..mt\0"
#define DEVF "/dev/MT\0"
#define MAXDEVS 20
void exit();
char *progname;

main(argc, argv)
   int argc;
   char *argv[];
{
   FILE *efopen();
   int open(), close(), fd;
   int tmpi, ndevs, errflg=0, lineopt=0;
   char *devname[MAXDEVS], *DEVFILE, *LOCKFILE;
   char *tapenr="1";
   int thisuser, thisgroup;
   struct stat statbuf;
   struct mtop top;
   struct passwd *getpwuid(), pwbuf;
   extern int optind;
   extern char *optarg;

/* User help section */

   progname=argv[0];
   /* get options */
   while ((tmpi = getopt(argc, argv, "t:l")) != EOF)
      switch (tmpi) {
         case 't':
            tapenr = optarg;
            break;
         case 'l':
            lineopt++;
            break;
         case '?':
            errflg++;
      }
   if (errflg) {
      (void)printf("Usage: tumount [-t tape_number] [-l]\n");
      exit(1);
   }
   /* build DEVFILE and LOCKFILE from tapenr; default 1 */
   if ((DEVFILE=(char *)malloc((unsigned)50)) == NULL) {
      (void)printf("Malloc failed: DEVFILE\n");
      exit(1);
   }
   if ((LOCKFILE=(char *)malloc((unsigned)50)) == NULL) {
      (void)printf("Malloc failed: LOCKFILE\n");
      exit(1);
   }
   DEVFILE=strcat(DEVF,tapenr);
   LOCKFILE=strcat(LOCKF,tapenr);
   /* allocate space for device file names and get them */
   for (tmpi=0;tmpi<MAXDEVS;tmpi++) {
      if ((devname[tmpi]=(char *)malloc((unsigned)50)) == NULL) {
         (void)printf("Malloc failed: tmpi = %d\n",tmpi);
         exit(1);
      }
   }
   if ((ndevs=getdevs(devname,DEVFILE)) == 0) {
      (void)printf("Getdevs failed.\n");
      exit(1);
   }

/* Get and Set UID */

   thisuser=getuid();
   thisgroup=getgid();
   if ((tmpi=setuid(0)) != 0) {
      (void)printf("Set UID failed.\n");
      exit(tmpi);
   }

/* Check lock file, set or exit */

   if (stat(LOCKFILE,&statbuf) != 0) {	/* failed, test why */
      if (errno == 2) {
         (void)printf("Tape drive not mounted.\n");
         exit(1);
      } else {
         (void)printf("Stat failed, errno = %d\n",errno);
         exit(1);
      }

   } else {				/* passed, file must exist */
      if (statbuf.st_uid == thisuser) {

		/* open mag tape file, take off line */
         if (! lineopt) {	/* option -l not specified */
            fd = open(devname[1],O_WRONLY);
            if (fd == -1) {
               if (errno != 5) {
                  (void)printf("Device open() failed, errno = %d.\n",errno);
                  exit(1);
               }
            }
            /* if fd == -1, I/O error, possibly tape off line.  Go on! */
            if (fd != -1) {
               top.mt_count = 1;
               top.mt_op = MTOFFL;
               fd = ioctl(fd,MTIOCTOP,&top);
            /* if failed, go on; may not be tape device
               if (fd == -1) {
                  (void)printf("Ioctl failed, errno = %d.\n",errno);
                  exit(1);
               }
            */
               fd = close(fd);
               if (fd == -1) {
                  (void)printf("Device close() failed, errno = %d.\n",errno);
                  exit(1);
               }
            }
         }

		/* restore owner of tape files to root */
         for (tmpi=0;tmpi<ndevs;tmpi++) {
            if (chown(devname[tmpi],0,thisgroup) != 0) {
               (void)printf("Chown failed, errno = %d.\n",errno);
               exit(1);
            }
         }

		/* remove lock file */
         if (unlink(LOCKFILE) != 0) {
            (void)printf("Unlink failed, errno = %d.\n",errno);
            exit(1);
         }
         (void)printf("Lock file removed; tape drive unmounted.\n");

      } else {
         pwbuf = *getpwuid(statbuf.st_uid);
         (void)printf("Permission denied: owner is uid %d, %s.\n",
            statbuf.st_uid, pwbuf.pw_name);
         exit(1);
      }
   }

   exit(0);
}

FILE *efopen(file, mode)	/* fopen file, die if cannot */
   char *file, *mode;
{
   FILE *fp, *fopen();
   extern char *progname;

   if ((fp = fopen(file, mode)) != NULL)
      return fp;
   (void)fprintf(stderr, "%s: can't open file %s mode %s\n",
      progname, file, mode);
   exit(1);
}

int getdevs(array,namefile)	/* get device file names */
   char *array[MAXDEVS], *namefile;
{
   FILE *fdevs, *efopen();
   int tmpi, nullnl(), nfiles=0;

   fdevs = efopen(namefile,"r");
   for (tmpi=0;tmpi<MAXDEVS;tmpi++) {
      if ((array[tmpi]=fgets(array[tmpi],50,fdevs)) != NULL) {
         (void)nullnl(array[tmpi]);
         nfiles++;
      } else {
         tmpi=MAXDEVS+1;
      }
   }
   if (tmpi == MAXDEVS) (void)printf("Warning: max device files reached.\n");
   if (fclose(fdevs) != 0) {
      (void)printf("Fclose on dev file failed.");
      exit(1);
   }
   return nfiles;
}

int nullnl(ptr)			/* null new-line characters */
   char *ptr;
{
   int tmpi=0;

   while (*ptr != '\0') {
      if (*ptr == '\n') {
         *ptr='\0';
         tmpi++;
      }
      ptr++;
   }
   return tmpi;
}
@EOF
if test "`wc -lwc <tumount.c`" != '    206    586   5244'
then
	echo ERROR: wc results of tumount.c are `wc -lwc <tumount.c` should be     206    586   5244
fi

chmod 644 tumount.c

echo x - tmount.1
cat >tmount.1 <<'@EOF'
.TH TMOUNT,TUMOUNT 1 "LOCAL"
.SH NAME
tmount, tumount \-
mount and umount tape drives

.SH SYNOPSIS
.B tmount
[ -t drive_number ]

.B tumount
[ -t drive_number ] [ -l ]

.SH HP-UX COMPATIBILITY
.TP 10
Level:
HP-UX/STANDARD
.TP
Origin:
Statware

.SH DESCRIPTION
These two utilities reserve or free tape drives for a
single user.
This prevents one user from destroying another's tape which may
be mounted at the time.

The tape drive is reserved by permitting read and write to the tape's
device files only by the owner of the device.
The owner is set by
.BR tmount ,
and set back to root by
.BR tumount .
In addition, a lock file is created which will provide information
to others about who is using the tape drive.

For the tape drive, \fIall\fR device files which access this device
are modified to have the current user as owner with no access permission
for other users.
The names of all device files are maintained in /dev/MT*.
By default, the file /dev/MT1 is read.
The option \fI-t\fR
may be used to specify another tape drive.
For example,
.B tmount -t2
is used to reserve the tape drive defined by the device files
listed in /dev/MT2.
It is up to the system administrator to enter the correct
device files in the appropriate /dev/MT* file.

Note that as this system depends upon the access permissions
of the device files, any device may be reserved by using
tmount and tumount.
The system administrator may define any number of /dev/MT*
files to control any number of devices.

Tumount will attempt to take the tape drive off line, using
.BR ioctl(2) .
This is disabled with \fI-l\fR.

.SH FILES
.nf
/usr/spool/uucp/LCK..mt*
/dev/MT*
.fi

.SH ERROR MESSAGES
Tmount and tumount return 0 on successful completion, 1
otherwise.

.TP
Set UID failed.
The SUID bit is not set; owner must be root.
.TP
Tape drive not mounted.
For tumount, drive is not mounted; i.e., tmount has not been used
to reserve the tape drive.
.TP
Permission denied: owner is uid, uname.
Tape is mounted for another user.
This error will be issued by both tmount and tumount if tmount has
been used to reserve the tape drive.
.TP
Other errors:
Indicate invalid device file specified in /dev/MT* or other
invalid access to a file.
See the system administrator for proper setup of file
permissions.
@EOF
if test "`wc -lwc <tmount.1`" != '     87    396   2275'
then
	echo ERROR: wc results of tmount.1 are `wc -lwc <tmount.1` should be      87    396   2275
fi

chmod 664 tmount.1

exit 0
-- 
Mathieu Federspiel                  mcf%statware.uucp@cs.orst.edu
Statware                            {hp-pcd,tektronix}!orstcs!statware!mcf
260 SW Madison Avenue, Suite 109    503-753-5382
Corvallis  OR  97333  USA