From 2a7fdfe43f7b9fc6360fe67b023f17ebb23a996c Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Fri, 2 Nov 2012 17:02:36 +0000 Subject: extlinux: Avoid dereferencing a garbage pointer If opt.reset_adv is set the call to ext_read_adv() is skipped which would have initialised 'filename'. This means that a pointer containing random data from the stack is passed to ext_write_adv(). Just delete the opt.reset_adv logic since modify_adv() handles that case anyway. Reported-by: Frediano Ziglio Signed-off-by: Matt Fleming --- extlinux/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extlinux/main.c b/extlinux/main.c index f0d8e11b..dbf538a1 100644 --- a/extlinux/main.c +++ b/extlinux/main.c @@ -1236,9 +1236,7 @@ int modify_existing_adv(const char *path) if (devfd < 0) return 1; - if (opt.reset_adv) - syslinux_reset_adv(syslinux_adv); - else if (ext_read_adv(path, devfd, &filename) < 0) { + if (ext_read_adv(path, devfd, &filename) < 0) { close(devfd); return 1; } -- cgit 1.2.3-korg From 37971728a5fc40b1c90512e79e47333d98ec8851 Mon Sep 17 00:00:00 2001 From: Shao Miller Date: Fri, 2 Nov 2012 11:59:10 -0400 Subject: fs: Fix searchdir resource leak This is a significant rewrite of the generic lookup logic inside core/fs/fs.c's searchdir function. Previously, there was a memory leak if a path involved multiple directories. After a sufficiently large number of invocations, this could be observed. Reported-by: Ady Signed-off-by: Shao Miller --- core/fs/fs.c | 263 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 155 insertions(+), 108 deletions(-) diff --git a/core/fs/fs.c b/core/fs/fs.c index 21f5dba0..3cb27b0d 100644 --- a/core/fs/fs.c +++ b/core/fs/fs.c @@ -202,11 +202,10 @@ void pm_searchdir(com32sys_t *regs) int searchdir(const char *name) { - struct inode *inode = NULL; - struct inode *parent = NULL; + static char root_name[] = "/"; struct file *file; - char *pathbuf = NULL; - char *part, *p, echar; + char *path, *inode_name, *next_inode_name; + struct inode *tmp, *inode = NULL; int symlink_count = MAX_SYMLINK_CNT; dprintf("searchdir: %s root: %p cwd: %p\n", @@ -228,113 +227,165 @@ int searchdir(const char *name) /* else, try the generic-path-lookup method */ - parent = get_inode(this_fs->cwd); - p = pathbuf = strdup(name); - if (!pathbuf) - goto err; + /* Copy the path */ + path = strdup(name); + if (!path) { + dprintf("searchdir: Couldn't copy path\n"); + goto err_path; + } + + /* Work with the current directory, by default */ + inode = get_inode(this_fs->cwd); + if (!inode) { + dprintf("searchdir: Couldn't use current directory\n"); + goto err_curdir; + } - do { - got_link: - if (*p == '/') { - put_inode(parent); - parent = get_inode(this_fs->root); + for (inode_name = path; inode_name; inode_name = next_inode_name) { + /* Root directory? */ + if (inode_name[0] == '/') { + next_inode_name = inode_name + 1; + inode_name = root_name; + } else { + /* Find the next inode name */ + next_inode_name = strchr(inode_name + 1, '/'); + if (next_inode_name) { + /* Terminate the current inode name and point to next */ + *next_inode_name++ = '\0'; + } + } + if (next_inode_name) { + /* Advance beyond redundant slashes */ + while (*next_inode_name == '/') + next_inode_name++; + + /* Check if we're at the end */ + if (*next_inode_name == '\0') + next_inode_name = NULL; + } + dprintf("searchdir: inode_name: %s\n", inode_name); + if (next_inode_name) + dprintf("searchdir: Remaining: %s\n", next_inode_name); + + /* Root directory? */ + if (inode_name[0] == '/') { + /* Release any chain that's already been established */ + put_inode(inode); + inode = get_inode(this_fs->root); + continue; } - do { - inode = get_inode(parent); - - while (*p == '/') - p++; - - if (!*p) - break; - - part = p; - while ((echar = *p) && echar != '/') - p++; - *p++ = '\0'; - - if (part[0] == '.' && part[1] == '.' && part[2] == '\0') { - if (inode->parent) { - put_inode(parent); - parent = get_inode(inode->parent); - put_inode(inode); - inode = NULL; - if (!echar) { - /* Terminal double dots */ - inode = parent; - parent = inode->parent ? - get_inode(inode->parent) : NULL; - } - } - } else if (part[0] != '.' || part[1] != '\0') { - inode = this_fs->fs_ops->iget(part, parent); - if (!inode) - goto err; - if (inode->mode == DT_LNK) { - char *linkbuf, *q; - int name_len = echar ? strlen(p) : 0; - int total_len = inode->size + name_len + 2; - int link_len; - - if (!this_fs->fs_ops->readlink || - --symlink_count == 0 || /* limit check */ - total_len > MAX_SYMLINK_BUF) - goto err; - - linkbuf = malloc(total_len); - if (!linkbuf) - goto err; - - link_len = this_fs->fs_ops->readlink(inode, linkbuf); - if (link_len <= 0) { - free(linkbuf); - goto err; - } - - q = linkbuf + link_len; - - if (echar) { - if (link_len > 0 && q[-1] != '/') - *q++ = '/'; - - memcpy(q, p, name_len+1); - } else { - *q = '\0'; - } - - free(pathbuf); - p = pathbuf = linkbuf; - put_inode(inode); - inode = NULL; - goto got_link; - } - - inode->name = strdup(part); - dprintf("path component: %s\n", inode->name); - - inode->parent = parent; - parent = NULL; - - if (!echar) - break; - - if (inode->mode != DT_DIR) - goto err; - - parent = inode; - inode = NULL; + /* Current directory? */ + if (!strncmp(inode_name, ".", sizeof ".")) + continue; + + /* Parent directory? */ + if (!strncmp(inode_name, "..", sizeof "..")) { + /* If there is no parent, just ignore it */ + if (!inode->parent) + continue; + + /* Add a reference to the parent so we can release the child */ + tmp = get_inode(inode->parent); + + /* Releasing the child will drop the parent back down to 1 */ + put_inode(inode); + + inode = tmp; + continue; + } + + /* Anything else */ + tmp = inode; + inode = this_fs->fs_ops->iget(inode_name, inode); + if (!inode) { + /* Failure. Release the chain */ + put_inode(tmp); + break; + } + + /* Sanity-check */ + if (inode->parent && inode->parent != tmp) { + dprintf("searchdir: iget returned a different parent\n"); + put_inode(inode); + inode = NULL; + put_inode(tmp); + break; + } + inode->parent = tmp; + inode->name = strdup(inode_name); + dprintf("searchdir: path component: %s\n", inode->name); + + /* Symlink handling */ + if (inode->mode == DT_LNK) { + char *new_path; + int new_len, copied; + + /* target path + NUL */ + new_len = inode->size + 1; + + if (next_inode_name) { + /* target path + slash + remaining + NUL */ + new_len += strlen(next_inode_name) + 1; + } + + if (!this_fs->fs_ops->readlink || + /* limit checks */ + --symlink_count == 0 || + new_len > MAX_SYMLINK_BUF) + goto err_new_len; + + new_path = malloc(new_len); + if (!new_path) + goto err_new_path; + + copied = this_fs->fs_ops->readlink(inode, new_path); + if (copied <= 0) + goto err_copied; + new_path[copied] = '\0'; + dprintf("searchdir: Symlink: %s\n", new_path); + + if (next_inode_name) { + new_path[copied] = '/'; + strcpy(new_path + copied + 1, next_inode_name); + dprintf("searchdir: New path: %s\n", new_path); } - } while (echar); - } while (0); - free(pathbuf); - pathbuf = NULL; - put_inode(parent); - parent = NULL; + free(path); + path = next_inode_name = new_path; - if (!inode) + /* Add a reference to the parent so we can release the child */ + tmp = get_inode(inode->parent); + + /* Releasing the child will drop the parent back down to 1 */ + put_inode(inode); + + inode = tmp; + continue; +err_copied: + free(new_path); +err_new_path: +err_new_len: + put_inode(inode); + inode = NULL; + break; + } + + /* If there's more to process, this should be a directory */ + if (next_inode_name && inode->mode != DT_DIR) { + dprintf("searchdir: Expected a directory\n"); + put_inode(inode); + inode = NULL; + break; + } + } +err_curdir: + free(path); +err_path: + if (!inode) { + dprintf("searchdir: Not found\n"); goto err; + } file->inode = inode; file->offset = 0; @@ -342,10 +393,6 @@ int searchdir(const char *name) return file_to_handle(file); err: - put_inode(inode); - put_inode(parent); - if (pathbuf) - free(pathbuf); _close_file(file); err_no_close: return -1; -- cgit 1.2.3-korg From cb015497a4e433ba81a47b28790b325807185617 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Thu, 10 Jan 2013 21:33:32 +0000 Subject: isolinux: Update LBA in getlinsec loop We need to increment the Logical Block Address in eax by the number of sectors we passed to getlinsec after every invocation, otherwise we'll start with the same sector everytime. This bug was discovered when booting an isohybrid image, which failed to boot after printing the following error, "Image checksum error, sorry..." because the isolinux.bin was bigger than 32K, and thus invoked the getlinsec loop that reads the file in chunks. Cc: H. Peter Anvin Signed-off-by: Matt Fleming --- core/isolinux.asm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/isolinux.asm b/core/isolinux.asm index 7a871f0e..db6d2d42 100644 --- a/core/isolinux.asm +++ b/core/isolinux.asm @@ -426,7 +426,9 @@ MaxLMA equ 384*1024 ; Reasonable limit (384K) .ok: xor bx,bx push bp + push eax call getlinsec + pop eax pop cx mov dx,cx pop bp @@ -434,6 +436,7 @@ MaxLMA equ 384*1024 ; Reasonable limit (384K) shl cx,SECTOR_SHIFT - 4 add bx,cx + add eax,edx sub bp,dx jnz .more -- cgit 1.2.3-korg From e40ba6059aa9c6b5cefd01e277eafbe60c7752fd Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Jan 2013 18:36:17 -0200 Subject: menugen: Make it compatible with Py3k Signed-off-by: Paulo Alcantara Signed-off-by: Matt Fleming --- com32/cmenu/menugen.py | 56 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/com32/cmenu/menugen.py b/com32/cmenu/menugen.py index 70ec1f87..da64d937 100644 --- a/com32/cmenu/menugen.py +++ b/com32/cmenu/menugen.py @@ -72,9 +72,9 @@ class Menusystem: self.init_entry() self.init_menu() self.init_system() - self.vtypes = " OR ".join(self.types.keys()) - self.vattrs = " OR ".join(filter(lambda x: x[0] != "_", self.entry.keys())) - self.mattrs = " OR ".join(filter(lambda x: x[0] != "_", self.menu.keys())) + self.vtypes = " OR ".join(list(self.types.keys())) + self.vattrs = " OR ".join([x for x in list(self.entry.keys()) if x[0] != "_"]) + self.mattrs = " OR ".join([x for x in list(self.menu.keys()) if x[0] != "_"]) def init_entry(self): self.entry = self.entry_init.copy() @@ -100,27 +100,27 @@ class Menusystem: if not self.entry["info"]: self.entry["info"] = self.entry["data"] if not self.menus: - print "Error before line %d" % self.lineno - print "REASON: menu must be declared before a menu item is declared" + print("Error before line %d" % self.lineno) + print("REASON: menu must be declared before a menu item is declared") sys.exit(1) self.menus[-1][1].append(self.entry) self.init_entry() def set_item(self,name,value): - if not self.entry.has_key(name): + if name not in self.entry: msg = ["Unknown attribute %s in line %d" % (name,self.lineno)] msg.append("REASON: Attribute must be one of %s" % self.vattrs) return "\n".join(msg) - if name=="type" and not self.types.has_key(value): + if name=="type" and value not in self.types: msg = [ "Unrecognized type %s in line %d" % (value,self.lineno)] msg.append("REASON: Valid types are %s" % self.vtypes) return "\n".join(msg) if name=="shortcut": - if (value <> "-1") and not re.match("^[A-Za-z0-9]$",value): + if (value != "-1") and not re.match("^[A-Za-z0-9]$",value): msg = [ "Invalid shortcut char '%s' in line %d" % (value,self.lineno) ] msg.append("REASON: Valid values are [A-Za-z0-9]") return "\n".join(msg) - elif value <> "-1": value = "'%s'" % value + elif value != "-1": value = "'%s'" % value elif name in ["state","helpid","ipappend"]: try: value = int(value) @@ -131,14 +131,14 @@ class Menusystem: return "" def set_menu(self,name,value): - if not self.menu.has_key(name): + if name not in self.menu: return "Error: Unknown keyword %s" % name self.menu[name] = value self.menu["_updated"] = 1 return "" def set_system(self,name,value): - if not self.system.has_key(name): + if name not in self.system: return "Error: Unknown keyword %s" % name if name == "skipcondn": try: # is skipcondn a number? @@ -146,7 +146,7 @@ class Menusystem: except: # it is a "-" delimited sequence value = value.lower() parts = [ self.shift_flags.get(x.strip(),None) for x in value.split("-") ] - self.system["skipcondn"] = " | ".join(filter(None, parts)) + self.system["skipcondn"] = " | ".join([_f for _f in parts if _f]) else: self.system[name] = value @@ -169,7 +169,7 @@ class Menusystem: if not err: return # all errors so return item's error message - print err + print(err) sys.exit(1) def print_entry(self,entry,fd): @@ -211,9 +211,9 @@ class Menusystem: missing = None for x in self.reqd_templates: - if not self.templates.has_key(x): missing = x + if x not in self.templates: missing = x if missing: - print "Template %s required but not defined in %s" % (missing,self.code_template_filename) + print("Template %s required but not defined in %s" % (missing,self.code_template_filename)) if filename == "-": fd = sys.stdout @@ -227,8 +227,8 @@ class Menusystem: fd.write(self.templates["footer"]) fd.close() if not self.foundmain: - print "main menu not found" - print self.menus + print("main menu not found") + print(self.menus) sys.exit(1) def input(self,filename): @@ -259,26 +259,26 @@ class Menusystem: # add property of current entry pos = line.find("=") # find the first = in string if pos < 0: - print "Syntax error in line %d" % self.lineno - print "REASON: non-section lines must be of the form ATTRIBUTE=VALUE" + print("Syntax error in line %d" % self.lineno) + print("REASON: non-section lines must be of the form ATTRIBUTE=VALUE") sys.exit(1) attr = line[:pos].strip().lower() value = line[pos+1:].strip() self.set(attr,value) except: - print "Error while parsing line %d: %s" % (self.lineno,line) + print("Error while parsing line %d: %s" % (self.lineno,line)) raise fd.close() self.add_item() def usage(): - print sys.argv[0]," [options]" - print "--input= is the name of the .menu file declaring the menu structure" - print "--output= is the name of generated C source" - print "--template= is the name of template to be used" - print - print "input and output default to - (stdin and stdout respectively)" - print "template defaults to adv_menu.tpl" + print(sys.argv[0]," [options]") + print("--input= is the name of the .menu file declaring the menu structure") + print("--output= is the name of generated C source") + print("--template= is the name of template to be used") + print() + print("input and output default to - (stdin and stdout respectively)") + print("template defaults to adv_menu.tpl") sys.exit(1) def main(): @@ -287,7 +287,7 @@ def main(): ofile = "-" opts,args = getopt.getopt(sys.argv[1:], "hi:o:t:",["input=","output=","template=","help"]) if args: - print "Unknown options %s" % args + print("Unknown options %s" % args) usage() for o,a in opts: if o in ["-i","--input"]: -- cgit 1.2.3-korg From a08be00983bcac88ab26db32ec280ce5ce190cdf Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Fri, 12 Jul 2013 07:33:21 +0100 Subject: version: bump version and date Welcome to the 4.07 release cycle Signed-off-by: Matt Fleming --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 4408eab2..94249a5e 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.06 2012 +4.07 2013 -- cgit 1.2.3-korg From 61f106ba8a5b2a7c2d9855291fd9e7f4a4d3143d Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Thu, 25 Jul 2013 10:28:59 +0100 Subject: NEWS: document changes in 4.07 Signed-off-by: Matt Fleming --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index c8022c69..7f696fd6 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,14 @@ Starting with 1.47, changes marked with SYSLINUX, PXELINUX, ISOLINUX or EXTLINUX apply to that specific program only; other changes apply to all derivatives. +Changes in 4.07: + * EXTLINUX: fix crash caused by dereferencing garbage pointer. + * Plug memory leak in searchdir which eventually leads to an + observable hang (Shao Miller). + * ISOLINUX: fix bug triggered by isohybrid images larger than + 32K which results in an invalid image checksum error. + * menugen: make it Py3k compatible (Paulo Alcantara). + Changes in 4.06: * Support for NTFS, by Paulo Alcantara. * EXTLINUX: more robust device detection, allow user to override. -- cgit 1.2.3-korg From 88d17d136c21b8afb7d27e091cbb1f757ded80df Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Tue, 26 Nov 2013 09:58:17 -0800 Subject: isolinux: Clear upper half of EDX before using MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In checkin: cb015497a4e4 isolinux: Update LBA in getlinsec loop ... we use EDX as a sector count, but the sector count is actually in DX, and the upper half of EDX is uninitialized. If the BIOS enters with a nonzero value in the upper half of EDX, this breaks horribly. At least one set of BIOSes has been identified where if the LBA > 64K then the upper half of EDX will be nonzero. Reported-by: Carl Duff Reported-by: Philip Müller Tested-by: Gerardo Exequiel Pozzi Signed-off-by: H. Peter Anvin --- core/isolinux.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/isolinux.asm b/core/isolinux.asm index db6d2d42..dd0fa892 100644 --- a/core/isolinux.asm +++ b/core/isolinux.asm @@ -430,7 +430,7 @@ MaxLMA equ 384*1024 ; Reasonable limit (384K) call getlinsec pop eax pop cx - mov dx,cx + movzx edx,cx pop bp pop bx -- cgit 1.2.3-korg From 54b29b18c684f865d81360764d2623bb06fe1d7a Mon Sep 17 00:00:00 2001 From: Ruben Kerkhof Date: Mon, 30 Dec 2013 16:53:35 -0500 Subject: Remove some whitespace Signed-off-by: Ruben Kerkhof Signed-off-by: Gene Cumm --- mbr/gptmbr.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mbr/gptmbr.S b/mbr/gptmbr.S index ae0549f0..a6a52ed2 100644 --- a/mbr/gptmbr.S +++ b/mbr/gptmbr.S @@ -204,7 +204,7 @@ found_part: movzwl %cx,%eax /* Length of GPT entry */ stosl - + rep; movsb /* GPT entry follows MBR entry */ popw %si -- cgit 1.2.3-korg From a561eb2b62e5f6bfd764738523774e8a69ed3d26 Mon Sep 17 00:00:00 2001 From: Gene Cumm Date: Sun, 8 Sep 2013 19:05:30 -0400 Subject: diag/geodsp: fix Makefile Results in null image Reported-By: ioannis Signed-off-by: Gene Cumm --- diag/geodsp/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diag/geodsp/Makefile b/diag/geodsp/Makefile index 55160859..f97a1bd5 100644 --- a/diag/geodsp/Makefile +++ b/diag/geodsp/Makefile @@ -34,14 +34,14 @@ all: $(BTARGET) # Higher compression levels result in larger files %.img.xz: %.bin mk-lba-img.pl - $(PERL) mk-lba-img $< | $(XZ) -0 > $@ || ( rm -f $@ ; false ) + $(PERL) mk-lba-img.pl $< | $(XZ) -0 > $@ || ( rm -f $@ ; false ) %.img.gz: %.bin mk-lba-img.pl - $(PERL) mk-lba-img $< | $(GZIPPROG) -9 > $@ || ( rm -f $@ ; false ) + $(PERL) mk-lba-img.pl $< | $(GZIPPROG) -9 > $@ || ( rm -f $@ ; false ) # in case someone really wants these without needing a decompressor %.img: %.bin mk-lba-img.pl - $(PERL) mk-lba-img $< > $@ || ( rm -f $@ ; false ) + $(PERL) mk-lba-img.pl $< > $@ || ( rm -f $@ ; false ) %.bin: %.asm $(coredir)/writehex.inc $(coredir)/macros.inc $(coredir)/diskboot.inc $(NASM) $(NASMOPT) -o $@ -l $(@:.bin=.lst) $< -- cgit 1.2.3-korg From a5fef9bb39d2712e17a9e77644a55bfa6ccdc6c9 Mon Sep 17 00:00:00 2001 From: Gene Cumm Date: Sun, 8 Sep 2013 23:22:22 -0400 Subject: diag/geodsp: README fixes Should clarify the situation; also word-wrap & save example Signed-off-by: Gene Cumm --- diag/geodsp/README | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/diag/geodsp/README b/diag/geodsp/README index 55e08438..9129b694 100644 --- a/diag/geodsp/README +++ b/diag/geodsp/README @@ -1,11 +1,29 @@ -GeoDsp: Images to display the geometry as the BIOS has choosen to interpret it. Both images are intended to be written to the first ~8MiB of a raw device (ie /dev/hda, /dev/sda) and be over one large cylinder of 255*63 512-byte sectors in size. +GeoDsp: Images to display the geometry as the BIOS has choosen to +interpret it. Both images are intended to be written to the first ~8MiB +of a raw device (ie /dev/hda, /dev/sda) and be over one large cylinder +of 255*63 512-byte sectors in size. -GeoDsp1S is a one-sector variant containing all code in one sector that is intended to test behavior with a typical MBR/partition table layout. A partition table should be written after writting an image. +To save the existing data for restore later: -GeoDspMS is a multi sector variant intended to look like Syslinux installed on a file system on the raw device (as opposed to a file system within a partition). + dd bs=1M iflag=fullblock count=8 if=/dev/sda of=sda.img -GeoDspMS can also be used to attempt to make the boot sector look like a normal file system's boot sector (ie FAT12/FAT16/FAT32). In order to do this, you must first save a portion the existing boot sector (the majority of the BIOS parameter block). - dd bs=1 skip=3 count=87 if=/dev/sda of=sda.bpb - dd conv=notrunc if=geodspms.img of=/dev/sda - dd conv=notrunc bs=1 seek=3 count=87 if=sda.bpb of=/dev/sda +GeoDsp1S is a one-sector variant containing all code in one sector that +is intended to test behavior with a typical MBR/partition table layout. +A partition table should be written after writting an image. +GeoDspMS is a multi sector variant intended to look like Syslinux +installed on a file system on the raw device (as opposed to a file +system within a partition). + +GeoDspMS can also be used to attempt to make the boot sector look like a +normal file system's boot sector (ie FAT12/FAT16/FAT32). In order to do +this, you must first save a portion the existing boot sector (the +majority of the BIOS parameter block). + + dd bs=1 skip=3 count=87 if=/dev/sda1 of=sda1.bpb + dd conv=notrunc if=geodspms.img of=/dev/sda1 + dd conv=notrunc bs=1 seek=3 count=87 if=sda1.bpb of=/dev/sda1 + + dd bs=1 skip=3 count=87 if=/dev/fd0 of=fd0.bpb + dd conv=notrunc if=geodspms.img of=/dev/fd0 + dd conv=notrunc bs=1 seek=3 count=87 if=fd0.bpb of=/dev/fd0 -- cgit 1.2.3-korg