/*
  Uniflex Kermit's support task.

  This privileged task accounts for the following functions:

  1. Set the baudrate of any terminal with that possibility.
  2. Get the number of free blocks from the current device.
  3. Set the specified date on a specified file.
  4. Set the default directory (to keep track with the caller).
  5. Send a break to the specified device.

  This program reads stdin (normally a pipe) to get the function.
  Current functions are:

  1. 'b' - set the baudrate on the specified device
  2. 'd' - set the specified date on the specified file
  3. 'f' - get number of free blocks on the specified device
  4. 's' - set the default directory
  5. 'w' - send 'break' to the specified device
  6. 'e' - exit the support task

*/

#asm
 info UniFLEX Kermit's support task
 info Author: Jur van der Burg
 info Nettelhorst 56
 info 2402 LS  Alphen aan den Rijn
 info The Netherlands
 info Version: V 1.2
#endasm

#define  PROTOCOL_VERSION   2

#include <stdio.h>
#include <stat.h>
#include <modes.h>
#include <signal.h>
#include <sys/dir.h>
#include <setjmp.h>

#define  chtim   touch          /* other name in my library */

#define  TRUE  1
#define  FALSE 0
#define  IN    0
#define  OUT   1
#define  BAUD  0
#define  BREAK 1
#define  ERROR (-1)

#define  TIMBYTE 0x2b

main()
{
   char command,
        mask,
        errmsg[128],
        device[30],
        file[128];
   int  status,
        len,
        owner,
        perms;
   unsigned int baudrate;
   struct stat buf;
   long get_freedisk(),
        free_blks,
        date;

   fstat(0,&buf);                       /* check standard input */
   if (buf.st_nlink != 0)               /* input must come from a pipe */
      exit(0);                          /* bye bye, leave him in confusion */

   signal(SIGTERM,SIG_IGN);             /* Ignore this signals */
   signal(SIGHUP,SIG_IGN);
   signal(SIGQUIT,SIG_IGN);
   signal(SIGINT,SIG_IGN);
   command = PROTOCOL_VERSION;
   write(OUT,&command,1);               /* Send acknowledge to parent */
   while (TRUE)                         /* Keep looking for commands */
   {
      if (read(IN,&command,1) <= 0)     /* Get command byte */
         exit(1);                       /* Read error */
      switch(command)
      {
          case 'b':                     /* Set device baudrate */
                read(IN,&len,2);        /* Get device name length */
                read(IN,device,len);    /* Get device name */
                read(IN,&baudrate,2);   /* Get the baudrate */
                read(IN,&mask,1);       /* Get the mask */
                status = set_dev(device,baudrate,mask,errmsg,BAUD); /* Set */
                write(OUT,&status,2);   /* Return status */
                if (status)
                   send_error(errmsg);  /* Send error message */
                break;

          case 'w':                     /* Send break to device */
                read(IN,&len,2);        /* Get device name length */
                read(IN,device,len);    /* Get device name */
                status = set_dev(device,0,0,errmsg,BREAK); /* Send it */
                write(OUT,&status,2);   /* Return status */
                if (status)
                   send_error(errmsg);  /* Send error message */
                break;

          case 'd':
                read(IN,&len,2);        /* Get file name length */
                read(IN,file,len);      /* Get file name */
                read(IN,&date,4);       /* Get the date */
                status = set_date(file,date,errmsg); /* Set it */
                write(OUT,&status,2);   /* Return status */
                if (status)
                   send_error(errmsg);  /* Send error message */
                break;

          case 'f':
                read(IN,&len,2);        /* Get device name length */
                read(IN,device,len);    /* Get device name */
                free_blks = get_freedisk(device,errmsg);/* Get # of blocks */
                write(OUT,&free_blks,4); /* Send to parent */
                if (free_blks == (long) ERROR)
                   send_error(errmsg);   /* Send error message */
                break;

          case 's':
                read(IN,&len,2);        /* Get directory name length */
                read(IN,device,len);    /* Get directory name */
                status = set_dir(device,errmsg); /* Set it */
                write(OUT,&status,2);   /* Return status */
                if (status)
                   send_error(errmsg);  /* Send error message */
                break;

          case 'e':
                exit(0);                /* bye bye */

          default:
                break;                  /* Ignore unknown command */
      }
   }
}

send_error(msg)
char *msg;
{
   int length;

   length = strlen(msg) + 1;            /* Must send terminator as well */
   write(OUT,&length,2);                /* Send length */
   write(OUT,msg,length);               /* Send message */
}

/*
  Routine to set the baudrate of a port, or to send a break to it.

  The user must either be the owner or have read and write
  access to the selected device. A check will be made if the
  system consists of new hardware. In the case of old hardware
  the baudrate is not under program control.
*/

