diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b84afcd --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +ALL=v4lfs + +all: $(ALL) + +v4lfs: fs.o NinePea.o v4l.o jpeg.o + $(CC) -o v4lfs fs.o NinePea.o v4l.o jpeg.o -ljpeg + +clean: + rm -f v4lfs *.o *~ diff --git a/NinePea.c b/NinePea.c new file mode 100644 index 0000000..ea93690 --- /dev/null +++ b/NinePea.c @@ -0,0 +1,485 @@ +#include "NinePea.h" + +struct htable *fs_fids; + +int +puthdr(unsigned char *buf, unsigned long index, unsigned char type, unsigned int tag, unsigned long size) { + put4(buf, index, size); + buf[index++] = type; + put2(buf, index, tag); + + return index; +} + +int +mkerr(unsigned char *buffer, unsigned char tag, char *errstr) { + int index; + int slen = strlen(errstr); + int size = slen + 9; + + index = puthdr(buffer, 0, RError, tag, size); + + put2(buffer, index, slen); + memcpy(&buffer[index], errstr, slen); + + return size; +} + +int +putstat(unsigned char *buffer, unsigned long index, Stat *stat) { + unsigned int namelen = strlen(stat->name); + unsigned int uidlen = strlen(stat->uid); + unsigned int gidlen = strlen(stat->gid); + unsigned int muidlen = strlen(stat->muid); + + unsigned int size = 2 + 4 + (1 + 4 + 8) + 4 + 4 + 4 + 8 + (2 * 4) + namelen + uidlen + gidlen + muidlen; + put2(buffer, index, size); + + put2(buffer, index, stat->type); + put4(buffer, index, stat->dev); + buffer[index++] = stat->qid.type; + put4(buffer, index, stat->qid.version); + put8(buffer, index, stat->qid.path, 0); + put4(buffer, index, stat->mode); + put4(buffer, index, stat->atime); + put4(buffer, index, stat->mtime); + put8(buffer, index, stat->length, 0); + + put2(buffer, index, namelen); + memcpy(&buffer[index], stat->name, namelen); + index += namelen; + + put2(buffer, index, uidlen); + memcpy(&buffer[index], stat->uid, uidlen); + index += uidlen; + + put2(buffer, index, gidlen); + memcpy(&buffer[index], stat->gid, gidlen); + index += gidlen; + + put2(buffer, index, muidlen); + memcpy(&buffer[index], stat->muid, muidlen); + index += muidlen; + + return (size + 2); +} + +int +getstat(unsigned char *buffer, unsigned long index, Stat *stat) { + unsigned int size; + unsigned long tmp; + unsigned int namelen; + unsigned int uidlen; + unsigned int gidlen; + unsigned int muidlen; + + get2(buffer, index, size); + + get2(buffer, index, stat->type); + get4(buffer, index, stat->dev); + buffer[index++] = stat->qid.type; + get4(buffer, index, stat->qid.version); + get8(buffer, index, stat->qid.path, tmp); + get4(buffer, index, stat->mode); + get4(buffer, index, stat->atime); + get4(buffer, index, stat->mtime); + get8(buffer, index, stat->length, tmp); + + get2(buffer, index, namelen); + stat->name = (char*)malloc (sizeof (char) * (namelen + 1)); + stat->name[namelen] = '\0'; + memcpy(stat->name, &buffer[index], namelen); + index += namelen; + + get2(buffer, index, uidlen); + stat->uid = (char*)malloc (sizeof (char) * (uidlen + 1)); + stat->uid[uidlen] = '\0'; + memcpy(stat->uid, &buffer[index], uidlen); + index += uidlen; + + get2(buffer, index, gidlen); + stat->gid = (char*)malloc (sizeof (char) * (gidlen + 1)); + stat->gid[gidlen] = '\0'; + memcpy(stat->gid, &buffer[index], gidlen); + index += gidlen; + + get2(buffer, index, muidlen); + stat->muid = (char*)malloc (sizeof (char) * (muidlen + 1)); + stat->muid[muidlen] = '\0'; + memcpy(stat->muid, &buffer[index], muidlen); + index += muidlen; + + return index; +} + +char Etoobig[] = "TMI"; +char Ebadtype[] = "9P?"; +char Enofile[] = "file not found"; +char Eperm[] = "permission denied"; + +unsigned long +proc9p(unsigned char *msg, unsigned long size, Callbacks *cb) { + Fcall ifcall; + Fcall *ofcall = NULL; + unsigned long slen, tmp; + unsigned long index; + unsigned char i; + + msg[size] = '\0'; + index = 4; + ifcall.type = msg[index++]; + get2(msg, index, ifcall.tag); + + if (size > MAX_MSG) { + return mkerr(msg, ifcall.tag, Etoobig); + } + + // if it isn't here, it isn't implemented + switch(ifcall.type) { + case TVersion: + i = index; + index = 7; + + get4(msg, i, ifcall.msize); + get2(msg, i, slen); + + if (ifcall.msize > MAX_MSG) + ifcall.msize = MAX_MSG; + + put4(msg, index, ifcall.msize); + put2(msg, index, slen); + + index += slen; + puthdr(msg, 0, RVersion, ifcall.tag, index); + + break; + case TAuth: + index = mkerr(msg, ifcall.tag, "no auth"); + break; + case TAttach: + get4(msg, index, ifcall.fid); + get4(msg, index, ifcall.afid); + + get2(msg, index, slen); + index += slen; + + get2(msg, index, slen); + msg[index-2] = '\0'; + ifcall.uname = strdup((char*)&msg[index-2-slen]); + + index += slen; + msg[index] = '\0'; + ifcall.aname = strdup((char*)&msg[index-slen]); + + ofcall = cb->attach(&ifcall); + + free(ifcall.uname); + free(ifcall.aname); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = 7; + msg[index++] = ofcall->qid.type; + put4(msg, index, ofcall->qid.version); + put8(msg, index, ofcall->qid.path, 0); + puthdr(msg, 0, RAttach, ifcall.tag, index); + + break; + case TWalk: + get4(msg, index, ifcall.fid); + get4(msg, index, ifcall.newfid); + get2(msg, index, ifcall.nwname); + + if (ifcall.nwname > MAX_WELEM) + ifcall.nwname = MAX_WELEM; + + get2(msg, index, slen); + for (i = 0; i < ifcall.nwname; i++) { + index += slen; + get2(msg, index, tmp); + msg[index-2] = '\0'; + ifcall.wname[i] = strdup((char*)&msg[index-2-slen]); + slen = tmp; + } + + ofcall = cb->walk(&ifcall); + + for (i = 0; i < ifcall.nwname; i++) + free(ifcall.wname[i]); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RWalk, ifcall.tag, 9 + ofcall->nwqid * 13); + put2(msg, index, ofcall->nwqid); + + for (i = 0; i < ofcall->nwqid; i++) { + msg[index++] = ofcall->wqid[i].type; + put4(msg, index, ofcall->wqid[i].version); + put8(msg, index, ofcall->wqid[i].path, 0); + } + + break; + case TStat: + get4(msg, index, ifcall.fid); + + ofcall = cb->stat(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + slen = putstat(msg, 9, &(ofcall->stat)); + index = puthdr(msg, 0, RStat, ifcall.tag, slen + 9); + put2(msg, index, slen); // bleh? + index += slen; + + break; + case TClunk: + get4(msg, index, ifcall.fid); + + ofcall = cb->clunk(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RClunk, ifcall.tag, 7); + + break; + case TOpen: + get4(msg, index, ifcall.fid); + ifcall.mode = msg[index++]; + + ofcall = cb->open(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, ROpen, ifcall.tag, 24); + msg[index++] = ofcall->qid.type; + put4(msg, index, ofcall->qid.version); + put8(msg, index, ofcall->qid.path, 0); + put4(msg, index, MAX_IO); + + break; + case TRead: + get4(msg, index, ifcall.fid); + get4(msg, index, ifcall.offset); + index += 4; // :( + get4(msg, index, ifcall.count); + + ofcall = cb->read(&ifcall, &msg[11]); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + // No response + if (ofcall == NULL) { + index = 0; + + break; + } + + index = puthdr(msg, 0, RRead, ifcall.tag, 11 + ofcall->count); + put4(msg, index, ofcall->count); + index += ofcall->count; + + break; + case TCreate: + get4(msg, index, ifcall.fid); + get2(msg, index, slen); + ifcall.name = (char*)&msg[index]; + index += slen; + get4(msg, index, ifcall.perm); + msg[index-4] = '\0'; + ifcall.mode = msg[index++]; + + ofcall = cb->create(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RCreate, ifcall.tag, 24); + msg[index++] = ofcall->qid.type; + put4(msg, index, ofcall->qid.version); + put8(msg, index, ofcall->qid.path, 0); + put4(msg, index, MAX_IO); + + break; + case TWrite: + get4(msg, index, ifcall.fid); + get4(msg, index, ifcall.offset); + index += 4; // bleh... again + get4(msg, index, ifcall.count); + + ofcall = cb->write(&ifcall, &msg[index]); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RWrite, ifcall.tag, 11); + put4(msg, index, ofcall->count); + + break; + case TRemove: + get4(msg, index, ifcall.fid); + + ofcall = cb->remove(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RRemove, ifcall.tag, 7); + + break; + case TFlush: + get2(msg, index, ifcall.oldtag); + + ofcall = cb->flush(&ifcall); + + if (ofcall->type == RError) { + index = mkerr(msg, ifcall.tag, ofcall->ename); + + break; + } + + index = puthdr(msg, 0, RFlush, ifcall.tag, 7); + + break; + case TWStat: +#if 0 + get4(msg, index, ifcall.fid); + index = getstat(msg, index, &ifcall.stat); + + ofcall = cb->wstat(&ifcall); + + free (ifcall.stat.name); + free (ifcall.stat.uid); + free (ifcall.stat.gid); + free (ifcall.stat.muid); + + if (ofcall->type == RError) + index = mkerr(msg, ifcall.tag, ofcall->ename); + else + index = puthdr(msg, 0, RWStat, ifcall.tag, 7); + break; +#endif + default: + index = mkerr(msg, ifcall.tag, Ebadtype); + break; + } + + if (index > MAX_MSG) { + index = mkerr(msg, ifcall.tag, Etoobig); + } + + return index; +} + +/* fid mapping functions */ + +unsigned long +hashf(struct htable *tbl, unsigned long id) { + return id % tbl->length; +} + +struct hentry* +fs_fid_find(unsigned long id) { + struct hentry **cur; + + for (cur = &(fs_fids->data[hashf(fs_fids, id)]); + *cur != NULL; cur = &(*cur)->next) { + if ((*cur)->id == id) + break; + } + + return *cur; +} + +struct hentry* +fs_fid_add(unsigned long id, unsigned long data) { + struct hentry *cur = fs_fid_find(id); + unsigned char h; + + if (cur == NULL) { + cur = (struct hentry*)calloc(1, sizeof(*cur)); + cur->id = id; + h = hashf(fs_fids, id); + + if (fs_fids->data[h]) { + cur->next = fs_fids->data[h]; + cur->next->prev = cur; + } + fs_fids->data[h] = cur; + } + + cur->data = data; + + return cur; +} + +void +fs_fid_del(unsigned long id) { + unsigned char h = hashf(fs_fids, id); + struct hentry *cur = fs_fids->data[h]; + + if (cur->id == id) + fs_fids->data[h] = cur->next; + else { + cur = cur->next; + while (cur) { + if (cur->id == id) + break; + + cur = cur->next; + } + } + + if (cur == NULL) { + return; + } + + if (cur->prev) + cur->prev->next = cur->next; + if (cur->next) + cur->next->prev = cur->prev; + + if (cur->aux != NULL) + free(cur->aux); + + free(cur); +} + +void +fs_fid_init(int l) { + fs_fids = (htable*)malloc(sizeof(htable)); + fs_fids->length = l; + fs_fids->data = (hentry**)calloc(fs_fids->length, sizeof(hentry*)); +} diff --git a/NinePea.h b/NinePea.h new file mode 100644 index 0000000..296144e --- /dev/null +++ b/NinePea.h @@ -0,0 +1,238 @@ +#ifndef NINEPEA_H +#define NINEPEA_H + +#include +#include + +#define put2(buffer, index, value) \ + buffer[index++] = value & 0xFF; \ + buffer[index++] = (value >> 8) & 0xFF; \ + +#define put4(buffer, index, value) \ + buffer[index++] = value & 0xFF; \ + buffer[index++] = (value >> 8) & 0xFF; \ + buffer[index++] = (value >> 16) & 0xFF; \ + buffer[index++] = (value >> 24) & 0xFF; \ + +#define put8(buffer, index, lvalue, hvalue) \ + buffer[index++] = lvalue & 0xFF; \ + buffer[index++] = (lvalue >> 8) & 0xFF; \ + buffer[index++] = (lvalue >> 16) & 0xFF; \ + buffer[index++] = (lvalue >> 24) & 0xFF; \ + buffer[index++] = hvalue & 0xFF; \ + buffer[index++] = (hvalue >> 8) & 0xFF; \ + buffer[index++] = (hvalue >> 16) & 0xFF; \ + buffer[index++] = (hvalue >> 24) & 0xFF; \ + +#define get4(buffer, index, value) \ + value = buffer[index++]; \ + value |= buffer[index++] << 8; \ + value |= buffer[index++] << 16; \ + value |= buffer[index++] << 24; \ + +#define get8(buffer, index, lvalue, hvalue) \ + get4(buffer, index, lvalue); \ + get4(buffer, index, hvalue); \ + +#define get2(buffer, index, value) \ + value = buffer[index++]; \ + value |= buffer[index++] << 8; \ + +// might have to change these depending on memory allocated +#define MAX_IO 8192 +#define MAX_MSG 8192+128 +#define MAX_WELEM 16 +#define MAX_PGMBUF 80 +#define NOTAG ~0 + +/* 9P message types */ +enum { + TVersion = 'd', + RVersion, + TAuth = 'f', + RAuth, + TAttach = 'h', + RAttach, + TError = 'j', /* illegal */ + RError, + TFlush = 'l', + RFlush, + TWalk = 'n', + RWalk, + TOpen = 'p', + ROpen, + TCreate = 'r', + RCreate, + TRead = 't', + RRead, + TWrite = 'v', + RWrite, + TClunk = 'x', + RClunk, + TRemove = 'z', + RRemove, + TStat = '|', + RStat, + TWStat = '~', + RWStat, +}; + +/* bits in Qid.type */ +enum { + QTDIR = 0x80, /* type bit for directories */ + QTAPPEND = 0x40, /* type bit for append only files */ + QTEXCL = 0x20, /* type bit for exclusive use files */ + QTMOUNT = 0x10, /* type bit for mounted channel */ + QTAUTH = 0x08, /* type bit for authentication file */ + QTTMP = 0x04, /* type bit for non-backed-up file */ + QTSYMLINK = 0x02, /* type bit for symbolic link */ + QTFILE = 0x00 /* type bits for plain file */ +}; + +/* from libc.h in p9p */ +enum { + OREAD = 0, /* open for read */ + OWRITE = 1, /* write */ + ORDWR = 2, /* read and write */ + OEXEC = 3, /* execute, == read but check execute permission */ + OTRUNC = 16, /* or'ed in (except for exec), truncate file first */ + OCEXEC = 32, /* or'ed in, close on exec */ + ORCLOSE = 64, /* or'ed in, remove on close */ + ODIRECT = 128, /* or'ed in, direct access */ + ONONBLOCK = 256, /* or'ed in, non-blocking call */ + OEXCL = 0x1000, /* or'ed in, exclusive use (create only) */ + OLOCK = 0x2000, /* or'ed in, lock after opening */ + OAPPEND = 0x4000 /* or'ed in, append only */ +}; + +/* Larger than int, can't be enum */ +#define DMDIR 0x80000000 /* mode bit for directories */ +#define DMAPPEND 0x40000000 /* mode bit for append only files */ +#define DMEXCL 0x20000000 /* mode bit for exclusive use files */ +#define DMMOUNT 0x10000000 /* mode bit for mounted channel */ +#define DMAUTH 0x08000000 /* mode bit for authentication file */ +#define DMTMP 0x04000000 /* mode bit for non-backed-up file */ + +typedef struct { + unsigned char type; + unsigned long version; + unsigned long path; +} Qid; + +typedef struct { + unsigned int type; + unsigned long dev; + Qid qid; + unsigned long mode; + unsigned long atime; + unsigned long mtime; + unsigned long length; + char *name; + char *uid; + char *gid; + char *muid; +} Stat; + +typedef struct { + unsigned char type; + unsigned long tag; + unsigned long fid; + + union { + struct {/* Tversion, Rversion */ + unsigned long msize; +// char *version; + }; + struct { /* Tflush */ + unsigned int oldtag; + }; + struct { /* Rerror */ + char *ename; + }; + struct { /* Ropen, Rcreate */ + Qid qid; /* +Rattach */ +// unsigned long iounit; + }; + struct { /* Rauth */ + Qid aqid; + }; + struct { /* Tauth, Tattach */ + unsigned long afid; + char *uname; + char *aname; + }; + struct { /* Tcreate */ + unsigned long perm; + char *name; + unsigned char mode; /* +Topen */ + }; + struct { /* Twalk */ + unsigned long newfid; + unsigned int nwname; + char *wname[MAX_WELEM]; + }; + struct { /* Rwalk */ + unsigned int nwqid; + Qid wqid[MAX_WELEM]; + }; + struct { + unsigned long offset; /* Tread, Twrite */ + unsigned long count; /* Tread, Twrite, Rread */ +// char *data; /* Twrite, Rread */ + }; + struct { /* Rstat */ + unsigned long nstat; + Stat stat; + }; + struct { /* Twstat */ + Stat st; + }; + }; +} Fcall; + +typedef struct { +// Fcall* (*version)(Fcall*); +// Fcall* (*auth)(Fcall*); + Fcall* (*attach)(Fcall*); + Fcall* (*flush)(Fcall*); + Fcall* (*walk)(Fcall*); + Fcall* (*open)(Fcall*); + Fcall* (*create)(Fcall*); + Fcall* (*read)(Fcall*, unsigned char*); + Fcall* (*write)(Fcall*, unsigned char*); + Fcall* (*clunk)(Fcall*); + Fcall* (*remove)(Fcall*); + Fcall* (*stat)(Fcall*); + Fcall* (*wstat)(Fcall*); +} Callbacks; + +int putstat(unsigned char *buffer, unsigned long index, Stat *stat); +unsigned long proc9p(unsigned char *msg, unsigned long size, Callbacks *cb); + +/* fid mapping functions */ + +typedef struct hentry { + unsigned long id; + unsigned long data; + void *aux; + int len; + struct hentry *next; + struct hentry *prev; +} hentry; + +typedef struct htable { + unsigned char length; + struct hentry **data; +} htable; + +struct hentry* fs_fid_find(unsigned long id); +struct hentry* fs_fid_add(unsigned long id, unsigned long data); +void fs_fid_del(unsigned long id); +void fs_fid_init(int l); + +extern char Etoobig[]; +extern char Ebadtype[]; +extern char Enofile[]; +extern char Eperm[]; + +#endif diff --git a/fs.c b/fs.c new file mode 100644 index 0000000..9f5eb37 --- /dev/null +++ b/fs.c @@ -0,0 +1,324 @@ +#include +#include +#include "NinePea.h" +#include "v4l.h" +#include "jpeg.h" + +Fcall ofcall; +char errstr[64]; +char Snone[] = "none"; +char Sroot[] = "/"; +char Sjpeg[] = "jpeg"; + +enum { + Qroot = 0, + Qjpeg, + QNUM, +}; + +/* 9p handlers */ + +Fcall* +fs_attach(Fcall *ifcall) { + ofcall.qid.type = QTDIR | QTTMP; + ofcall.qid.version = 0; + ofcall.qid.path = Qroot; + + fs_fid_add(ifcall->fid, Qroot); + + return &ofcall; +} + +Fcall* +fs_walk(Fcall *ifcall) { + unsigned long path; + struct hentry *ent = fs_fid_find(ifcall->fid); + int i; + + if (!ent) { + ofcall.type = RError; + ofcall.ename = Enofile; + + return &ofcall; + } + + path = ent->data; + + for (i = 0; i < ifcall->nwname; i++) { + switch(ent->data) { + case Qroot: + if (!strcmp(ifcall->wname[i], "jpeg")) { + ofcall.wqid[i].type = QTFILE; + ofcall.wqid[i].version = 0; + ofcall.wqid[i].path = path = Qjpeg; + } + else if (!strcmp(ifcall->wname[i], ".")) { + ofcall.wqid[i].type = QTDIR; + ofcall.wqid[i].version = 0; + ofcall.wqid[i].path = path = Qroot; + } + else { + ofcall.type = RError; + ofcall.ename = Enofile; + return &ofcall; + } + break; + default: + ofcall.type = RError; + ofcall.ename = Enofile; + + return &ofcall; + break; + } + } + + ofcall.nwqid = i; + + if (fs_fid_find(ifcall->newfid) != NULL) { + ofcall.type = RError; + strcpy(errstr, "new fid exists"); + ofcall.ename = errstr; + return &ofcall; + } + + fs_fid_add(ifcall->newfid, path); + + return &ofcall; +} + +Fcall* +fs_stat(Fcall *ifcall) { + struct hentry *ent; + + if ((ent = fs_fid_find(ifcall->fid)) == NULL) { + ofcall.type = RError; + ofcall.ename = Enofile; + + return &ofcall; + } + + ofcall.stat.qid.type = QTTMP; + ofcall.stat.mode = 0666 | DMTMP; + ofcall.stat.atime = ofcall.stat.mtime = ofcall.stat.length = 0; + ofcall.stat.uid = Snone; + ofcall.stat.gid = Snone; + ofcall.stat.muid = Snone; + + switch (ent->data) { + case Qroot: + ofcall.stat.qid.type |= QTDIR; + ofcall.stat.qid.path = Qroot; + ofcall.stat.mode |= 0111 | DMDIR; + ofcall.stat.name = Sroot; + break; + case Qjpeg: + ofcall.stat.qid.path = Qjpeg; + ofcall.stat.name = Sjpeg; + break; + } + + return &ofcall; +} + +Fcall* +fs_clunk(Fcall *ifcall) { + fs_fid_del(ifcall->fid); + + return ifcall; +} + +Fcall* +fs_open(Fcall *ifcall) { + int len; + struct hentry *cur = fs_fid_find(ifcall->fid); + + if (cur == NULL) { + ofcall.type = RError; + ofcall.ename = Enofile; + + return &ofcall; + } + + ofcall.qid.type = QTFILE; + ofcall.qid.path = cur->data; + + if (cur->data == Qroot) + ofcall.qid.type = QTDIR; + if (cur->data == Qjpeg) { + read_frame(); + len = compressjpg(RGB, 640, 480); + cur->aux = malloc(len); + memcpy(cur->aux, RGB, len); + cur->len = len; + } + + return &ofcall; +} + +Fcall* +fs_read(Fcall *ifcall, unsigned char *out) { + struct hentry *cur = fs_fid_find(ifcall->fid); + Stat stat; + char tmpstr[32]; + unsigned int i; + unsigned long value; + + if (cur == NULL) { + ofcall.type = RError; + ofcall.ename = Enofile; + } + else if (((unsigned long)cur->data) == Qroot) { + if (ifcall->offset == 0) { + stat.type = 0; + stat.dev = 0; + stat.qid.type = QTFILE; + stat.mode = 0666; + stat.atime = 0; + stat.mtime = 0; + stat.length = 0; + + stat.qid.path = Qjpeg; + stat.name = Sjpeg; + stat.uid = Snone; + stat.gid = Snone; + stat.muid = Snone; + ofcall.count = putstat(out, 0, &stat); + } + } + else if (((unsigned long)cur->data) == Qjpeg) { + i = cur->len - ifcall->offset; + if (i > MAX_IO) + i = MAX_IO; + if (i > 0) + memcpy(out, &((unsigned char*)cur->aux)[ifcall->offset], i); + ofcall.count = i; + } + else { + ofcall.type = RError; + ofcall.ename = Enofile; + } + + return &ofcall; +} + +Fcall* +fs_create(Fcall *ifcall) { + ofcall.type = RError; + ofcall.ename = Eperm; + + return &ofcall; +} + +Fcall* +fs_write(Fcall *ifcall, unsigned char *in) { + struct hentry *cur = fs_fid_find(ifcall->fid); + char *ep = &in[ifcall->count]; + + ofcall.count = ifcall->count; + + if (cur == NULL) { + ofcall.type = RError; + ofcall.ename = Enofile; + } + else if (((unsigned long)cur->data) == Qroot) { + ofcall.type = RError; + ofcall.ename = Eperm; + } + else { + ofcall.type = RError; + ofcall.ename = Eperm; + } + + return &ofcall; +} + +Fcall* +fs_remove(Fcall *ifcall) { + ofcall.type = RError; + ofcall.ename = Eperm; + + return &ofcall; +} + +Fcall* +fs_flush(Fcall *ifcall) { + return ifcall; +} + +Fcall* +fs_wstat(Fcall *ifcall) { + return ifcall; +} + +Callbacks callbacks; + +void +sysfatal(int code) +{ + fprintf(stderr, "sysfatal: %d\n", code); + exit(code); +} + +int main() { + fs_fid_init(64); + + // this is REQUIRED by proc9p (see below) + callbacks.attach = fs_attach; + callbacks.flush = fs_flush; + callbacks.walk = fs_walk; + callbacks.open = fs_open; + callbacks.create = fs_create; + callbacks.read = fs_read; + callbacks.write = fs_write; + callbacks.clunk = fs_clunk; + callbacks.remove = fs_remove; + callbacks.stat = fs_stat; + callbacks.wstat = fs_wstat; + + unsigned char msg[MAX_MSG+1]; + unsigned int msglen = 0; + unsigned int r = 0; + + dev_name = "/dev/video0"; + + open_device(); + init_device(); + start_capturing(); + + for(;;){ + unsigned long i; + + while (r < 5) { + msg[r++] = getchar(); + } + + i = 0; + get4(msg, i, msglen); + + // sanity check + if (msg[i] & 1 || msglen > MAX_MSG || msg[i] < TVersion || msg[i] > TWStat) { + sysfatal(3); + } + + while (r < msglen) { + msg[r++] = getchar(); + } + + memset(&ofcall, 0, sizeof(ofcall)); + + // proc9p accepts valid 9P msgs of length msglen, + // processes them using callbacks->various(functions); + // returns variable out's msglen + msglen = proc9p(msg, msglen, &callbacks); + + write(1, msg, msglen); + fflush(stdout); + + r = msglen = 0; + } + + stop_capturing(); + uninit_device(); + close_device(); +} + diff --git a/jpeg.c b/jpeg.c new file mode 100644 index 0000000..5f0ce87 --- /dev/null +++ b/jpeg.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +int +compressjpg(unsigned char *image, int width, int height) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; + unsigned char *buf; + size_t buflen = 0; + int length; + + cinfo.err = jpeg_std_error( &jerr ); + jpeg_create_compress(&cinfo); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = 3; + cinfo.in_color_space = 3; + + length = width * height * 3; + + jpeg_mem_dest( &cinfo, &buf, &buflen ); + jpeg_set_defaults( &cinfo ); + + jpeg_start_compress( &cinfo, TRUE ); + while( cinfo.next_scanline < cinfo.image_height ) { + row_pointer[0] = &image[ cinfo.next_scanline * cinfo.image_width * cinfo.input_components]; + + jpeg_write_scanlines( &cinfo, row_pointer, 1 ); + } + + jpeg_finish_compress( &cinfo ); + + if (buflen < length) + length = buflen; + memcpy(image, buf, length); + + jpeg_destroy_compress( &cinfo ); + free(buf); + + return length; +} + diff --git a/jpeg.h b/jpeg.h new file mode 100644 index 0000000..3c36992 --- /dev/null +++ b/jpeg.h @@ -0,0 +1,2 @@ +int compressjpg(unsigned char *, int, int); + diff --git a/v4l.c b/v4l.c new file mode 100644 index 0000000..f59af0b --- /dev/null +++ b/v4l.c @@ -0,0 +1,703 @@ +/* + * V4L2 video capture example + * + * This program can be used and distributed without restrictions. + * + * This program is provided with the V4L2 API + * see http://linuxtv.org/docs.php for more information + */ + +#include +#include +#include +#include + +#include /* getopt_long() */ + +#include /* low-level i/o */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +enum io_method { + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR, +}; + +struct buffer { + void *start; + size_t length; +}; + +char *dev_name; +char *rgbbuffer; +static enum io_method io = IO_METHOD_MMAP; +static int fd = -1; +struct buffer *buffers; +static unsigned int n_buffers; +static int out_buf; +static int force_format = 1; +static int frame_count = 70; +unsigned char *RGB; + +static void errno_exit(const char *s) +{ + fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); + exit(EXIT_FAILURE); +} + +static int xioctl(int fh, unsigned long int request, void *arg) +{ + int r; + + do { + r = ioctl(fh, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +int CLIPVALUE(int x) { + if (x < 0) + return 0; + else if (x > 255) + return 255; + return x; +} + +void YUV2RGB(const unsigned char y, const unsigned char u,const unsigned char v, unsigned char* r, + unsigned char* g, unsigned char* b) +{ + const int y2 = (int)y; + const int u2 = (int)u - 128; + const int v2 = (int)v - 128; + //std::cerr << "YUV=("<> 16); + // int g2 = y2 - ( ((u2*22544) + (v2*46793)) >> 16 ); + // int b2 = y2 + ( (u2*115999) >> 16); + // This is an adjusted version (UV spread out a bit) + + int r2 = y2 + ((v2 * 37221) >> 15); + int g2 = y2 - (((u2 * 12975) + (v2 * 18949)) >> 15); + int b2 = y2 + ((u2 * 66883) >> 15); + //std::cerr << " RGB=("< 0) { + for (;;) { + fd_set fds; + struct timeval tv; + int r; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + /* Timeout. */ + tv.tv_sec = 2; + tv.tv_usec = 0; + + r = select(fd + 1, &fds, NULL, NULL, &tv); + + if (-1 == r) { + if (EINTR == errno) + continue; + errno_exit("select"); + } + + if (0 == r) { + fprintf(stderr, "select timeout\n"); + exit(EXIT_FAILURE); + } + + if (read_frame()) + break; + /* EAGAIN - continue select loop. */ + } + } +} + +void stop_capturing(void) +{ + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) + errno_exit("VIDIOC_STREAMOFF"); + break; + } +} + +void start_capturing(void) +{ + unsigned int i; + enum v4l2_buf_type type; + + switch (io) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf; + + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)buffers[i].start; + buf.length = buffers[i].length; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + errno_exit("VIDIOC_QBUF"); + } + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) + errno_exit("VIDIOC_STREAMON"); + break; + } +} + +void uninit_device(void) +{ + unsigned int i; + + switch (io) { + case IO_METHOD_READ: + free(buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i = 0; i < n_buffers; ++i) + if (-1 == munmap(buffers[i].start, buffers[i].length)) + errno_exit("munmap"); + break; + + case IO_METHOD_USERPTR: + for (i = 0; i < n_buffers; ++i) + free(buffers[i].start); + break; + } + + free(buffers); +} + +static void init_read(unsigned int buffer_size) +{ + buffers = calloc(1, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + buffers[0].length = buffer_size; + buffers[0].start = malloc(buffer_size); + + if (!buffers[0].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } +} + +static void init_mmap(void) +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, "%s does not support " + "memory mapping\n", dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + if (req.count < 2) { + fprintf(stderr, "Insufficient buffer memory on %s\n", + dev_name); + exit(EXIT_FAILURE); + } + + buffers = calloc(req.count, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; + + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) + errno_exit("VIDIOC_QUERYBUF"); + + buffers[n_buffers].length = buf.length; + buffers[n_buffers].start = + mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + fd, buf.m.offset); + + if (MAP_FAILED == buffers[n_buffers].start) + errno_exit("mmap"); + } +} + +static void init_userp(unsigned int buffer_size) +{ + struct v4l2_requestbuffers req; + + CLEAR(req); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + fprintf(stderr, "%s does not support " + "user pointer i/o\n", dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_REQBUFS"); + } + } + + buffers = calloc(4, sizeof(*buffers)); + + if (!buffers) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + + for (n_buffers = 0; n_buffers < 4; ++n_buffers) { + buffers[n_buffers].length = buffer_size; + buffers[n_buffers].start = malloc(buffer_size); + + if (!buffers[n_buffers].start) { + fprintf(stderr, "Out of memory\n"); + exit(EXIT_FAILURE); + } + } +} + +void init_device(void) +{ + struct v4l2_capability cap; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { + if (EINVAL == errno) { + fprintf(stderr, "%s is no V4L2 device\n", + dev_name); + exit(EXIT_FAILURE); + } else { + errno_exit("VIDIOC_QUERYCAP"); + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + fprintf(stderr, "%s is no video capture device\n", + dev_name); + exit(EXIT_FAILURE); + } + + switch (io) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + fprintf(stderr, "%s does not support read i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + fprintf(stderr, "%s does not support streaming i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + } + + + /* Select video input, video standard and tune here. */ + + + CLEAR(cropcap); + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ + + if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } + + + CLEAR(fmt); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (force_format) { + fmt.fmt.pix.width = 640; + fmt.fmt.pix.height = 480; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) + errno_exit("VIDIOC_S_FMT"); + + /* Note VIDIOC_S_FMT may change width and height. */ + } else { + /* Preserve original settings as set by v4l2-ctl for example */ + if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) + errno_exit("VIDIOC_G_FMT"); + } + + switch (io) { + case IO_METHOD_READ: + init_read(fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + init_mmap(); + break; + + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } +} + +void close_device(void) +{ + if (-1 == close(fd)) + errno_exit("close"); + + fd = -1; + + free(RGB); +} + +void open_device(void) +{ + struct stat st; + + if (-1 == stat(dev_name, &st)) { + fprintf(stderr, "Cannot identify '%s': %d, %s\n", + dev_name, errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!S_ISCHR(st.st_mode)) { + fprintf(stderr, "%s is no device\n", dev_name); + exit(EXIT_FAILURE); + } + + fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); + + if (-1 == fd) { + fprintf(stderr, "Cannot open '%s': %d, %s\n", + dev_name, errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + RGB = (unsigned char*)malloc(1920 * 1080 * 3); +} + +static void usage(FILE *fp, int argc, char **argv) +{ + fprintf(fp, + "Usage: %s [options]\n\n" + "Version 1.3\n" + "Options:\n" + "-d | --device name Video device name [%s]\n" + "-h | --help Print this message\n" + "-m | --mmap Use memory mapped buffers [default]\n" + "-r | --read Use read() calls\n" + "-u | --userp Use application allocated buffers\n" + "-o | --output Outputs stream to stdout\n" + "-f | --format Force format to 640x480 YUYV\n" + "-c | --count Number of frames to grab [%i]\n" + "", + argv[0], dev_name, frame_count); +} + +static const char short_options[] = "d:hmruofc:"; + +static const struct option +long_options[] = { + { "device", required_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "mmap", no_argument, NULL, 'm' }, + { "read", no_argument, NULL, 'r' }, + { "userp", no_argument, NULL, 'u' }, + { "output", no_argument, NULL, 'o' }, + { "format", no_argument, NULL, 'f' }, + { "count", required_argument, NULL, 'c' }, + { 0, 0, 0, 0 } +}; + +/* +int main(int argc, char **argv) +{ + dev_name = "/dev/video0"; + + for (;;) { + int idx; + int c; + + c = getopt_long(argc, argv, + short_options, long_options, &idx); + + if (-1 == c) + break; + + switch (c) { + case 0: // getopt_long() flag + break; + + case 'd': + dev_name = optarg; + break; + + case 'h': + usage(stdout, argc, argv); + exit(EXIT_SUCCESS); + + case 'm': + io = IO_METHOD_MMAP; + break; + + case 'r': + io = IO_METHOD_READ; + break; + + case 'u': + io = IO_METHOD_USERPTR; + break; + + case 'o': + out_buf++; + break; + + case 'f': + force_format++; + break; + + case 'c': + errno = 0; + frame_count = strtol(optarg, NULL, 0); + if (errno) + errno_exit(optarg); + break; + + default: + usage(stderr, argc, argv); + exit(EXIT_FAILURE); + } + } + + open_device(); + init_device(); + start_capturing(); + mainloop(); + stop_capturing(); + uninit_device(); + close_device(); + fprintf(stderr, "\n"); + return 0; +} +*/ diff --git a/v4l.h b/v4l.h new file mode 100644 index 0000000..14b468e --- /dev/null +++ b/v4l.h @@ -0,0 +1,13 @@ +extern char *dev_name; +extern unsigned char *RGB; + +void open_device(); +void init_device(); +void start_capturing(); +void stop_capturing(); +void uninit_device(); +void close_device(); + +int read_frame(); + +