/*      $NetBSD: lif.c,v 1.2 2018/09/04 15:08:30 riastradh Exp $        */

/*      $OpenBSD: lif.c,v 1.7 2001/06/09 03:54:41 mickey Exp $  */

/*
* Copyright (c) 1998-2004 Michael Shalayeff
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <sys/param.h>
#include <sys/disklabel.h>
#include "libsa.h"

extern int debug;

struct file {
       char f_buf[HPPA_LIF_FILESTART];/* buffer for lif volume header and dir */
       struct hppa_lifvol *f_lp;       /* lif volume header pointer */
       struct hppa_lifdir *f_ld;       /* lif dir pointer */
       int     f_nfiles;       /* gross number for lif dir entries */

       off_t   f_seek;         /* seek pointer for file read */
       struct hppa_lifdir *f_rd;       /* lif dir pointer for readdir */

       int     f_isdir;        /* special hacky flag for '.' dir */
       int     f_count;        /* this file length */
       int     f_off;          /* this file offset */
};

int
lif_open(const char *path, struct open_file *f)
{
       struct file *fp;
       struct hppa_lifdir *dp;
       const char *p, *q;
       struct hppa_lifload load;
       int err, l;
       size_t buf_size;

#ifdef LIFDEBUG
       if (debug)
               printf("lif_open(%s, %p)\n", path, f);
#endif

       fp = alloc(sizeof(*fp));
       /* XXX we're assuming here that sizeof(fp->f_buf) >= HPPA_LIF_FILESTART */

       err = (*f->f_dev->dv_strategy)(f->f_devdata, F_READ, 0,
           sizeof(fp->f_buf), &fp->f_buf, &buf_size);
       if (err || buf_size != sizeof(fp->f_buf)) {
#ifdef LIFDEBUG
               if (debug)
                       printf("lif_open: unable to read LIF header (%d)\n", err);
#endif
       } else if ((fp->f_lp = (struct hppa_lifvol *)fp->f_buf)->vol_id ==
                  HPPA_LIF_VOL_ID) {
               f->f_fsdata = fp;
               fp->f_ld = (struct hppa_lifdir *)(fp->f_buf + HPPA_LIF_DIRSTART);
               fp->f_seek = 0;
               fp->f_rd = fp->f_ld;
               fp->f_nfiles = hppa_lifstob(fp->f_lp->vol_dirsize) /
                       sizeof(struct hppa_lifdir);

               /* no dirs on the lif */
               for (p = path + (l = strlen(path)); p >= path; p--)
                       if (*p == '/') {
                               p++;
                               break;
                       }
               if (p > path)
                       path = p;
       } else
               err = EINVAL;

       if (!err && *path != '.') {
               fp->f_isdir = 0;
               err = ENOENT;
               for (dp = fp->f_ld; dp < &fp->f_ld[fp->f_nfiles]; dp++) {
#ifdef LIFDEBUG
                       if (debug)
                               printf("lif_open: "
                                      "%s <--> '%c%c%c%c%c%c%c%c%c%c'\n",
                                      path, dp->dir_name[0], dp->dir_name[1],
                                      dp->dir_name[2], dp->dir_name[3],
                                      dp->dir_name[4], dp->dir_name[5],
                                      dp->dir_name[6], dp->dir_name[7],
                                      dp->dir_name[8], dp->dir_name[9]);
#endif
                       for (p = path, q = dp->dir_name;
                            *q && *q != ' '; q++, p++)
                               if (tolower(*q) != tolower(*p))
                                       break;
                       if ((!*q || *q == ' ') && !*p) {
                               err = 0;
                               break;
                       }
               }
               if (!err) {
                       fp->f_off = hppa_lifstodb(dp->dir_addr);
                       if (!(err =(f->f_dev->dv_strategy)(f->f_devdata, F_READ,
                             fp->f_off, sizeof(load), &load, &buf_size)) &&
                           buf_size == sizeof(load)) {
                               /* no checksum */
                               fp->f_count = load.count - sizeof(int);
                               fp->f_off = dbtob(fp->f_off) + sizeof(load);
#ifdef LIFDEBUG
                               if (debug)
                                       printf("lif_open: %u @ %u [%x]\n",
                                              fp->f_count, fp->f_off,
                                              load.address);
#endif
                       } else if (!err)
                               err = EIO;
               }
       } else
               fp->f_isdir = 1;

       if (err) {
               dealloc (fp, sizeof(*fp));
               f->f_fsdata = NULL;
       }
#ifdef LIFDEBUG
       if (debug)
               printf("ret(%d)\n", err);
#endif
       return err;
}

int
lif_close(struct open_file *f)
{
       dealloc(f->f_fsdata, sizeof(struct file));
       f->f_fsdata = NULL;
       return 0;
}

int
lif_read(struct open_file *f, void *buf, size_t size, size_t *resid)
{
       struct file *fp = (struct file *)f->f_fsdata;
       char *p;
       char bbuf[DEV_BSIZE];
       size_t bsize, count = sizeof(bbuf);
       int err = 0;
       int foff;

#ifdef LIFDEBUG
       if (debug)
               printf("lif_read(%p, %p, %zu, %p)\n", f, buf, size, resid);
#endif

       for (p = bbuf; size; fp->f_seek += bsize, p += bsize) {
               twiddle();
               foff = fp->f_off + fp->f_seek;
               if (fp->f_seek >= fp->f_count ||
                   (err = (f->f_dev->dv_strategy)(f->f_devdata, F_READ,
                    btodb(foff), count, p, &bsize)))
                       break;
               if (p == bbuf) {
                       bsize = sizeof(bbuf) - (foff & (sizeof(bbuf) - 1));
                       bsize = uimin(bsize, size);
                       memcpy(buf, bbuf + (foff & (sizeof(bbuf) - 1)), bsize);
                       p = buf;
               }
               count = size -= bsize;
       }
       if (resid)
               *resid = size;

       return err;
}

int
lif_write(struct open_file *f, void *buf, size_t size, size_t *resid)
{
       return EOPNOTSUPP;
}

off_t
lif_seek(struct open_file *f, off_t offset, int where)
{
       struct file *fp = (struct file *)f->f_fsdata;

       switch (where) {
       case SEEK_SET:
               fp->f_seek = offset;
               break;
       case SEEK_CUR:
               fp->f_seek += offset;
               break;
       case SEEK_END:
               fp->f_seek = fp->f_count - offset;
               break;
       default:
               return (-1);
       }
       return (fp->f_seek);
}

int
lif_stat(struct open_file *f, struct stat *sb)
{
       struct file *fp = (struct file *)f->f_fsdata;

       sb->st_mode = 0755 | (fp->f_isdir? S_IFDIR: 0); /* XXX */
       sb->st_uid = 0;
       sb->st_gid = 0;
       sb->st_size = fp->f_count;
       return 0;
}

int
lif_readdir(struct open_file *f, char *name)
{
       struct file *fp = (struct file *)f->f_fsdata;
       char *p;

       if (name) {
               while ((fp->f_rd->dir_name[0] == ' ' ||
                       !fp->f_rd->dir_name[0]) &&
                      (fp->f_rd - fp->f_ld) < fp->f_nfiles)
                       fp->f_rd++;
               if ((fp->f_rd - fp->f_ld) >= fp->f_nfiles) {
                       *name = '\0';
                       return -1;
               }
               strncpy(name, fp->f_rd->dir_name, sizeof(fp->f_rd->dir_name));
               if ((p = strchr(name, ' ')))
                       *p = '\0';
               fp->f_rd++;
       } else
               fp->f_rd = fp->f_ld;

       return 0;
}