#define P_TTY  0x5c             /* Pointer to tty structure */
#define P_NTTY 0x5008           /* Pointer to number of tty's */

int valid_speed[] = {           /* Table containing valid speed's */
   75,0xff,
  150,0xee,
  300,0xdd,
  600,0xcc,
 1200,0xbb,
 2400,0xaa,
 4800,0x99,
 9600,0x88,
    0
      };

struct ttydef {                 /* Internal UniFLEX tty structure definition */
       char *rawq,
            *canq,
            *outq;
       unsigned int devadr;
       char flags,
            delay,
            major,
            minor,
            delcnt,
            col,
            kill,
            erase,
            speed,
            type,
            state,
            xstate };

#define  ISOPEN  0x04           /* Current state */

jmp_buf env;

set_dev(dev,speed,mask,errmsg,what)
char *dev;
unsigned int speed;
char *errmsg;
char mask,
     what;
{
   int *fp,
       *fp1,
       *p,
       i,
       timeout();
   char found,
        speed_val,
        n_tty;
   unsigned int c,
                np;
   struct stat buf;
   struct ttydef tt_dsc;
   struct ttydef *sp;

   if (stat(dev,&buf))                        /* Get device parameters */
      return(prterr("Invalid device specified",errmsg));

   if ((buf.st_mode & S_IFMT) != S_IFCHR) /* Check for a character device */
      return(prterr("Not a character device",errmsg));
   buf.st_mode &= S_IPRM;                 /* Mask type bits */
   if (!((buf.st_mode & S_IOREAD) &&      /* Check for read and write */
         (buf.st_mode & S_IOWRITE)))      /* access for others */
      if (buf.st_uid != getuid())         /* No access, check if he owns */
                                          /* the device */
         return(prterr("Don't own device",errmsg));

   if (what == BAUD)
   {
      found = FALSE;
      p = valid_speed;
      while (*p)
      {
         if (speed == *p++)               /* Compare against table */
         {
            speed_val = *p;               /* Got it ! */
            found = TRUE;
            break;
         }
         p++;                             /* Point to next entry */
      }
      if (!found)
         return(prterr("Invalid speed specified",errmsg));
      if (mask)
         speed_val &= 0x77;               /* Mask RTS and DCD */
   }

/*
   The following piece of code ensures that a device address will
   be available in the internal data structures for the specified
   device. This is simply done by opening the device. In case it's
   not ready (modem for example) a timeout has been setup to allow
   the program to continue.
*/

   signal(SIGALRM,timeout);               /* Catch the timeout trap */
   alarm(1);                              /* One second timeout */
   if (!setjmp(env))                      /* If for the first time */
      fp = open(dev,2);                   /* Open the port */
   alarm(0);                              /* Reset timer */
   close(fp);

   if ((fp = open("/dev/smem",2)) == ERROR) /* Must open this one */
      return(prterr("Error opening '/dev/smem'",errmsg));
   readat(fp,P_NTTY,&np,2);               /* Get pointer to number of tty's */
   readat(fp,np,&n_tty,1);                /* Get number of tty's */
   readat(fp,P_TTY,&sp,2);                /* Get pointer to TTY structure */
   found = FALSE;
   for (i = 0; i < n_tty; i++)
   {
      readat(fp,sp++,&tt_dsc,sizeof(struct ttydef));/* Read total structure */
      if (tt_dsc.devadr == 0)             /* Skip on zero device address */
         continue;

      if ((tt_dsc.major == ((int)(buf.st_size >> 16) & 0xff)) &&
          (tt_dsc.minor == ((int)buf.st_size & 0xff))) /* Chk maj + min id */
      {
         readat(fp,tt_dsc.devadr + 2,&c,2); /* Get speed register */
         if ((c == 0xffff) || (what == BREAK)) /* Should read as 'ffff' */
            found = TRUE;
         else
            return(prterr("Baudrate not programmable",errmsg));
         break;
      }
   }
   if (found)
   {
      setuid(0);                          /* Make sure we've got the priv's */
      if (what == BAUD)
         writeat(fp,tt_dsc.devadr + 2,&speed_val,1); /* Update speed */
      else if (what == BREAK)
         poke_tty(fp,&tt_dsc,--sp);
   }
   else
      return(prterr("Unable to set baudrate",errmsg));
   close(fp);
   return(NULL);
}

poke_tty(fp,tt_dsc,sp)
int *fp;
struct ttydef *tt_dsc,
              *sp;
{
   char break_val,
        org_value,
        new_speed;

   org_value = (tt_dsc->speed & ~0x1c) | 0x81; /* Current characteristics */
   if (!(tt_dsc->state & ISOPEN))              /* RTS high if port closed */
      org_value |= 0x40;
   break_val = org_value | 0x60;            /* Add 'break' bits */
   new_speed = tt_dsc->speed | 0x60;
   writeat(fp,&sp->speed,&new_speed,1);     /* break will not be terminated */
   writeat(fp,tt_dsc->devadr,&break_val,1); /* if new char is received */
   short_delay(fp,3);                       /* 300 Ms delay */
   writeat(fp,&sp->speed,&tt_dsc->speed,1);
   writeat(fp,tt_dsc->devadr,&org_value,1);
}

