/*
* File system devices.
* '#k'.
* Follows device config in Ken's file server.
* Builds mirrors, device cats, interleaving, and partition of devices out of
* other (inner) devices.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"
enum {
Fmirror, // mirror of others
Fcat, // catenation of others
Finter, // interleaving of others
Fpart, // part of others
Blksize = 8*1024, // for Finter only
Maxconf = 1024, // max length for config
Nfsdevs = 64,
Ndevs = 8,
Qtop = 0, // top dir (contains "fs")
Qdir = 1, // actual dir
Qctl = 2, // ctl file
Qfirst = 3, // first fs file
};
#define Cfgstr "fsdev:\n"
typedef struct Fsdev Fsdev;
struct Fsdev
{
int type;
char *name; // name for this fsdev
vlong start; // start address (for Fpart)
vlong size; // min(idev sizes)
int ndevs; // number of inner devices
char *iname[Ndevs]; // inner device names
Chan *idev[Ndevs]; // inner devices
vlong isize[Ndevs]; // sizes for inneer devices
};
/*
* Once configured, a fsdev is never removed. The name of those
* configured is never nil. We have no locks here.
*/
static Fsdev fsdev[Nfsdevs];
static Qid tqid = {Qtop, 0, QTDIR};
static Qid dqid = {Qdir, 0, QTDIR};
static Qid cqid = {Qctl, 0, 0};
static Cmdtab configs[] = {
Fmirror,"mirror", 0,
Fcat, "cat", 0,
Finter, "inter", 0,
Fpart, "part", 5,
};
static char confstr[Maxconf];
static int configed;
static Fsdev*
path2dev(int i, int mustexist)
{
if (i < 0 || i >= nelem(fsdev))
error("bug: bad index in devfsdev");
if (mustexist && fsdev[i].name == nil)
error(Enonexist);
if (fsdev[i].name == nil)
return nil;
else
return &fsdev[i];
}
static Fsdev*
devalloc(void)
{
int i;
for (i = 0; i < nelem(fsdev); i++)
if (fsdev[i].name == nil)
break;
if (i == nelem(fsdev))
error(Enodev);
return &fsdev[i];
}
static void
setdsize(Fsdev* mp)
{
uchar buf[128]; /* old DIRLEN plus a little should be plenty */
int i;
Chan *mc;
Dir d;
long l;
if (mp->type != Fpart){
mp->start= 0;
mp->size = 0LL;
}
for (i = 0; i < mp->ndevs; i++){
mc = mp->idev[i];
l = devtab[mc->type]->stat(mc, buf, sizeof(buf));
convM2D(buf, l, &d, nil);
mp->isize[i] = d.length;
switch(mp->type){
case Fmirror:
if (mp->size == 0LL || mp->size > d.length)
mp->size = d.length;
break;
case Fcat:
mp->size += d.length;
break;
case Finter:
// truncate to multiple of Blksize
d.length = (d.length & ~(Blksize-1));
mp->isize[i] = d.length;
mp->size += d.length;
break;
case Fpart:
// should raise errors here?
if (mp->start > d.length)
mp->start = d.length;
if (d.length < mp->start + mp->size)
mp->size = d.length - mp->start;
break;
}
}
}
static void
mpshut(Fsdev *mp)
{
int i;
char *nm;
nm = mp->name;
mp->name = nil; // prevent others from using this.
if (nm)
free(nm);
for (i = 0; i < mp->ndevs; i++){
if (mp->idev[i] != nil)
cclose(mp->idev[i]);
if (mp->iname[i])
free(mp->iname[i]);
}
memset(mp, 0, sizeof(*mp));
}
static void
mconfig(char* a, long n) // "name idev0 idev1"
{
static QLock lck;
Cmdbuf *cb;
Cmdtab *ct;
Fsdev *mp;
int i;
char *oldc;
char *c;
vlong size, start;
size = 0;
start = 0;
if (confstr[0] == 0)
seprint(confstr, confstr+sizeof(confstr), Cfgstr);
mp = nil;
cb = nil;
oldc = confstr + strlen(confstr);
qlock(&lck);
if (waserror()){
*oldc = 0;
if (mp != nil)
mpshut(mp);
qunlock(&lck);
if (cb)
free(cb);
nexterror();
}
cb = parsecmd(a, n);
c = oldc;
for (i = 0; i < cb->nf; i++)
c = seprint(c, confstr+sizeof(confstr), "%s ", cb->f[i]);
*(c-1) = '\n';
ct = lookupcmd(cb, configs, nelem(configs));
cb->f++; // skip command
cb->nf--;
if (ct->index == Fpart){
size = strtoll(cb->f[3], nil, 10);
cb->nf--;
start = strtoll(cb->f[2], nil, 10);
cb->nf--;
}
for (i = 0; i < nelem(fsdev); i++)
if (fsdev[i].name != nil && strcmp(fsdev[i].name, cb->f[0])==0)
error(Eexist);
if (cb->nf - 1 > Ndevs)
error("too many devices; fix me");
for (i = 0; i < cb->nf; i++)
validname(cb->f[i], (i != 0));
mp = devalloc();
mp->type = ct->index;
if (mp->type == Fpart){
mp->size = size;
mp->start = start;
}
kstrdup(&mp->name, cb->f[0]);
for (i = 1; i < cb->nf; i++){
kstrdup(&mp->iname[i-1], cb->f[i]);
mp->idev[i-1] = namec(mp->iname[i-1], Aopen, ORDWR, 0);
if (mp->idev[i-1] == nil)
error(Egreg);
mp->ndevs++;
}
setdsize(mp);
poperror();
configed = 1;
qunlock(&lck);
free(cb);
}
static void
rdconf(void)
{
int mustrd;
char *s;
char *c;
char *p;
char *e;
Chan *cc;
Chan **ccp;
s = getconf("fsconfig");
if (s == nil){
mustrd = 0;
s = "/dev/sdC0/fscfg";
} else
mustrd = 1;
ccp = &cc;
*ccp = nil;
c = nil;
if (waserror()){
configed = 1;
if (*ccp != nil)
cclose(*ccp);
if (c)
free(c);
if (!mustrd)
return;
nexterror();
}
*ccp = namec(s, Aopen, OREAD, 0);
devtab[(*ccp)->type]->read(*ccp, confstr, sizeof(confstr), 0);
cclose(*ccp);
*ccp = nil;
if (strncmp(confstr, Cfgstr, strlen(Cfgstr)) != 0)
error("Bad config, first line must be: 'fsdev:\\n'");
kstrdup(&c, confstr + strlen(Cfgstr));
memset(confstr, 0, sizeof(confstr));
for (p = c; p != nil && *p != 0; p = e){
e = strchr(p, '\n');
if (e == nil)
e = p + strlen(p);
if (e == p) {
e++;
continue;
}
mconfig(p, e - p);
}
poperror();
}
static int
mgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
{
Qid qid;
Fsdev *mp;
if (c->qid.path == Qtop){
switch(i){
case DEVDOTDOT:
devdir(c, tqid, "#k", 0, eve, DMDIR|0775, dp);
return 1;
case 0:
devdir(c, dqid, "fs", 0, eve, DMDIR|0775, dp);
return 1;
default:
return -1;
}
}
if (c->qid.path != Qdir){
switch(i){
case DEVDOTDOT:
devdir(c, dqid, "fs", 0, eve, DMDIR|0775, dp);
return 1;
default:
return -1;
}
}
switch(i){
case DEVDOTDOT:
devdir(c, tqid, "#k", 0, eve, DMDIR|0775, dp);
return 1;
case 0:
devdir(c, cqid, "ctl", 0, eve, 0664, dp);
return 1;
}
i--; // for ctl
qid.path = Qfirst + i;
qid.vers = 0;
qid.type = 0;
mp = path2dev(i, 0);
if (mp == nil)
return -1;
kstrcpy(up->genbuf, mp->name, sizeof(up->genbuf));
devdir(c, qid, up->genbuf, mp->size, eve, 0664, dp);
return 1;
}
static Chan*
mattach(char *spec)
{
*confstr = 0;
return devattach(L'k', spec);
}
static Walkqid*
mwalk(Chan *c, Chan *nc, char **name, int nname)
{
if (!configed)
rdconf();
return devwalk(c, nc, name, nname, 0, 0, mgen);
}
static int
mstat(Chan *c, uchar *db, int n)
{
Dir d;
Fsdev *mp;
int p;
p = c->qid.path;
memset(&d, 0, sizeof(d));
switch(p){
case Qtop:
devdir(c, tqid, "#k", 0, eve, DMDIR|0775, &d);
break;
case Qdir:
devdir(c, dqid, "fs", 0, eve, DMDIR|0775, &d);
break;
case Qctl:
devdir(c, cqid, "ctl", 0, eve, 0664, &d);
break;
default:
mp = path2dev(p - Qfirst, 1);
devdir(c, c->qid, mp->name, mp->size, eve, 0664, &d);
}
n = convD2M(&d, db, n);
if (n == 0)
error(Ebadarg);
return n;
}
static Chan*
mopen(Chan *c, int omode)
{
if((c->qid.type & QTDIR) && omode != OREAD)
error(Eperm);
if (omode & OTRUNC)
omode &= ~OTRUNC;
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void
mclose(Chan*)
{
// that's easy
}
static long
catio(Fsdev *mp, int isread, void *a, long n, vlong off)
{
int i;
Chan* mc;
long l, wl, res;
//print("catio %d %p %ld %lld\n", isread, a, n, off);
res = n;
for (i = 0; n >= 0 && i < mp->ndevs ; i++){
mc = mp->idev[i];
if (off > mp->isize[i]){
off -= mp->isize[i];
continue;
}
if (off + n > mp->isize[i])
l = mp->isize[i] - off;
else
l = n;
//print("\tdev %d %p %ld %lld\n", i, a, l, off);
if (isread)
wl = devtab[mc->type]->read(mc, a, l, off);
else
wl = devtab[mc->type]->write(mc, a, l, off);
if (wl != l)
error("#k: write failed");
a = (char*)a + l;
off = 0;
n -= l;
}
//print("\tres %ld\n", res - n);
return res - n;
}
static long
interio(Fsdev *mp, int isread, void *a, long n, vlong off)
{
int i;
Chan* mc;
long l, wl, wsz;
vlong woff, blk, mblk;
long boff, res;
blk = off / Blksize;
boff = off % Blksize;
wsz = Blksize - boff;
res = n;
while(n > 0){
i = blk % mp->ndevs;
mc = mp->idev[i];
mblk = blk / mp->ndevs;
woff = mblk * Blksize + boff;
if (n > wsz)
l = wsz;
else
l = n;
if (isread)
wl = devtab[mc->type]->read(mc, a, l, woff);
else
wl = devtab[mc->type]->write(mc, a, l, woff);
if (wl != l || l == 0)
error(Eio);
a = (char*)a + l;
n -= l;
blk++;
boff = 0;
wsz = Blksize;
}
return res;
}
static long
mread(Chan *c, void *a, long n, vlong off)
{
int i;
Fsdev *mp;
Chan *mc;
long l;
long res;
if (c->qid.type & QTDIR)
return devdirread(c, a, n, 0, 0, mgen);
if (c->qid.path == Qctl)
return readstr((long)off, a, n, confstr + strlen(Cfgstr));
i = c->qid.path - Qfirst;
mp = path2dev(i, 1);
if (off >= mp->size)
return 0;
if (off + n > mp->size)
n = mp->size - off;
if (n == 0)
return 0;
res = -1;
switch(mp->type){
case Fmirror:
for (i = 0; i < mp->ndevs; i++){
mc = mp->idev[i];
if (waserror()){
// if a read fails we let the user know and try
// another device.
print("#k: mread: (%llx %d): %s\n",
c->qid.path, i, up->errstr);
continue;
}
l = devtab[mc->type]->read(mc, a, n, off);
poperror();
if (l >=0){
res = l;
break;
}
}
if (i == mp->ndevs)
error(Eio);
break;
case Fcat:
res = catio(mp, 1, a, n, off);
break;
case Finter:
res = interio(mp, 1, a, n, off);
break;
case Fpart:
off += mp->start;
mc = mp->idev[0];
res = devtab[mc->type]->read(mc, a, n, off);
break;
}
return res;
}
static long
mwrite(Chan *c, void *a, long n, vlong off)
{
Fsdev *mp;
long l, res;
int i;
Chan *mc;
if (c->qid.type & QTDIR)
error(Eperm);
if (c->qid.path == Qctl){
mconfig(a, n);
return n;
}
mp = path2dev(c->qid.path - Qfirst, 1);
if (off >= mp->size)
return 0;
if (off + n > mp->size)
n = mp->size - off;
if (n == 0)
return 0;
res = n;
switch(mp->type){
case Fmirror:
for (i = mp->ndevs-1; i >=0; i--){
mc = mp->idev[i];
l = devtab[mc->type]->write(mc, a, n, off);
if (l < res)
res = l;
}
break;
case Fcat:
res = catio(mp, 0, a, n, off);
break;
case Finter:
res = interio(mp, 0, a, n, off);
break;
case Fpart:
mc = mp->idev[0];
off += mp->start;
l = devtab[mc->type]->write(mc, a, n, off);
if (l < res)
res = l;
break;
}
return res;
}
Dev fsdevtab = {
'k',
"devfs",
devreset,
devinit,
devshutdown,
mattach,
mwalk,
mstat,
mopen,
devcreate,
mclose,
mread,
devbread,
mwrite,
devbwrite,
devremove,
devwstat,
devpower,
devconfig,
};
|