/* * Copyright (c) 2020 Bouffalolab. * * This file is part of * *** Bouffalolab Software Dev Kit *** * (see www.bouffalolab.com). * * 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. * 3. Neither the name of Bouffalo Lab nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 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. */ /* * Some sparse words about how to use the program * * `genromfs' is the `mkfs' equivalent of the other filesystems, but * you must tell it from which directory you want to build the * filesystem. I.e. all files (and directories) in that directory * will be part of the newly created filesystem. Imagine it like * building a cd image, or creating an archive (tar, zip) file. * * Basic usage: * * # genromfs -d rescue/ -f /dev/fd0 * * All files in the rescue directory will be written to /dev/fd0 as a * new romfs filesystem image. You can mount it (if you have the * romfs module loaded, or compiled into the kernel) via: * * # mount -t romfs /dev/fd0 /mnt * * You can also set the volume name of the filesystem (which is not * currently used by the kernel) with the -V option. If you don't * specify one, genromfs will create a volume name of the form: 'rom * xxxxxxxx', where the x's represent the current time in a cryptic * form. * * All in all, it's as simple as: * * # genromfs -d rescue -f testimg.rom -V "Install disk" * * Other options: * -a N force all regular file data to be aligned on N bytes boundary * -A N,/name force named file(s) (shell globbing applied against the filenames) * to be aligned on N bytes boundary * In both cases, N must be a power of two. */ /* * Warning! Quite spaghetti code, it was born in a few hours. * Sorry about that. Feel free to contact me if you have problems. */ #include /* Userland pieces of the ANSI C standard I/O package */ #include /* Userland prototypes of the ANSI C std lib functions */ #include /* Userland prototypes of the string handling funcs */ #include /* Userland prototypes of the Unix std system calls */ #include /* Flag value for file handling functions */ #include #include #include #include #include #include /* Consts & structs defined by the internet system */ /* good old times without autoconf... */ #if defined(__linux__) || defined(__sun__) || defined(__CYGWIN__) #include #endif struct romfh { int32_t nextfh; int32_t spec; int32_t size; int32_t checksum; }; #define ROMFS_MAXFN 128 #define ROMFH_HRD 0 #define ROMFH_DIR 1 #define ROMFH_REG 2 #define ROMFH_LNK 3 #define ROMFH_BLK 4 #define ROMFH_CHR 5 #define ROMFH_SCK 6 #define ROMFH_FIF 7 #define ROMFH_EXEC 8 struct filenode; struct filehdr { /* leave h, t, tp at front, this is a linked list header */ struct filenode *head; struct filenode *tail; struct filenode *tailpred; /* end fragile header */ struct filenode *owner; }; struct filenode { /* leave n, p at front, this is a linked list item */ struct filenode *next; struct filenode *prev; /* end fragile header */ struct filenode *parent; struct filehdr dirlist; struct filenode *orig_link; char *name; char *realname; dev_t ondev; dev_t devnode; ino_t onino; mode_t modes; unsigned int offset; unsigned int size; unsigned int pad; }; struct aligns { struct aligns *next; int align; char pattern[0]; }; struct excludes { struct excludes *next; char pattern[0]; }; void initlist(struct filehdr *fh, struct filenode *owner) { fh->head = (struct filenode *)&fh->tail; fh->tail = NULL; fh->tailpred = (struct filenode *)&fh->head; fh->owner = owner; } int listisempty(struct filehdr *fh) { return fh->head == (struct filenode *)&fh->tail; } void append(struct filehdr *fh, struct filenode *n) { struct filenode *tail = (struct filenode *)&fh->tail; n->next = tail; n->prev = tail->prev; tail->prev = n; n->prev->next =n; n->parent = fh->owner; } void shownode(int level, struct filenode *node, FILE *f) { struct filenode *p; fprintf(f, "%-4d %-20s [0x%-8x, 0x%-8x] %07o, sz %5u, at 0x%-6x", level, node->name, (int)node->ondev, (int)node->onino, node->modes, node->size, node->offset); if (node->orig_link) fprintf(f, " [link to 0x%-6x]", node->orig_link->offset); fprintf(f, "\n"); p = node->dirlist.head; while (p->next) { shownode(level+1, p, f); p = p->next; } } /* Dumping functions */ static char bigbuf[4096]; static char fixbuf[512]; static int atoffs = 0; static int align = 16; struct aligns *alignlist = NULL; struct excludes *excludelist = NULL; int realbase; /* helper function to match an exclusion or align pattern */ int nodematch(char *pattern, struct filenode *node) { char *start = node->name; /* XXX: ugly realbase is global */ if (pattern[0] == '/') start = node->realname + realbase; return fnmatch(pattern,start,FNM_PATHNAME|FNM_PERIOD); } int findalign(struct filenode *node) { struct aligns *pa; int i; if (S_ISREG(node->modes)) i = align; else i = 16; for (pa = alignlist; pa; pa = pa->next) { if (pa->align > i) { if (!nodematch(pa->pattern,node)) i = pa->align; } } return i; } int romfs_checksum(void *data, int size) { int32_t sum, word, *ptr; sum = 0; ptr = data; size>>=2; while (size>0) { word = *ptr++; sum += ntohl(word); size--; } return sum; } void fixsum(struct romfh *ri, int size) { ri->checksum = 0; ri->checksum = htonl(-romfs_checksum(ri, size)); } void dumpdata(void *addr, int len, FILE *f) { int tocopy; struct romfh *ri; if (!len) return; if (atoffs >= 512) { fwrite(addr, len, 1, f); atoffs+=len; return; } tocopy = len < 512-atoffs ? len : 512-atoffs; memcpy(fixbuf+atoffs, addr, tocopy); atoffs+=tocopy; addr=(char*)addr+tocopy; len-=tocopy; if (atoffs==512) { ri = (struct romfh *)&fixbuf; fixsum(ri, atoffssize)?atoffs:ntohl(ri->size)); fwrite(fixbuf, atoffs, 1, f); } if (len) { fwrite(addr, len, 1, f); atoffs+=len; } } void dumpzero(int len, FILE *f) { memset(bigbuf, 0, len); dumpdata(bigbuf, len, f); } void dumpdataa(void *addr, int len, FILE *f) { dumpdata(addr, len, f); if ((len & 15) != 0) dumpzero(16-(len&15), f); } void dumpstring(char *str, FILE *f) { dumpdataa(str, strlen(str)+1, f); } void dumpri(struct romfh *ri, struct filenode *n, FILE *f) { int len; len = strlen(n->name)+1; memcpy(bigbuf, ri, sizeof(*ri)); memcpy(bigbuf+16, n->name, len); if (len&15) { memset(bigbuf+16+len, 0, 16-(len&15)); len += 16-(len&15); } len+=16; ri=(struct romfh *)bigbuf; if (n->offset) fixsum(ri, len); dumpdata(bigbuf, len, f); #if 0 fprintf(stderr, "RI: [at %06x] %08lx, %08lx, %08lx, %08lx [%s]\n", n->offset, ntohl(ri->nextfh), ntohl(ri->spec), ntohl(ri->size), ntohl(ri->checksum), n->name); #endif } void dumpnode(struct filenode *node, FILE *f) { struct romfh ri; struct filenode *p; ri.nextfh = 0; ri.spec = 0; ri.size = htonl(node->size); ri.checksum = htonl(0x55555555); if (node->pad) dumpzero(node->pad, f); if (node->next && node->next->next) ri.nextfh = htonl(node->next->offset); if ((node->modes & 0111) && (S_ISDIR(node->modes) || S_ISREG(node->modes))) ri.nextfh |= htonl(ROMFH_EXEC); if (node->orig_link) { ri.nextfh |= htonl(ROMFH_HRD); /* Don't allow hardlinks to convey attributes */ ri.nextfh &= ~htonl(ROMFH_EXEC); ri.spec = htonl(node->orig_link->offset); dumpri(&ri, node, f); } else if (S_ISDIR(node->modes)) { ri.nextfh |= htonl(ROMFH_DIR); if (listisempty(&node->dirlist)) { ri.spec = htonl(node->offset); } else { ri.spec = htonl(node->dirlist.head->offset); } dumpri(&ri, node, f); } else if (S_ISLNK(node->modes)) { ri.nextfh |= htonl(ROMFH_LNK); dumpri(&ri, node, f); memset(bigbuf, 0, sizeof(bigbuf)); readlink(node->realname, bigbuf, node->size); dumpdataa(bigbuf, node->size, f); } else if (S_ISREG(node->modes)) { int offset, len, fd, max, avail; ri.nextfh |= htonl(ROMFH_REG); dumpri(&ri, node, f); offset = 0; max = node->size; /* XXX warn about size mismatch */ fd = open(node->realname, O_RDONLY #ifdef O_BINARY | O_BINARY #endif ); if (fd) { while(offset < max) { avail = max-offset < sizeof(bigbuf) ? max-offset : sizeof(bigbuf); len = read(fd, bigbuf, avail); if (len <= 0) break; dumpdata(bigbuf, len, f); offset+=len; } close(fd); } max = (max+15)&~15; while (offset < max) { avail = max-offset < sizeof(bigbuf) ? max-offset : sizeof(bigbuf); memset(bigbuf, 0, avail); dumpdata(bigbuf, avail, f); offset+=avail; } } else if (S_ISCHR(node->modes)) { ri.nextfh |= htonl(ROMFH_CHR); ri.spec = htonl(major(node->devnode)<<16|minor(node->devnode)); dumpri(&ri, node, f); } else if (S_ISBLK(node->modes)) { ri.nextfh |= htonl(ROMFH_BLK); ri.spec = htonl(major(node->devnode)<<16|minor(node->devnode)); dumpri(&ri, node, f); } else if (S_ISFIFO(node->modes)) { ri.nextfh |= htonl(ROMFH_FIF); dumpri(&ri, node, f); } else if (S_ISSOCK(node->modes)) { ri.nextfh |= htonl(ROMFH_SCK); dumpri(&ri, node, f); } p = node->dirlist.head; while (p->next) { dumpnode(p, f); p = p->next; } } void dumpall(struct filenode *node, int lastoff, FILE *f) { struct romfh ri; struct filenode *p; ri.nextfh = htonl(0x2d726f6d); ri.spec = htonl(0x3166732d); ri.size = htonl(lastoff); ri.checksum = htonl(0x55555555); dumpri(&ri, node, f); p = node->dirlist.head; while (p->next) { dumpnode(p, f); p = p->next; } /* Align the whole bunch to ROMBSIZE boundary */ if (lastoff&1023) dumpzero(1024-(lastoff&1023), f); } /* Node manipulating functions */ void freenode(struct filenode *n) { /* Rare, not yet */ } void setnode(struct filenode *n, dev_t dev, ino_t ino, mode_t um) { n->ondev = dev; n->onino = ino; n->modes = um; } struct filenode *newnode(const char *base, const char *name, int curroffset) { struct filenode *node; int len; char *str; node = malloc(sizeof (*node)); if (!node) { fprintf(stderr,"out of memory\n"); exit(1); } len = strlen(name); str = malloc(len+1); if (!str) { fprintf(stderr,"out of memory\n"); exit(1); } strcpy(str, name); node->name = str; if (!curroffset) { len = 1; name = "."; } if (strlen(base)) len++; str = malloc(strlen(base)+len+1); if (!str) { fprintf(stderr,"out of memory\n"); exit(1); } if (strlen(base)) { sprintf(str, "%s/%s", base, name); } else { strcpy(str, name); } node->realname = str; node->next = node->prev = NULL; node->parent = NULL; initlist(&node->dirlist, node); node->ondev = -1; node->onino = -1; node->modes = -1; node->size = 0; node->devnode = 0; node->orig_link = NULL; node->offset = curroffset; node->pad = 0; return node; } struct filenode *findnode(struct filenode *node, dev_t dev, ino_t ino) { struct filenode *found, *p; /* scan the whole tree */ if (node->ondev == dev && node->onino == ino) return node; p = node->dirlist.head; while (p->next) { found = findnode(p, dev, ino); if (found) return found; p = p->next; } return NULL; } #define ALIGNUP16(x) (((x)+15)&~15) int spaceneeded(struct filenode *node) { return 16 + ALIGNUP16(strlen(node->name)+1) + ALIGNUP16(node->size); } int alignnode(struct filenode *node, int curroffset, int extraspace) { int align = findalign(node), d; d = ((curroffset + extraspace) & (align - 1)); if (d) { align -= d; curroffset += align; node->offset += align; node->pad = align; } return curroffset; } int processdir(int level, const char *base, const char *dirname, struct stat *sb, struct filenode *dir, struct filenode *root, int curroffset) { DIR *dirfd; struct dirent *dp; struct filenode *n, *link; struct excludes *pe; if (level <= 1) { /* Ok, to make sure . and .. are handled correctly * we add them first. Note also that we alloc them * first to get to know the real name */ link = newnode(base, ".", curroffset); if (!lstat(link->realname, sb)) { setnode(link, sb->st_dev, sb->st_ino, sb->st_mode); append(&dir->dirlist, link); /* special case for root node - '..'s in subdirs should link to * '.' of root node, not root node itself. */ dir->dirlist.owner = link; curroffset = alignnode(link, curroffset, 0) + spaceneeded(link); n = newnode(base, "..", curroffset); if (!lstat(n->realname, sb)) { setnode(n, sb->st_dev, sb->st_ino, sb->st_mode); append(&dir->dirlist, n); n->orig_link = link; curroffset = alignnode(n, curroffset, 0) + spaceneeded(n); } } } dirfd = opendir(dir->realname); if (dirfd == NULL) { perror(dir->realname); } while(dirfd && (dp = readdir(dirfd))) { /* don't process main . and .. twice */ if (level <= 1 && (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)) continue; n = newnode(base, dp->d_name, curroffset); /* Process exclude list. */ for (pe = excludelist; pe; pe = pe->next) { if (!nodematch(pe->pattern, n)) { freenode(n); break; } } if (pe) continue; if (lstat(n->realname, sb)) { fprintf(stderr, "ignoring '%s' (lstat failed)\n", n->realname); freenode(n); continue; } /* Handle special names */ if ( n->name[0] == '@' ) { if (S_ISLNK(sb->st_mode)) { /* this is a link to follow at build time */ n->name = n->name + 1; /* strip off the leading @ */ memset(bigbuf, 0, sizeof(bigbuf)); readlink(n->realname, bigbuf, sizeof(bigbuf)); n->realname = strdup(bigbuf); if (lstat(n->realname, sb)) { fprintf(stderr, "ignoring '%s' (lstat failed)\n", n->realname); freenode(n); continue; } } else if (S_ISREG(sb->st_mode) && sb->st_size == 0) { /* * special file @name,[bcp..],major,minor */ char devname[32]; char type; int major; int minor; if (sscanf(n->name, "@%[a-zA-Z0-9_+-],%c,%d,%d", devname, &type, &major, &minor) == 4 ) { strcpy(n->name, devname); sb->st_rdev = makedev(major, minor); sb->st_mode &= ~S_IFMT; switch (type) { case 'c': case 'u': sb->st_mode |= S_IFCHR; break; case 'b': sb->st_mode |= S_IFBLK; break; case 'p': sb->st_mode |= S_IFIFO; break; default: fprintf(stderr, "Invalid special device type '%c' " "for file %s\n", type, n->realname); freenode(n); continue; } } } } setnode(n, sb->st_dev, sb->st_ino, sb->st_mode); /* Skip unreadable files/dirs */ if (!S_ISLNK(n->modes) && access(n->realname, R_OK)) { fprintf(stderr, "ignoring '%s' (access failed)\n", n->realname); freenode(n); continue; } /* Look up old links */ if ( strcmp(n->name, ".") == 0 ) { append(&dir->dirlist, n); link = n->parent; } else if (strcmp(n->name, "..") == 0) { append(&dir->dirlist, n); link = n->parent->parent; } else { link = findnode(root, n->ondev, n->onino); append(&dir->dirlist, n); } if (link) { n->orig_link = link; curroffset = alignnode(n, curroffset, 0) + spaceneeded(n); continue; } if (S_ISREG(sb->st_mode)) { curroffset = alignnode(n, curroffset, spaceneeded(n)); n->size = sb->st_size; } else curroffset = alignnode(n, curroffset, 0); if (S_ISLNK(sb->st_mode)) { n->size = sb->st_size; } curroffset += spaceneeded(n); if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode)) { n->devnode = sb->st_rdev; } if (S_ISDIR(sb->st_mode)) { if (!strcmp(n->name, "..")) { curroffset = processdir(level+1, dir->realname, dp->d_name, sb, dir, root, curroffset); } else { curroffset = processdir(level+1, n->realname, dp->d_name, sb, n, root, curroffset); } } } if (dirfd) closedir(dirfd); return curroffset; } void showhelp(const char *argv0) { printf("genromfs %s\n",VERSION); printf("Usage: %s [OPTIONS] -f IMAGE\n",argv0); printf("Create a romfs filesystem image from a directory\n"); printf("\n"); printf(" -f IMAGE Output the image into this file\n"); printf(" -d DIRECTORY Use this directory as source\n"); printf(" -v (Too) verbose operation\n"); printf(" -V VOLUME Use the specified volume name\n"); printf(" -a ALIGN Align regular file data to ALIGN bytes\n"); printf(" -A ALIGN,PATTERN Align all objects matching pattern to at least ALIGN bytes\n"); printf(" -x PATTERN Exclude all objects matching pattern\n"); printf(" -h Show this help\n"); printf("\n"); printf("Report bugs to chexum@shadow.banki.hu\n"); } int main(int argc, char *argv[]) { int c; char *dir = "."; char *outf = NULL; char *volname = NULL; int verbose=0; char buf[256]; struct filenode *root; struct stat sb; int lastoff; int i; char *p; struct aligns *pa, *pa2; struct excludes *pe, *pe2; FILE *f; while ((c = getopt(argc, argv, "V:vd:f:ha:A:x:")) != EOF) { switch(c) { case 'd': dir = optarg; break; case 'f': outf = optarg; break; case 'V': volname = optarg; break; case 'v': verbose = 1; break; case 'h': showhelp(argv[0]); exit(0); case 'a': align = strtoul(optarg, NULL, 0); if (align < 16 || (align & (align - 1))) { fprintf(stderr, "Align has to be at least 16 bytes and a power of two\n"); exit(1); } break; case 'A': i = strtoul(optarg, &p, 0); if (i < 16 || (i & (i - 1))) { fprintf(stderr, "Align has to be at least 16 bytes and a power of two\n"); exit(1); } if (*p != ',' || !p[1]) { fprintf(stderr, "-A takes N,PATTERN format of argument, where N is a number\n"); exit(1); } /* strlen(p+1) + 1 eq strlen(p) */ pa = (struct aligns *)malloc(sizeof(*pa) + strlen(p)); pa->align = i; pa->next = NULL; strcpy(pa->pattern, p + 1); if (!alignlist) alignlist = pa; else { for (pa2 = alignlist; pa2->next; pa2 = pa2->next) ; pa2->next = pa; } break; case 'x': pe = (struct excludes *)malloc(sizeof(*pe) + strlen(optarg) + 1); pe->next = NULL; strcpy(pe->pattern, optarg); if (!excludelist) excludelist = pe; else { for (pe2 = excludelist; pe2->next; pe2 = pe2->next) ; pe2->next = pe; } break; default: exit(1); } } if (!volname) { sprintf(buf, "rom %08lx", time(NULL)); volname = buf; } if (!outf) { fprintf(stderr, "%s: you must specify the destination file\n", argv[0]); fprintf(stderr, "Try `%s -h' for more information\n",argv[0]); exit(1); } if (strcmp(outf, "-") == 0) { f = fdopen(1,"wb"); } else f = fopen(outf, "wb"); if (!f) { perror(outf); exit(1); } realbase = strlen(dir); root = newnode(dir, volname, 0); root->parent = root; lastoff = processdir (1, dir, dir, &sb, root, root, spaceneeded(root)); if (verbose) shownode(0, root, stderr); dumpall(root, lastoff, f); exit(0); }