readat(fp,address,value,count)
int *fp;
unsigned int address;
char *value;
int count;
{
   lseek(fp, (unsigned long)address, 0);
   read (fp, value, count);
}

writeat(fp,address,value,count)
int *fp;
unsigned int address;
char *value;
int count;
{
   lseek(fp, (unsigned long)address, 0);
   write(fp, value, count);
}

short_delay(fp,time)
int *fp, time;
{
   int i;
   char n,
        m;

   n = m = -1;
   for (i = 0; i < time + 1; i++)
   {
      while (n == m)
      {
         lseek(fp, (long)TIMBYTE, 0);          /* Position to time byute */
         read(fp,&m,1);                        /* Read current value */
      }
      n = m;
   }
}


timeout()
{
   longjmp(env,TRUE);                     /* Return after timeout */
}

prterr(str,errstr)
char *str,
     *errstr;
{
   sprintf(errstr,"%s",str);
   return(ERROR);
}

/*
  This routine returns the number of free blocks on the specified device
  or directory. The real device is found by searching for it in the '/dev'
  directory, and comparing the major and minor id of the device with the
  device number of the specified directory. If the device name is found,
  a 'sync' is done to be sure that the information on the disk is current.
  Then the SIR is read and the number of free blocks is retrieved from that.
*/

long get_freedisk(indev,errstr)
char *indev,
     *errstr;
{
   struct stat statbuf;
   struct direct dirptr;
   int device,
       status,
       fpt;
   unsigned int mode;
   char found,
        filename[20];
   long freeblks,
        get_disk();

   fpt = ERROR;
   found = FALSE;                       /* Loop end indicator */
   strcpy(filename,"/dev/");
   if (!stat(indev,&statbuf))           /* Get stat of specified directory */
   {
      device = statbuf.st_dev;          /* Save device major and minor */
      mode = statbuf.st_mode & S_IFMT;  /* mask for file type */
      if (mode == S_IFCHR)              /* Character device not allowed */
         return(prterr("Invalid device or file specified",errstr));
      else if (mode == S_IFBLK)         /* already a block device ? */
      {
         strcpy(filename,indev);        /* supply device name */
         found = TRUE;
      }
      if ((fpt = open("/dev",0)) != ERROR)
         while (!found)                 /* Until we're done... */
         {
            status = read(fpt,&dirptr,sizeof(struct direct)); /* Get entry */
            if ((status == ERROR) || (status == NULL))/* Quit on err or EOF */
               break;
            if (dirptr.d_ino)                  /* If not deleted */
            {
               strcpy(&filename[5],dirptr.d_name);
               if (stat(filename,&statbuf))     /* Get status */
                  break;
               else
               {
                  statbuf.st_mode &= S_IFMT;    /* Mask file type bits */
                  if ((statbuf.st_mode == S_IFBLK) && /* block type device */
                      (statbuf.st_size == device)) /* Maj. and Min. match ? */
                     found = TRUE;                 /* Yes, return name */
               }
            }
         }
   }
   else
      return(prterr("Invalid  device or file specified",errstr));

   if (fpt != ERROR)
      close(fpt);                        /* We're done with it */
   if (found)
      if ((freeblks = get_disk(filename)) != ERROR) /* Get the data */
         return(freeblks);
   return(prterr("Disk read error",errstr));
}

long get_disk(name)
char *name;
{
   int dpt;
   unsigned char freeb[3];
   long size;

   size = ERROR;                        /* assume error */
   if ((dpt = open(name,0)) == ERROR)   /* Open the block device */
      return(ERROR);
   sync();                              /* Make sure count is up to date */
   if (lseek(dpt,(long) 512 + 21, 0) != ERROR) /* Position in SIR */
      if (read(dpt,freeb,3) != ERROR)   /* Get the three bytes */
         l3tol(&size,freeb,1);          /* Convert to long int */
   close(dpt);
   return (size);
}

set_date(filename,filedate,errmsg)
char *filename,
     *errmsg;
long filedate;
{
   if (chtim(filename,filedate) == ERROR) /* change the date */
      return(prterr("Error setting file date",errmsg));
   else
      return(NULL);
}

set_dir(directory,errmsg)
char *directory,
     *errmsg;
{
   if (chdir(directory) == ERROR)
      return(prterr("Error setting directory",errmsg));
   else
      return NULL;
}
