/* writeisofs - simple ISO9660-format-image writing utility */ #include #include #include #include #include #include #include #include #include #include #include #define Writefield(fd, f) Write(fd, &(f), sizeof(f)) extern char *optarg; extern int optind; typedef unsigned char u_int8_t; typedef unsigned short int u_int16_t; typedef unsigned long int u_int32_t; #ifndef min #define min(a,b) ((a) < (b) ? (a) : (b)) #endif #define FLAG_DIR 2 #include #include #define NAMELEN (DIRSIZ+5) #define ISONAMELEN 12 #define PLATFORM_80X86 0 #define ISO_SECTOR 2048 #define VIRTUAL_SECTOR 512 #define CURRENTDIR "." #define PARENTDIR ".." /* *** CD (disk) data structures ********************* */ /* primary volume descriptor */ struct pvd { u_int8_t one; char set[6]; u_int8_t zero; char system[32]; char volume[32]; u_int8_t zeroes1[8]; u_int32_t sectors[2]; u_int8_t zeroes2[32]; u_int16_t setsize[2]; u_int16_t seq[2]; u_int16_t sectorsize[2]; u_int32_t pathtable[2]; u_int32_t first_little_pathtable_start; u_int32_t second_little_pathtable_start; u_int32_t first_big_pathtable_start; u_int32_t second_big_pathtable_start; u_int8_t rootrecord[34]; u_int8_t volumeset[128]; u_int8_t publisher[128]; u_int8_t preparer[128]; u_int8_t application[128]; u_int8_t copyrightfile[37]; u_int8_t abstractfile[37]; u_int8_t bibliofile[37]; u_int8_t create[17]; u_int8_t modified[17]; char expiry[17]; u_int8_t effective[17]; u_int8_t one2; u_int8_t zero2; u_int8_t zeroes3[512]; u_int8_t zeroes4[653]; }; /* boot record volume descriptor */ struct bootrecord { u_int8_t indicator; /* 0 */ char set[5]; /* "CD001" */ u_int8_t version; /* 1 */ char ident[32]; /* "EL TORITO SPECIFICATION" */ u_int8_t zero[32]; /* unused, must be 0 */ u_int32_t bootcatalog; /* starting sector of boot catalog */ u_int8_t zero2[1973]; /* unused, must be 0 */ }; /* boot catalog validation entry */ struct bc_validation { u_int8_t headerid; /* 1 */ u_int8_t platform; /* 0: 80x86; 1: powerpc; 2: mac */ u_int8_t zero[2]; /* unused, must be 0 */ char idstring[24]; /* id string */ u_int16_t checksum; u_int8_t keys[2]; /* 0x55AA */ }; /* boot catalog initial/default entry */ #define INDICATE_BOOTABLE 0x88 #define BOOTMEDIA_NONE 0 #define BOOTMEDIA_120M 1 #define BOOTMEDIA_144M 2 #define BOOTMEDIA_288M 3 #define BOOTMEDIA_HARDDISK 4 struct bc_initial { u_int8_t indicator; /* INDICATE_BOOTABLE */ u_int8_t media; /* BOOTMEDIA_* */ u_int16_t seg; /* load segment or 0 for default */ u_int8_t type; /* system type (from part. table) */ u_int8_t zero; u_int16_t sectors; u_int32_t startsector; u_int8_t zero2[20]; }; /* directory entry */ struct dir { u_int8_t recordsize; u_int8_t extended; u_int32_t datasector[2]; u_int32_t filesize[2]; u_int8_t year; u_int8_t month; u_int8_t day; u_int8_t hour; u_int8_t minute; u_int8_t second; u_int8_t offset; u_int8_t flags; u_int8_t interleaved; u_int8_t interleavegap; u_int16_t sequence[2]; u_int8_t namelen; char name[NAMELEN]; }; /* *** program (memory) data structures ********************* */ struct node { char name[NAMELEN]; int isdir; int pathtablerecord; struct node *firstchild, *nextchild; /* filled out at i/o time */ u_int32_t startsector, bytesize; }; int n_reserved_pathtableentries = 0, n_used_pathtableentries = 0; int harddisk_emulation = 0; int system_type = 0; int get_system_type(int fd); ssize_t Write(int fd, void *buf, ssize_t len) { ssize_t r; if((r=write(fd, buf, len)) != len) { if(r < 0) { perror("write"); } fprintf(stderr, "failed or short write - aborting.\n"); exit(1); } return len; } off_t Lseek(int fd, off_t pos, int rel) { off_t r; if((r=lseek(fd, pos, rel)) < 0) { perror("lseek"); fprintf(stderr, "lseek failed - aborting.\n"); exit(1); } return r; } void writesector(int fd, char *block, int *currentsector) { Write(fd, block, ISO_SECTOR); (*currentsector)++; return; } void seeksector(int fd, int sector, int *currentsector) { Lseek(fd, sector*ISO_SECTOR, SEEK_SET); *currentsector = sector; } void seekwritesector(int fd, int sector, char *block, int *currentsector) { seeksector(fd, sector, currentsector); writesector(fd, block, currentsector); } ssize_t Read(int fd, void *buf, ssize_t len) { ssize_t r; if((r=read(fd, buf, len)) != len) { if(r < 0) { perror("read"); } fprintf(stderr, "failed or short read.\n"); exit(1); } return len; } void both16(unsigned char *both, unsigned short i16) { unsigned char *little, *big; little = both; big = both + 2; little[0] = big[1] = i16 & 0xFF; little[1] = big[0] = (i16 >> 8) & 0xFF; } void both32(unsigned char *both, unsigned long i32) { unsigned char *little, *big; little = both; big = both + 4; little[0] = big[3] = i32 & 0xFF; little[1] = big[2] = (i32 >> 8) & 0xFF; little[2] = big[1] = (i32 >> 16) & 0xFF; little[3] = big[0] = (i32 >> 24) & 0xFF; } #define MINDIRLEN 1 #define MAXDIRLEN 31 #define MAXLEVEL 8 static int cmpf(const void *v1, const void *v2) { struct node *n1, *n2; int i; char f1[NAMELEN], f2[NAMELEN]; n1 = (struct node *) v1; n2 = (struct node *) v2; strcpy(f1, n1->name); strcpy(f2, n2->name); for(i = 0; i < strlen(f1); i++) f1[i] = toupper(f1[i]); for(i = 0; i < strlen(f2); i++) f2[i] = toupper(f2[i]); return -strcmp(f1, f2); } void maketree(struct node *thisdir, char *name, int level) { DIR *dir; struct dirent *e; struct node *dirnodes = NULL; int reserved_dirnodes = 0, used_dirnodes = 0; struct node *child; thisdir->firstchild = NULL; thisdir->isdir = 1; thisdir->startsector = 0xdeadbeef; if(level >= MAXLEVEL) { fprintf(stderr, "ignoring entries in %s (too deep for iso9660)\n", name); return; } if(!(dir = opendir(CURRENTDIR))) { perror("opendir"); return; } /* how many entries do we need to allocate? */ while(readdir(dir)) reserved_dirnodes++; if(!reserved_dirnodes) { closedir(dir); return; } if(!(dirnodes = malloc(sizeof(*dirnodes)*reserved_dirnodes))) { fprintf(stderr, "couldn't allocate dirnodes (%d bytes)\n", sizeof(*dirnodes)*reserved_dirnodes); exit(1); } /* remember all entries in this dir */ rewinddir(dir); child = dirnodes; while((e=readdir(dir))) { struct stat st; mode_t type; if(!strcmp(e->d_name, CURRENTDIR) || !strcmp(e->d_name, PARENTDIR)) continue; if(stat(e->d_name, &st) < 0) { perror(e->d_name); fprintf(stderr, "failed to stat file/dir\n"); exit(1); } type = st.st_mode & S_IFMT; /* printf("%s type: %x dir: %x file: %x\n", e->d_name, type, S_IFDIR, S_IFREG); */ if(type != S_IFDIR && type != S_IFREG) continue; used_dirnodes++; if(used_dirnodes > reserved_dirnodes) { fprintf(stderr, "huh, directory entries appeared " "(not enough pre-allocated nodes; this can't happen) ?\n"); exit(1); } if(type == S_IFDIR) { child->isdir = 1; } else { child->isdir = 0; child->firstchild = NULL; } strncpy(child->name, e->d_name, sizeof(child->name)); child++; } closedir(dir); if(!used_dirnodes) return; if(!(dirnodes=realloc(dirnodes, used_dirnodes*sizeof(*dirnodes)))) { fprintf(stderr, "realloc() of dirnodes failed - aborting\n"); exit(1); } qsort(dirnodes, used_dirnodes, sizeof(*dirnodes), cmpf); child = dirnodes; while(used_dirnodes--) { child->nextchild = thisdir->firstchild; thisdir->firstchild = child; if(child->isdir) { if(chdir(child->name) < 0) { perror(child->name); } else { maketree(child, child->name, level+1); if(chdir(PARENTDIR) < 0) { perror("chdir() failed"); fprintf(stderr, "couldn't chdir() to parent, aborting\n"); exit(1); } } } child++; } } void little32(unsigned char *dest, u_int32_t src) { dest[0] = ((src >> 0) & 0xFF); dest[1] = ((src >> 8) & 0xFF); dest[2] = ((src >> 16) & 0xFF); dest[3] = ((src >> 24) & 0xFF); return; } void little16(unsigned char *dest, u_int16_t src) { dest[0] = ((src >> 0) & 0xFF); dest[1] = ((src >> 8) & 0xFF); return; } void big32(unsigned char *dest, u_int32_t src) { dest[3] = ((src >> 0) & 0xFF); dest[2] = ((src >> 8) & 0xFF); dest[1] = ((src >> 16) & 0xFF); dest[0] = ((src >> 24) & 0xFF); return; } void big16(unsigned char *dest, u_int16_t src) { dest[1] = ((src >> 0) & 0xFF); dest[0] = ((src >> 8) & 0xFF); return; } void traversetree(struct node *root, int level, int littleendian, int maxlevel, int *bytes, int fd, int parentrecord, int *recordno) { struct node *child; struct pte { u_int8_t len; u_int8_t zero; u_int32_t startsector; u_int16_t parent; } pte; if(level == maxlevel) { int i; char newname[NAMELEN]; if(!root->isdir) return; pte.zero = 0; if(level == 1) { /* root */ pte.len = 1; pte.parent = 1; root->name[0] = root->name[1] = '\0'; } else { pte.len = strlen(root->name); pte.parent = parentrecord; } pte.startsector = root->startsector; root->pathtablerecord = (*recordno)++; if(littleendian) { little32((unsigned char *) &pte.startsector, pte.startsector); little16((unsigned char *) &pte.parent, pte.parent); } else { big32((unsigned char *) &pte.startsector, pte.startsector); big16((unsigned char *) &pte.parent, pte.parent); } *bytes += Write(fd, &pte.len, sizeof(pte.len)); *bytes += Write(fd, &pte.zero, sizeof(pte.zero)); *bytes += Write(fd, &pte.startsector, sizeof(pte.startsector)); *bytes += Write(fd, &pte.parent, sizeof(pte.parent)); if(!(pte.len%2)) root->name[pte.len++] = '\0'; for(i = 0; i < pte.len; i++) newname[i] = toupper(root->name[i]); *bytes += Write(fd, newname, pte.len); return; } for(child = root->firstchild; child; child = child->nextchild) if(child->isdir) traversetree(child, level+1, littleendian, maxlevel, bytes, fd, root->pathtablerecord, recordno); return; } int makepathtables(struct node *root, int littleendian, int *bytes, int fd) { int level; static char block[ISO_SECTOR]; int recordno; recordno = 1; *bytes = 0; for(level = 1; level <= MAXLEVEL; level++) traversetree(root, 1, littleendian, level, bytes, fd, 1, &recordno); if(*bytes % ISO_SECTOR) { ssize_t x; x = ISO_SECTOR-(*bytes % ISO_SECTOR); write(fd, block, x); *bytes += x; } return *bytes/ISO_SECTOR; } ssize_t write_direntry(char *origname, u_int32_t sector, u_int32_t size, int isdir, int fd) { int namelen, total = 0; struct dir entry; char copyname[NAMELEN]; memset(&entry, 0, sizeof(entry)); if(!strcmp(origname, CURRENTDIR)) { namelen = 1; } else if(!strcmp(origname, PARENTDIR)) { entry.name[0] = '\001'; namelen = 1; } else { int i; strcpy(copyname, origname); namelen = strlen(copyname); if(namelen > ISONAMELEN) { fprintf(stderr, "%s: truncated, too long for iso9660\n", copyname); namelen = ISONAMELEN; copyname[namelen] = '\0'; } strcpy(entry.name, copyname); for(i = 0; i < namelen; i++) entry.name[i] = toupper(entry.name[i]); /* padding byte + system field */ entry.name[namelen] = '\0'; entry.name[namelen+1] = '\0'; entry.name[namelen+2] = '\0'; } entry.namelen = namelen; /* original length */ if(!(namelen%2)) namelen++; /* length with padding byte */ /* XXX 2 extra bytes for 'system use'.. */ entry.recordsize = 33 + namelen; both32((unsigned char *) entry.datasector, sector); both32((unsigned char *) entry.filesize, size); if(isdir) entry.flags = FLAG_DIR; /* XXX node date */ both16((unsigned char *) entry.sequence, 1); total = Write(fd, &entry.recordsize, sizeof(entry.recordsize)); total += Write(fd, &entry.extended, sizeof(entry.extended)); total += Write(fd, entry.datasector, sizeof(entry.datasector)); total += Write(fd, entry.filesize, sizeof(entry.filesize)); total += Write(fd, &entry.year, sizeof(entry.year)); total += Write(fd, &entry.month, sizeof(entry.month)); total += Write(fd, &entry.day, sizeof(entry.day)); total += Write(fd, &entry.hour, sizeof(entry.hour)); total += Write(fd, &entry.minute, sizeof(entry.minute)); total += Write(fd, &entry.second, sizeof(entry.second)); total += Write(fd, &entry.offset, sizeof(entry.offset)); total += Write(fd, &entry.flags, sizeof(entry.flags)); total += Write(fd, &entry.interleaved, sizeof(entry.interleaved)); total += Write(fd, &entry.interleavegap, sizeof(entry.interleavegap)); total += Write(fd, entry.sequence, sizeof(entry.sequence)); total += Write(fd, &entry.namelen, sizeof(entry.namelen)); total += Write(fd, entry.name, namelen); if(total != entry.recordsize || (total % 2) != 0) { printf("%2d, %2d! ", total, entry.recordsize); printf("%3d = %3d - %2d + %2d\n", entry.recordsize, sizeof(entry), sizeof(entry.name), namelen); } return entry.recordsize; } void writedata(struct node *parent, struct node *root, int fd, int *currentsector, int dirs, struct dir *rootentry, int rootsize, int remove_after) { static char buf[1024*1024]; struct node *c; ssize_t written = 0, rest; for(c = root->firstchild; c; c = c->nextchild) { if(c->isdir && chdir(c->name) < 0) { perror(c->name); fprintf(stderr, "couldn't chdir to %s - aborting\n", c->name); exit(1); } writedata(root, c, fd, currentsector, dirs, rootentry, rootsize, remove_after); if(c->isdir && chdir(PARENTDIR) < 0) { perror("chdir to .."); fprintf(stderr, "couldn't chdir to parent - " "aborting\n"); exit(1); } } /* write nodes depth-first, down-top */ if(root->isdir && dirs) { /* dir */ written = 0; root->startsector = *currentsector; written += write_direntry(CURRENTDIR, root->startsector, root->bytesize, root->isdir, fd); if(parent) { written += write_direntry(PARENTDIR, parent->startsector, root->bytesize, root->isdir, fd); } else { written += write_direntry(PARENTDIR, root->startsector, root->bytesize, root->isdir, fd); } for(c = root->firstchild; c; c = c->nextchild) { off_t cur1, cur2; ssize_t written_before; cur1 = Lseek(fd, 0, SEEK_CUR); written_before = written; written += write_direntry(c->name, c->startsector, c->bytesize, c->isdir, fd); cur2 = Lseek(fd, 0, SEEK_CUR); if(cur1/ISO_SECTOR != (cur2-1)/ISO_SECTOR) { /* passed a sector boundary, argh! */ Lseek(fd, cur1, SEEK_SET); written = written_before; rest=(ISO_SECTOR-(written % ISO_SECTOR)); memset(buf, 0, rest); Write(fd, buf, rest); written += rest; written += write_direntry(c->name, c->startsector, c->bytesize, c->isdir, fd); } } root->bytesize = written; } else if(!root->isdir && !dirs) { /* file */ struct stat st; ssize_t rem; int filefd; if(stat(root->name, &st) < 0) { perror(root->name); fprintf(stderr, "couldn't stat %s - aborting\n", root->name); exit(1); } if((filefd = open(root->name, O_RDONLY)) < 0) { perror(root->name); fprintf(stderr, "couldn't open %s - aborting\n", root->name); exit(1); } rem = st.st_size; root->startsector = *currentsector; while(rem > 0) { ssize_t chunk; chunk = min(sizeof(buf), rem); Read(filefd, buf, chunk); Write(fd, buf, chunk); rem -= chunk; } close(filefd); root->bytesize = written = st.st_size; if(remove_after && unlink(root->name) < 0) { perror("unlink"); fprintf(stderr, "couldn't remove %s\n", root->name); } } else { /* nothing to be done */ return; } /* fill out sector with zero bytes */ if((rest=(ISO_SECTOR-(written % ISO_SECTOR)))) { memset(buf, 0, rest); Write(fd, buf, rest); written += rest; } /* update dir size with padded size */ if(root->isdir) { root->bytesize = written; } *currentsector += written/ISO_SECTOR; } void writebootcatalog(int fd, int *currentsector, int imagesector, int imagesectors) { static char buf[ISO_SECTOR]; struct bc_validation validate; struct bc_initial initial; ssize_t written, rest; u_int16_t *v, sum = 0; int i; /* write validation entry */ memset(&validate, 0, sizeof(validate)); validate.headerid = 1; validate.platform = PLATFORM_80X86; strcpy(validate.idstring, ""); validate.keys[0] = 0x55; validate.keys[1] = 0xaa; v = (u_int16_t *) &validate; for(i = 0; i < sizeof(validate)/2; i++) sum += v[i]; validate.checksum = 65535 - sum + 1; /* sum must be 0 */ written = Write(fd, &validate, sizeof(validate)); /* write initial/default entry */ memset(&initial, 0, sizeof(initial)); initial.indicator = INDICATE_BOOTABLE; if (harddisk_emulation) { initial.media = BOOTMEDIA_HARDDISK; initial.type = system_type; } else initial.media = BOOTMEDIA_144M; /* initial.sectors = imagesectors; */ initial.sectors = 1; initial.startsector = imagesector; written += Write(fd, &initial, sizeof(initial)); /* fill out the rest of the sector with 0's */ if((rest = ISO_SECTOR - (written % 2048))) { memset(buf, 0, sizeof(buf)); written += Write(fd, buf, rest); } (*currentsector) += written / ISO_SECTOR; return; } int writebootimage(char *bootimage, int bootfd, int fd, int *currentsector) { static char buf[1024*64]; ssize_t chunk, written = 0, rest; int virtuals; while((chunk=read(bootfd, buf, sizeof(buf))) > 0) written += Write(fd, buf, chunk); if(chunk < 0) { perror("read boot image"); exit(1); } virtuals = written / VIRTUAL_SECTOR; if((rest = ISO_SECTOR - (written % 2048))) { memset(buf, 0, sizeof(buf)); written += Write(fd, buf, rest); } (*currentsector) += written/ISO_SECTOR; return virtuals; } void writebootrecord(int fd, int *currentsector, int bootcatalogsector) { int i; static struct bootrecord bootrecord; ssize_t w = 0; /* boot record volume descriptor */ memset(&bootrecord, 0, sizeof(bootrecord)); bootrecord.set[0] = 'C'; bootrecord.set[1] = 'D'; bootrecord.set[2] = '0'; bootrecord.set[3] = '0'; bootrecord.set[4] = '1'; bootrecord.version = 1; bootrecord.bootcatalog = bootcatalogsector; strcpy(bootrecord.ident, "EL TORITO SPECIFICATION"); for(i = strlen(bootrecord.ident); i < sizeof(bootrecord.ident); i++) bootrecord.ident[i] = '\0'; w = Writefield(fd, bootrecord.indicator); w += Writefield(fd, bootrecord.set); w += Writefield(fd, bootrecord.version); w += Writefield(fd, bootrecord.ident); w += Writefield(fd, bootrecord.zero); w += Writefield(fd, bootrecord.bootcatalog); w += Writefield(fd, bootrecord.zero2); if(w != ISO_SECTOR) { fprintf(stderr, "WARNING: something went wrong - boot record (%d) isn't a sector size (%d)\n", w, ISO_SECTOR); } (*currentsector)++; } int main(int argc, char *argv[]) { int currentsector = 0; int imagesector, imagesectors; int bootfd, fd, i, ch, nsectors; int remove_after = 0; static char block[ISO_SECTOR]; static struct pvd pvd; char *label = "ISO9660"; struct tm *now; time_t nowtime; char timestr[20], *prog; char *bootimage = NULL; struct node root; int pvdsector; int bigpath, littlepath, pathbytes = 0, dirsector, filesector, enddir; int bootvolumesector, bootcatalogsector; prog = argv[0]; /* This check is to prevent compiler padding screwing up * our format. */ if(sizeof(struct pvd) != ISO_SECTOR) { fprintf(stderr, "Something confusing happened at\n" "compile-time; pvd should be a sector size. %d != %d\n", sizeof(struct pvd), ISO_SECTOR); return 1; } while ((ch = getopt(argc, argv, "Rb:hl:")) != -1) { switch(ch) { case 'h': harddisk_emulation= 1; break; case 'l': label = optarg; break; case 'r': remove_after = 1; break; case 'b': bootimage = optarg; if((bootfd = open(bootimage, O_RDONLY)) < 0) { perror(bootimage); return 1; } break; } } argc -= optind; argv += optind; if(argc != 2) { fprintf(stderr, "usage: %s [-l