/*
* smart monitoring for scsi and ata
*      copyright © 2009 erik quanstrom
*/
#include <u.h>
#include <libc.h>
#include <fis.h>
#include "smart.h"

enum{
       Checksec        = 600,
       Opensec         = 60 * 60,
       Relogsec                = 38400 / 4,
};

static  Sdisk   *disks;
static  Dtype   dtab[] = {
       Tata,   "ata",  ataprobe,       ataenable,      atastatus,
       Tscsi,  "scsi", scsiprobe,      scsienable,     scsistatus,
};
static  char    *logfile = "smart";
static  int     aflag;
static  int     tflag;
static  int     vflag;

void
eprint(Sdisk *d, char *s, ...)
{
       char buf[256];
       va_list arg;

       va_start(arg, s);
       vseprint(buf, buf + sizeof buf, s, arg);
       va_end(arg);
//      syslog(0, logfile, "%s: %s", d->name, buf);
       if(vflag)
               fprint(2, "%s: %s", d->name, buf);
}

void
smartlog(Sdisk *d, char *s, ...)
{
       char buf[256];
       va_list arg;

       va_start(arg, s);
       vseprint(buf, buf + sizeof buf, s, arg);
       va_end(arg);
       if(!tflag)
               syslog(0, logfile, "%s: %s", d->name, buf);
       if(tflag || vflag)
               fprint(2, "%s: %s\n", d->name, buf);
}

static void
diskclose(Sdisk *d)
{
       close(d->fd);
       d->fd = -1;
}

static int
diskopen(Sdisk *d)
{
       char buf[128];

       snprint(buf, sizeof buf, "%s/raw", d->path);
       werrstr("");
       return d->fd = open(buf, ORDWR);
}

static int
noexist(void)
{
       char buf[ERRMAX];

       errstr(buf, sizeof buf);
       if(strstr(buf, "exist"))
               return -1;
       return 0;
}

static void
lognew(Sdisk *d)
{
       if(aflag && !tflag)
               smartlog(d, d->t->tname);
}

static int
newdisk(char *s)
{
       char buf[128], *p;
       int i;
       Sdisk d;

       memset(&d, 0, sizeof d);
       snprint(d.path, sizeof d.path, "%s", s);
       if(p = strrchr(s, '/'))
               p++;
       else
               p = s;
       snprint(d.name, sizeof d.name, "%s", p);
       snprint(buf, sizeof buf, "%s/raw", s);
       if(diskopen(&d) == -1)
               return noexist();
       for(i = 0; i < nelem(dtab); i++)
               if(dtab[i].probe(&d) == 0)
               if(dtab[i].enable(&d) == 0){
                       d.t = dtab + i;
                       lognew(&d);
                       break;
               }
       diskclose(&d);
       if(d.t != 0){
               d.next = disks;
               disks = malloc(sizeof d);
               memmove(disks, &d, sizeof d);
       }
       return 0;
}

static int
probe0(char *s, int l)
{
       char *p, *f[3], buf[16];
       int i;

       s[l] = 0;
       for(; p = strchr(s, '\n'); s = p + 1){
               if(tokenize(s, f, nelem(f)) < 1)
                       continue;
               for(i = 0; i < 0x10; i++){
                       snprint(buf, sizeof buf, "/dev/%s%ux", f[0], i);
                       if(newdisk(buf) == -1 && i > 2)
                               break;
               }
       }
       return -1;
}

int
probe(void)
{
       char *s;
       int fd, l, r;

       fd = open("/dev/sdctl", OREAD);
       if(fd == -1)
               return -1;
       r = -1;
       l = 1024;       /* #S/sdctl has 0 size; guess */
       if(s = malloc(l + 1))
       if((l = read(fd, s, l)) > 0)
               r = probe0(s, l);
       free(s);
       close(fd);
       return r;
}

void
run(void)
{
       char buf[1024];
       int e, s0;
       uvlong t, t0;
       Sdisk *d;

       e = 0;
       t = time(0);
       for(d = disks; d; d = d->next){
               t0 = d->lastcheck;
               if(t0 != 0 && t - t0 < Checksec)
                       continue;
               if(diskopen(d) == -1){
                       if(t - t0 > Opensec)
                               smartlog(d, "can't open in %ullds\n", t - t0);
                       continue;
               }
               s0 = d->status;
               d->status = d->t->status(d, buf, sizeof buf);
               diskclose(d);
               if(d->status == -1)
                       e++;
               if((aflag || d->status != s0 || d->status != 0) && !d->silent){
                       t0 = d->lastlog;
                       if(t0 == 0 || t - t0 >= Relogsec){
                               smartlog(d, buf);
                               d->lastlog = t;
                       }
               }else
                       d->lastlog = 0;
               d->lastcheck = t;
       }
       if(tflag)
               exits(e? "smart errors": "");
}

void
usage(void)
{
       fprint(2, "usage: disk/smart [-aptv] [/dev/sdXX] ...\n");
       exits("usage");
}

void
main(int argc, char **argv)
{
       int pflag;

       pflag = 0;
       ARGBEGIN{
       case 'a':
               aflag = 1;
               break;
       case 'p':
               pflag = 1;
               break;
       case 't':
               tflag = 1;
       case 'v':
               vflag = 1;
               break;
       default:
               usage();
       }ARGEND

       for(; *argv; argv++)
               newdisk(*argv);
       if(argc == 0 || pflag)
               probe();
       if(disks == nil)
               sysfatal("no disks");
       for(;; sleep(30*1000))
               run();
}