#include "tag.h" Recfields infieldstab[] = { {Rcomment, nil, {Flang, Fstring, Fstrings}}, {Rtext, nil, {Fstrings}}, }; char *filename; int lineno, badlines; void inputerr(char *msg, ...) { char buf[1024], *p; va_list args; p = seprint(buf, buf+sizeof(buf), "%s:%d ", filename, lineno); va_start(args, msg); p = vseprint(p, buf+sizeof(buf), msg, args); p = seprint(p, buf+sizeof(buf), "\n"); va_end(args); write(2, buf, p-buf); badlines++; } char * estrdup(char *s) { char *t; if((t = strdup(s)) == nil) sysfatal("estrdup: %r"); return t; } int move(int fd, vlong dist) { char buf[16*1024]; Dir *dir; vlong i; long n, rdsz; if(dist < 0) abort(); if((dir = dirfstat(fd)) == nil) { werrstr("stat: %r"); return -1; } i = dir->length; rdsz = sizeof buf; while(i > 0) { if(i - rdsz < 0) rdsz = i; i -= rdsz; if((n = pread(fd, buf, rdsz, i)) < 0) { werrstr("read: %r"); return -1; } if(pwrite(fd, buf, n, i + dist) < n) { werrstr("write: %r"); return -1; } } free(dir); return 0; } int reclen(Record *r) { switch(r->type) { case Rcomment: return 1+3+strlen(r->c.description)+1+strlen(r->c.comment); case Rtext: { char **s; int n; for(n=0, s=r->t.strings; *s != nil; s++) n += strlen(*s) + (*(s+1) != nil ? 1 : 0); return 1 + n; } } abort(); return -1; } int checktypes(Recfields *proto, char **fields) { int *p; char **f; for(p=proto->fields, f=fields; *p != Fend; p++, f++) { switch(*p) { case Fenc: if(encnum(*f) < 0) { werrstr("unknown encoding"); return -1; } break; case Flang: if(strlen(*f) != LangSize) { werrstr("language must be %d bytes", LangSize); return -1; } break; case Fstring: case Fstrings: break; default: abort(); } } return 0; } int checkarity(Recfields *proto, int nfields) { int *f; for(f=proto->fields; *f != Fend;) { if(*f++ == Fstrings) { if(nfields >= f - proto->fields) return 0; else goto err; } } if(f - proto->fields == nfields) return 0; err: werrstr("expected %ld fields, saw %d", f - proto->fields, nfields); return 1; } Recfields * infields(int rtype) { Recfields *rf; for(rf=infieldstab; rf < infieldstab + nelem(infieldstab); rf++) { if(rf->type == rtype) return rf; } abort(); return nil; } void usage(void) { fprint(2, "usage: %s file\n", argv0); exits("usage"); } void main(int argc, char **argv) { Biobuf bin; int fd; char *line; char *toks[1+MaxFields+MaxStrings]; /* more than enough */ int ntoks; int rtype; Recfields *rproto; Record **r, *rs[64]; /* arbitrary and nil terminated */ Header oldhdr, hdr; uchar *buf, *p; int padsize, dist; ARGBEGIN { default: usage(); }ARGEND if(argc != 1) usage(); filename = ""; if(Binit(&bin, 0, OREAD) < 0) sysfatal("Binit 0: %r"); if((fd = open(argv[0], ORDWR)) < 0) { if((fd = create(argv[0], ORDWR, 0666)) < 0) sysfatal("create %s: %r", argv[0]); fprint(2, "%s: creating %s\n", argv0, argv[0]); } /* parse each line into a Record and append it to rs */ for(r=rs; (line = Brdline(&bin, '\n')) != nil; r++) { line[Blinelen(&bin)-1] = '\0'; lineno++; if((ntoks = tokenize(line, toks, nelem(toks))) == 0) continue; if(r - rs + 1 > nelem(rs)-1) sysfatal("too many records; limit is %d", nelem(rs)-1); if(strlen(toks[0]) != IdSize) { inputerr("record name must be %d bytes", IdSize); continue; } if((rtype = rectype(toks[0])) < 0) { inputerr("unknown frame type: %s", toks[0]); continue; } rproto = infields(rtype); if(checkarity(rproto, ntoks-1)) { inputerr("%r"); continue; } if(checktypes(rproto, toks+1) < 0) { inputerr("%r"); continue; } *r = emalloc(sizeof **r); (*r)->type = rtype; switch(rtype) { case Rcomment: strcpy((*r)->c.language, toks[1]); (*r)->c.description = estrdup(toks[2]); (*r)->c.comment = estrdup(toks[3]); break; case Rtext: { char *ss[MaxStrings+1], **s; int i, sz; for(i=1, s=ss; i < ntoks; i++, s++) *s = estrdup(toks[i]); *s = nil; sz = sizeof(char *) * (s-ss + 1); (*r)->t.strings = emalloc(sz); memcpy((*r)->t.strings, ss, sz); } break; default: abort(); } strcpy((*r)->id, toks[0]); (*r)->length = reclen(*r); (*r)->flags = 0; } *r = nil; if(badlines) sysfatal("bad input"); strcpy(hdr.magic, "ID3"); hdr.version = 4; hdr.revision = 0; hdr.flags = 0; hdr.length = 0; for(r=rs; *r != nil; r++) hdr.length += RecHdrSize + (*r)->length; padsize = ((((HeaderSize + hdr.length) / PadChunkSize) + 1) * PadChunkSize) - (HeaderSize + hdr.length); if(padsize - (HeaderSize + hdr.length) < MinPadding) padsize += PadChunkSize; buf = emalloc(HeaderSize + hdr.length); if(readn(fd, buf, HeaderSize) < 0) sysfatal("couldn't read header: %r"); p = buf; gheader(&p, &oldhdr); if(strcmp(oldhdr.magic, "ID3") != 0) { dist = HeaderSize + hdr.length + padsize; } else if(hdr.length > oldhdr.length) { dist = HeaderSize + hdr.length + padsize - oldhdr.length; } else { padsize = oldhdr.length - hdr.length; dist = 0; } hdr.length += padsize; free(buf); // fprint(2, "padding %d\n", padsize); if(dist != 0) fprint(2, "dist %d\n", dist); if(dist > 0) { if(move(fd, dist) < 0) sysfatal("move: %r"); } buf = emalloc(HeaderSize + hdr.length); p = buf; pheader(&p, &hdr); for(r=rs; *r != nil; r++) precord(&p, *r); memset(p, 0, padsize); p += padsize; if(pwrite(fd, buf, p-buf, 0) < 0) sysfatal("%r"); exits(nil); }