Skip to contentSkip to conversation

Reimplementing the legacy "map" command

PATCH In review Javier Martín 2 participants 6 messages Jun 5, 2008 – Jun 8, 2008

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Reimplementing the legacy "map" command

Hi again everybody. After some hacking with the GRUB internals, I've completed a reimplementation of the legacy "map" command that allowed the BIOS drives to have their numbers reassigned, in order to boot OSes whose boot code requires their installation to be located in the first HD (BIOS drive 0x80).

This implementation gets next to the status of the command in GRUB Legacy, with the main changes being:

* Command: now called "drivemap", attending to suggestions in this list
* UI: the less complete part of the job. The syntax is now as follows:
drivemap (hd1) 0x80 # Makes (hd1) appear as the first BIOS drive
i.e. the second argument is now any BIOS drive number instead of another device. This change is in part due to my lack of time, but also because others here pointed that it would be useful to allow arbitrary mappings. If required, old syntax could be reimplemented in short time.
* Backend: the command is now a module, but there is also code in the several files like the machine-specific loader.S, the loader header file and the makefiles.
* Backend: a preboot hook system has been added to the loader backend, allowing modules to register functions to be run just before the grub_X_real_boot function is called. The drivemap module uses this new functionality to get its install_int13_handler function to be called before booting the target OS without creating chaos in GRUB itself.
* Functionality: the new module is independent of chainloader, so its functions can be applied with any target loader. What effects will this have, I don't know.

This patch is just a preliminary version that has been tested only in QEMU and Bochs, not on real hardware because I'm cut off from x86 computers from the time being (I'm running on a PPC mac now). I will be back to town (and to my olde amd64 box) this weekend, but I wanted to submit this so others could scrutinize it for obvious mistakes, and maybe test it.

I've tried to respect the style rules I've seen, but please note that this is my first patch. Cheers!

Habbit

? commands/i386/pc/drivemap.c
Index: conf/i386-pc.mk
===================================================================
RCS file: /sources/grub/grub2/conf/i386-pc.mk,v
retrieving revision 1.132

i386-pc.mk +57 -1
diff -u -r1.132 i386-pc.mk
@@ -842,7 +842,7 @@
 	vbe.mod vbetest.mod vbeinfo.mod video.mod gfxterm.mod \
 	videotest.mod play.mod bitmap.mod tga.mod cpuid.mod serial.mod	\
 	ata.mod vga.mod memdisk.mod jpeg.mod png.mod pci.mod lspci.mod \
-	aout.mod _bsd.mod bsd.mod
+	aout.mod _bsd.mod bsd.mod drivemap.mod
 
 # For biosdisk.mod.
 biosdisk_mod_SOURCES = disk/i386/pc/biosdisk.c
@@ -2970,4 +2970,60 @@
 bsd_mod_CFLAGS = $(COMMON_CFLAGS)
 bsd_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For drivemap.mod.
+drivemap_mod_SOURCES = commands/i386/pc/drivemap.c
+CLEANFILES += drivemap.mod mod-drivemap.o mod-drivemap.c pre-drivemap.o drivemap_mod-commands_i386_pc_drivemap.o und-drivemap.lst
+ifneq ($(drivemap_mod_EXPORTS),no)
+CLEANFILES += def-drivemap.lst
+DEFSYMFILES += def-drivemap.lst
+endif
+MOSTLYCLEANFILES += drivemap_mod-commands_i386_pc_drivemap.d
+UNDSYMFILES += und-drivemap.lst
+
+drivemap.mod: pre-drivemap.o mod-drivemap.o
+	-rm -f $@
+	$(TARGET_CC) $(drivemap_mod_LDFLAGS) $(TARGET_LDFLAGS) -Wl,-r,-d -o $@ $^
+	$(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -R .note -R .comment $@
+
+pre-drivemap.o: $(drivemap_mod_DEPENDENCIES) drivemap_mod-commands_i386_pc_drivemap.o
+	-rm -f $@
+	$(TARGET_CC) $(drivemap_mod_LDFLAGS) $(TARGET_LDFLAGS) -Wl,-r,-d -o $@ drivemap_mod-commands_i386_pc_drivemap.o
+
+mod-drivemap.o: mod-drivemap.c
+	$(TARGET_CC) $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(drivemap_mod_CFLAGS) -c -o $@ $<
+
+mod-drivemap.c: moddep.lst genmodsrc.sh
+	sh $(srcdir)/genmodsrc.sh 'drivemap' $< > $@ || (rm -f $@; exit 1)
+
+ifneq ($(drivemap_mod_EXPORTS),no)
+def-drivemap.lst: pre-drivemap.o
+	$(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 drivemap/' > $@
+endif
+
+und-drivemap.lst: pre-drivemap.o
+	echo 'drivemap' > $@
+	$(NM) -u -P -p $< | cut -f1 -d' ' >> $@
+
+drivemap_mod-commands_i386_pc_drivemap.o: commands/i386/pc/drivemap.c $(commands/i386/pc/drivemap.c_DEPENDENCIES)
+	$(TARGET_CC) -Icommands/i386/pc -I$(srcdir)/commands/i386/pc $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(drivemap_mod_CFLAGS) -MD -c -o $@ $<
+-include drivemap_mod-commands_i386_pc_drivemap.d
+
+CLEANFILES += cmd-drivemap_mod-commands_i386_pc_drivemap.lst fs-drivemap_mod-commands_i386_pc_drivemap.lst partmap-drivemap_mod-commands_i386_pc_drivemap.lst
+COMMANDFILES += cmd-drivemap_mod-commands_i386_pc_drivemap.lst
+FSFILES += fs-drivemap_mod-commands_i386_pc_drivemap.lst
+PARTMAPFILES += partmap-drivemap_mod-commands_i386_pc_drivemap.lst
+
+cmd-drivemap_mod-commands_i386_pc_drivemap.lst: commands/i386/pc/drivemap.c $(commands/i386/pc/drivemap.c_DEPENDENCIES) gencmdlist.sh
+	set -e; 	  $(TARGET_CC) -Icommands/i386/pc -I$(srcdir)/commands/i386/pc $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(drivemap_mod_CFLAGS) -E $< 	  | sh $(srcdir)/gencmdlist.sh drivemap > $@ || (rm -f $@; exit 1)
+
+fs-drivemap_mod-commands_i386_pc_drivemap.lst: commands/i386/pc/drivemap.c $(commands/i386/pc/drivemap.c_DEPENDENCIES) genfslist.sh
+	set -e; 	  $(TARGET_CC) -Icommands/i386/pc -I$(srcdir)/commands/i386/pc $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(drivemap_mod_CFLAGS) -E $< 	  | sh $(srcdir)/genfslist.sh drivemap > $@ || (rm -f $@; exit 1)
+
+partmap-drivemap_mod-commands_i386_pc_drivemap.lst: commands/i386/pc/drivemap.c $(commands/i386/pc/drivemap.c_DEPENDENCIES) genpartmaplist.sh
+	set -e; 	  $(TARGET_CC) -Icommands/i386/pc -I$(srcdir)/commands/i386/pc $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(drivemap_mod_CFLAGS) -E $< 	  | sh $(srcdir)/genpartmaplist.sh drivemap > $@ || (rm -f $@; exit 1)
+
+
+drivemap_mod_CFLAGS = $(COMMON_CFLAGS)
+drivemap_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 include $(srcdir)/conf/common.mk

Index: conf/i386-pc.rmk
===================================================================
RCS file: /sources/grub/grub2/conf/i386-pc.rmk,v
retrieving revision 1.119

i386-pc.rmk +5 -0
diff -u -r1.119 i386-pc.rmk
@@ -319,4 +319,9 @@
 bsd_mod_CFLAGS = $(COMMON_CFLAGS)
 bsd_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For drivemap.mod.
+drivemap_mod_SOURCES = commands/i386/pc/drivemap.c
+drivemap_mod_CFLAGS = $(COMMON_CFLAGS)
+drivemap_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 include $(srcdir)/conf/common.mk

Index: include/grub/loader.h
===================================================================
RCS file: /sources/grub/grub2/include/grub/loader.h,v retrieving revision 1.9

grub/loader.h +13 -0
diff -u -r1.9 loader.h
@@ -37,6 +37,19 @@
 /* Unset current loader, if any.  */
 void EXPORT_FUNC(grub_loader_unset) (void);
 
+typedef struct hooklist_node *grub_preboot_hookid;
+
+/* Register a function to be called before the boot hook. Returns an id that
+   can be later used to unregister the preboot (i.e. if module unloaded). If
+   abort_on_error is set, the boot sequence will abort if any of the registered
+   functions return anything else than GRUB_ERR_NONE */
+grub_preboot_hookid EXPORT_FUNC(grub_loader_register_preboot)
+           (grub_err_t (*hook) (void), int abort_on_error); 
+
+/* Unregister a preboot hook by the id returned by loader_register_preboot.
+   This functions becomes a no-op if no such function is registered */
+void EXPORT_FUNC(grub_loader_unregister_preboot) (grub_preboot_hookid id);
+
 /* Call the boot hook in current loader. This may or may not return,
    depending on the setting by grub_loader_set.  */
 grub_err_t EXPORT_FUNC(grub_loader_boot) (void);

Index: include/grub/i386/loader.h
===================================================================
RCS file: /sources/grub/grub2/include/grub/i386/loader.h,v retrieving revision 1.4

grub/i386/loader.h +9 -0
diff -u -r1.4 loader.h
@@ -31,6 +31,15 @@
 extern grub_addr_t EXPORT_VAR(grub_os_area_addr);
 extern grub_size_t EXPORT_VAR(grub_os_area_size);
 
+extern grub_uint32_t EXPORT_VAR(grub_drivemap_int13_oldhandler); /* realm far ptr = 2 * 16b */
+extern grub_uint16_t EXPORT_VAR(grub_drivemap_int13_size);  /* Size of the section to be copied */
+/* This is NOT a typo: we just want this "void" var in order to take its
+ * address with &var - used for relative addressing within the int13 handler */
+extern void EXPORT_VAR(grub_drivemap_int13_handler_base);
+extern void EXPORT_VAR(grub_drivemap_int13_mapstart);
+
+void EXPORT_FUNC(grub_drivemap_int13_handler)(void);
+
 grub_err_t EXPORT_FUNC(grub_linux_boot) (void);
 
 /* The asm part of the multiboot loader.  */

Index: kern/loader.c
===================================================================
RCS file: /sources/grub/grub2/kern/loader.c,v
retrieving revision 1.9

loader.c +67 -0

78 lines not on this page · raw message

Index: kern/i386/loader.S
===================================================================
RCS file: /sources/grub/grub2/kern/i386/loader.S,v retrieving revision 1.3

i386/loader.S +136 -0

142 lines not on this page · raw message

#1 lore raw
Quoted 37 lines · Javier Martín

Hi again everybody. After some hacking with the GRUB internals, I've completed a reimplementation of the legacy "map" command that allowed the BIOS drives to have their numbers reassigned, in order to boot OSes whose boot code requires their installation to be located in the first HD (BIOS drive 0x80).

This implementation gets next to the status of the command in GRUB Legacy, with the main changes being:

* Command: now called "drivemap", attending to suggestions in this list
* UI: the less complete part of the job. The syntax is now as follows:
drivemap (hd1) 0x80 # Makes (hd1) appear as the first BIOS drive
i.e. the second argument is now any BIOS drive number instead of another device. This change is in part due to my lack of time, but also because others here pointed that it would be useful to allow arbitrary mappings. If required, old syntax could be reimplemented in short time.
* Backend: the command is now a module, but there is also code in the several files like the machine-specific loader.S, the loader header file and the makefiles.
* Backend: a preboot hook system has been added to the loader backend, allowing modules to register functions to be run just before the grub_X_real_boot function is called. The drivemap module uses this new functionality to get its install_int13_handler function to be called before booting the target OS without creating chaos in GRUB itself.
* Functionality: the new module is independent of chainloader, so its functions can be applied with any target loader. What effects will this have, I don't know.

This patch is just a preliminary version that has been tested only in QEMU and Bochs, not on real hardware because I'm cut off from x86 computers from the time being (I'm running on a PPC mac now). I will be back to town (and to my olde amd64 box) this weekend, but I wanted to submit this so others could scrutinize it for obvious mistakes, and maybe test it.

Quoted 2 lines

I've tried to respect the style rules I've seen, but please note that this is my first patch. Cheers!

Did you forgot something from this patch :) ?

Anyway... Couple of comments. No chatting on the code. No commented out code unless there is a really good reason for it. Try to move int13h handle to module not to kernel. We do not want to put anything more to kernel unless there is a really good reason to do so.

+/* This is NOT a typo: we just want this "void" var in order to take its
+ * address with &var - used for relative addressing within the int13 handler */ +extern void EXPORT_VAR(grub_drivemap_int13_handler_base);

Create new type for it. Or use variable type that eats that space. This way you do not need this comment to confuse people.

Please do not include .mk files on next patch.

Abort on error?... Ok... do you go to deinit everything before that was successfully installed or just give warning or ?

#2 lore raw
Quoted 1 line · Vesa Jääskeläinen

Did you forgot something from this patch :) ?

Er... not that I know of. What do you mean? Did I leave something important off? If it's the licensing info, I put it under the same as the whole GRUB2 project, I suppose. The int13 handler code, however, is based (though changed) on the equivalent code in GRUB legacy.

Quoted 1 line

No commented out code unless there is a really good reason for it.

Ok, I'll remove the "debug" sections in the int13 handler. Should I also remove the non-error text output of the command? (like "mapping (hd1) to 0x80")

Quoted 2 lines

Try to move int13h handle to module not to kernel. We do not want to put anything more to kernel unless there is a really good reason to do so.

Seems A Good Thing (tm). However, all the loaders have their assembler code in the kernel, and I have yet to find a single assembly file out of the /kern dir (except for the setjmp.S support routines). As I state below, I don't understand the GRUB build system quite well, and would appreciate to have it explained so that I can break the asm part off the kernel loader.S and into its own drivemap.S file.

Quoted 7 lines

+/* This is NOT a typo: we just want this "void" var in order to take its
+ * address with &var - used for relative addressing within the int13 handler */
+extern void EXPORT_VAR(grub_drivemap_int13_handler_base);

Create new type for it. Or use variable type that eats that space. This way you do not need this comment to confuse people.

Well, seems the comment was not as effective as I thought ^.^ - there is _no_ space, the only purpose of that pseudo-variable is having its address taken with the & operator. The only sensible "new type" for it would be something akin to

	typedef void grub_symbol_t;

Which would be also puzzling and require a comment to stop people from changing it to void*. However, the information would be more centralized then and it would cause less head-scratching.

Quoted 1 line

Please do not include .mk files on next patch.

I don't quite understand the GRUB2 build system right now, but if .mk files don't get patched, how does it know they exist? Are those files autogenerated somehow? I didn't find the autotools files...

Quoted 2 lines

Abort on error?... Ok... do you go to deinit everything before that was successfully installed or just give warning or ?

I don't know; either choice might be sensible and still has drawbacks (increased complexity, potentially undoable actions, etc). My design of this new functionality was a bit ad-hoc, and I added that flag thinking "if the drives cannot be mapped, then the user wouldn't want the system started". However, as you reason, with multiple preboot hooks the system could be let in a potentially inconsistent state.

#3 lore raw
Quoted 6 lines · Javier Martín
Quoted 1 line · Vesa Jääskeläinen

Did you forgot something from this patch :) ?

Er... not that I know of. What do you mean? Did I leave something important off? If it's the licensing info, I put it under the same as the whole GRUB2 project, I suppose. The int13 handler code, however, is based (though changed) on the equivalent code in GRUB legacy.

diff -pruN ...

New files were missing from the patch. Anyway it is a good idea to review the patch before sending it.

Quoted 4 lines
Quoted 1 line

No commented out code unless there is a really good reason for it.

Ok, I'll remove the "debug" sections in the int13 handler. Should I also remove the non-error text output of the command? (like "mapping (hd1) to 0x80")

I have no idea what you have there as it wasn't in patch. Perhaps differnet message in this case:

"Mapping (hd0) as BIOS device 0x80."

Shown during when hook is called?

During registration of drive mapping (drivemap command), you then check if this is possible or not and can give error or warning to screen. But if everything is good no messages during processing of command. Only show the stuff what is actually happening during the boot.

Future extensions could be:

drivemap --clear
drivemap --ata (ata0) 0x80
drivemap --ata (ata1) --next-device drivemap --usb (usb0) --next-device drivemap --eltorito (cd0) --next-device drivemap --memdisk (hd0)/floppy-image.flp 0x00

--ata would install ATA handler (if loaded)
--usb would install USB handler (if loaded) --eltorito would install el torito handler (if loaded) --memdisk would use memory disk for mapping drive (if loaded)

After this sequence following would apply:

0x00: Floppy disk image on memdisk 0x80: ata0 device
0x81: ata1 device
0x82: usb device
0x83: cd-rom with el torito emulation

Another extesion could be that if you can specify your harddisk with some UUID it could then be used to identify disk as a source and grub would find device matching it. (or use search command)

Quoted 8 lines
Quoted 2 lines

Try to move int13h handle to module not to kernel. We do not want to put anything more to kernel unless there is a really good reason to do so.

Seems A Good Thing (tm). However, all the loaders have their assembler code in the kernel, and I have yet to find a single assembly file out of the /kern dir (except for the setjmp.S support routines). As I state below, I don't understand the GRUB build system quite well, and would appreciate to have it explained so that I can break the asm part off the kernel loader.S and into its own drivemap.S file.

Well I think the first one to do so is gdb debugging patch recently presnted on mailinglist. Check .rmk file for ideas.

Quoted 10 lines
Quoted 6 lines

+/* This is NOT a typo: we just want this "void" var in order to take its
+ * address with &var - used for relative addressing within the int13 handler */
+extern void EXPORT_VAR(grub_drivemap_int13_handler_base);

Create new type for it. Or use variable type that eats that space. This way you do not need this comment to confuse people.

Well, seems the comment was not as effective as I thought ^.^ - there is _no_ space, the only purpose of that pseudo-variable is having its address taken with the & operator. The only sensible "new type" for it would be something akin to

Quoted 4 lines
	typedef void grub_symbol_t;

Which would be also puzzling and require a comment to stop people from changing it to void*. However, the information would be more centralized then and it would cause less head-scratching.

There is space... if you do not have space, then where does the pointer point :) ? If you provide external symbol let it be variable or function there is always an address and space for it. Even if you say here that this is uint8_t then you get same address that if you had uint32_t declared as an "extern". extern there it give idea for compiler what kind if data there is. It would be best to match what actually is there.

So if you have 32 bits on assembler module you could do

typedef grub_uint32_t my_custom_type_t;

extern my_custom_type_t EXPORT_VAR(my_variable);

Or just grub_uint32_t as a type.

Quoted 4 lines
Quoted 1 line

Please do not include .mk files on next patch.

I don't quite understand the GRUB2 build system right now, but if .mk files don't get patched, how does it know they exist? Are those files autogenerated somehow? I didn't find the autotools files...

You modify .rmk files then run autogen.sh script on the root.

Quoted 7 lines
Quoted 1 line

Abort on error?... Ok... do you go to deinit everything before that was successfully installed or just give warning or ?

I don't know; either choice might be sensible and still has drawbacks (increased complexity, potentially undoable actions, etc). My design of this new functionality was a bit ad-hoc, and I added that flag thinking "if the drives cannot be mapped, then the user wouldn't want the system started". However, as you reason, with multiple preboot hooks the system could be let in a potentially inconsistent state.

Right. So it might be a good idea to show message to screen and still continue. User can easily see that there is a problem with something if there is a clue on the screen. For some users this might generate support request somewhere, but hey... it would generate it anyway should it return back to grub...

#4 lore raw
Quoted 8 lines · Vesa Jääskeläinen
Quoted 6 lines · Javier Martín
Quoted 1 line · Vesa Jääskeläinen

Did you forgot something from this patch :) ?

Er... not that I know of. What do you mean? Did I leave something important off? If it's the licensing info, I put it under the same as the whole GRUB2 project, I suppose. The int13 handler code, however, is based (though changed) on the equivalent code in GRUB legacy.

Quoted 4 lines

diff -pruN ...

New files were missing from the patch. Anyway it is a good idea to review the patch before sending it.

Oops! (blushes) I'm used to GUI SVN tools diffing for me, and I thought that cvs diff -u would do the trick. Seems that it doesn't u_u. My apologies for making everyone waste their time because of my stupid oversight...

Quoted 9 lines

Future extensions could be:

drivemap --clear
drivemap --ata (ata0) 0x80
drivemap --ata (ata1) --next-device
drivemap --usb (usb0) --next-device
drivemap --eltorito (cd0) --next-device
drivemap --memdisk (hd0)/floppy-image.flp 0x00

Quoted 4 lines

--ata would install ATA handler (if loaded)
--usb would install USB handler (if loaded) --eltorito would install el torito handler (if loaded) --memdisk would use memory disk for mapping drive (if loaded)

Nice idea for the future, as it might be a way to add such drives to DOS-like OSes that delegate all work to the BIOS. However, drivers for some things might need to stay resident (with the associated memory consumption) and, if they're written for GRUB (32bit pmode), we'd need so many things that just thinking about it makes my head spin. Definitely not an easy task for now.

Oh, and the --clear option already exists, it's called -r or --reset, which clears all the mappings. Mapping a drive to itself also clears the mapping, since drivemap only stores remapped drives.

Quoted 3 lines

Another extesion could be that if you can specify your harddisk with some UUID it could then be used to identify disk as a source and grub would find device matching it. (or use search command)

This one could be implemented in short time, or might even be available already, if the grub_disk_open function accepts UUIDs.

Quoted 11 lines
Quoted 8 lines
Quoted 2 lines

Try to move int13h handle to module not to kernel. We do not want to put anything more to kernel unless there is a really good reason to do so.

Seems A Good Thing (tm). However, all the loaders have their assembler code in the kernel, and I have yet to find a single assembly file out of the /kern dir (except for the setjmp.S support routines). As I state below, I don't understand the GRUB build system quite well, and would appreciate to have it explained so that I can break the asm part off the kernel loader.S and into its own drivemap.S file.

Well I think the first one to do so is gdb debugging patch recently presnted on mailinglist. Check .rmk file for ideas.

Ok, will do. However, why are the .mk files kept in the CVS tree if they are autogenerated? Is it because autogen.sh requires Ruby?

Quoted 16 lines
Quoted 11 lines
Quoted 7 lines

+/* This is NOT a typo: we just want this "void" var in order to take its
+ * address with &var - used for relative addressing within the int13 handler */
+extern void EXPORT_VAR(grub_drivemap_int13_handler_base);

Create new type for it. Or use variable type that eats that space. This way you do not need this comment to confuse people.

Well, seems the comment was not as effective as I thought ^.^ - there is _no_ space, the only purpose of that pseudo-variable is having its address taken with the & operator. The only sensible "new type" for it would be something akin to

Quoted 4 lines
	typedef void grub_symbol_t;

Which would be also puzzling and require a comment to stop people from changing it to void*. However, the information would be more centralized then and it would cause less head-scratching.

Quoted 6 lines

There is space... if you do not have space, then where does the pointer point :) ? If you provide external symbol let it be variable or function there is always an address and space for it. Even if you say here that this is uint8_t then you get same address that if you had uint32_t declared as an "extern". extern there it give idea for compiler what kind if data there is. It would be best to match what actually is there.

The "variable" (I assume we're talking about int13_handler_base) does not point anywhere. Let me explicit it: there is no cabal, er... variable. It is just a label in the assembly code, but it has no space reserved to it (unlike variables, which are labels with some space after them). Thus, it has no "content" at all; nothing is read or written to it (in fact, that would overwrite the variable that is declared _after_ the label), and it's only used to take its address with &var.

The situation with int13_handler_mapstart is similar: it is just a symbol (space for it is reserved at runtime), and the map proper is *at* &int13_handler_mapstart. I hope the drivemap.c file will make it clearer...

Quoted 9 lines
Quoted 8 lines
Quoted 2 lines

Abort on error?... Ok... do you go to deinit everything before that was successfully installed or just give warning or ?

I don't know; either choice might be sensible and still has drawbacks (increased complexity, potentially undoable actions, etc). My design of this new functionality was a bit ad-hoc, and I added that flag thinking "if the drives cannot be mapped, then the user wouldn't want the system started". However, as you reason, with multiple preboot hooks the system could be let in a potentially inconsistent state.

Quoted 5 lines

Right. So it might be a good idea to show message to screen and still continue. User can easily see that there is a problem with something if there is a clue on the screen. For some users this might generate support request somewhere, but hey... it would generate it anyway should it return back to grub...

The problem is that if we "still continue" a user could get stuck with any of these non-informative possibilities, depending on the chainloaded MBR/bootsector:
* Nothing at all (Windows XP Pro x64, my PC)
* An "invalid operating system" message (FreeDOS MBR)
* A "cannot find kernel/ntldr/whatever" message (?)

I'm attaching the current version missing file (drivemap.c), just so that it can be examined. However, I will be applying all your suggestions (removing superfluous messages, moving the asm routine to its own file, editing just the .rmk file, cleaning commented code out...) and send an updated version of the full patch, either today or tomorrow. Thanks!

Habbit

/* drivemap.c - command to manage the BIOS drive mappings. */ /*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008 Free Software Foundation, Inc. *

 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/

#include <grub/normal.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/loader.h>
#include <grub/machine/loader.h>
#include <grub/machine/biosdisk.h>

static const struct grub_arg_option options[] =

  {
    {"list", 'l', 0, "show the current mappings", 0, 0},
    {"reset", 'r', 0, "reset all mappings to the default values", 0, 0},
    {0, 0, 0, 0, 0, 0}

};

static grub_preboot_hookid insthandler_hook = 0;

typedef struct drivemap_node
{
grub_uint8_t newdrive;
grub_uint8_t redirto;
struct drivemap_node *next;
} drivemap_node_t;

static drivemap_node_t *drivemap = 0;
static grub_err_t drivemap_install_int13_handler(void);

static grub_err_t
drivemap_set (grub_uint8_t newdrive, grub_uint8_t redirto)
/* Puts the specified mapping into the table, replacing an existing mapping
* for newdrive or adding a new one if required. */
{
drivemap_node_t *mapping = 0, *search = drivemap;
while (search)

    {
      if (search->newdrive == newdrive)
        {
          mapping = search;
          break;
        }
      search = search->next;
    }

if (mapping) /* There was a mapping already in place, modify it */

    mapping->redirto = redirto;
  else  /* Create a new mapping and add it to the head of the list */
    {
      mapping = grub_malloc (sizeof (drivemap_node_t));
      if (!mapping)
        return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate map entry");
      mapping->newdrive = newdrive;
      mapping->redirto = redirto;
      mapping->next = drivemap;
      drivemap = mapping;
    }

return GRUB_ERR_NONE;
}

static void
drivemap_remove (grub_uint8_t newdrive)
/* Removes the mapping for newdrive from the table. If there is no mapping,
* then this function behaves like a no-op on the map. */
{
drivemap_node_t *mapping = 0, *search = drivemap, *previous = 0; while (search)

    {
      if (search->newdrive == newdrive)
        {
          mapping = search;
          break;
        }
      previous = search;
      search = search->next;
    }
  if (mapping) /* Found */
    {
      if (previous)
        previous->next = mapping->next;
      else drivemap = mapping->next; /* Entry was head of list */
      grub_free (mapping);
    }
}

static grub_err_t parse_biosdisk (const char *name, grub_uint8_t *disknum) {
if (!name) return GRUB_ERR_BAD_ARGUMENT;
if (name[0] == '(')
name++; /* Skip the first ( in (hd0) - disk_open wants just the name! */
grub_disk_t disk = grub_disk_open (name);
if (!disk)

    return GRUB_ERR_UNKNOWN_DEVICE;
  else
    {
      enum grub_disk_dev_id id = disk->dev->id;
      if (disknum)
        *disknum = disk->id;   /* Only valid, of course if it's a biosdisk */
      grub_disk_close (disk);
      return (GRUB_DISK_DEVICE_BIOSDISK_ID != id) ?
          GRUB_ERR_BAD_DEVICE : GRUB_ERR_NONE;
    }
}

static grub_err_t revparse_biosdisk(const grub_uint8_t dnum, const char **output) {
grub_err_t retval = GRUB_ERR_UNKNOWN_DEVICE;
auto int find (const char *name);
int find (const char *name)

  {
    grub_disk_t disk = grub_disk_open (name);
    if (!disk)
      return 0;
    else
      {
        int found = 0;
        if (dnum == disk->id && GRUB_DISK_DEVICE_BIOSDISK_ID == disk->dev->id)
          {
            found = 1;
            *output = name;
            retval = GRUB_ERR_NONE;
          }
        grub_disk_close (disk);
        return found;
      }

}

grub_disk_dev_iterate (&find);
return retval;
}

static grub_err_t
grub_cmd_drivemap (struct grub_arg_list *state, int argc, char **args) {
if (state[0].set) /* Show: list mappings */

    {
      if (!drivemap)
        grub_printf ("No drives have been remapped");
      else
        {
          grub_printf ("Showing only remapped drives. Drives that have had "
                       "their slot assigned to another one and have not been "
                       "themselves remapped will become inaccessible through "
                       "the BIOS routines to the booted OS.\n\n");
          grub_printf ("Mapped\tGRUB\n");
          drivemap_node_t *curnode = drivemap;
          while (curnode)
            {
              const char *dname = 0;
              grub_err_t err = revparse_biosdisk (curnode->redirto, &dname);
              if (err != GRUB_ERR_NONE)
                return grub_error (err, "invalid mapping: non-existent disk or"
                                        "not managed by the BIOS");
              grub_printf("0x%02x\t%4s\n", curnode->newdrive, dname);
              curnode = curnode->next;
            }
        }
    }
  else if (state[1].set) /* Reset: just delete all mappings */
    {
      if (drivemap)
        {
          drivemap_node_t *curnode = drivemap, *prevnode = 0;
          while (curnode)
            {
              prevnode = curnode;
              curnode = curnode->next;
              grub_free (prevnode);
            }
          drivemap = 0;
        }
    }
  else
    {
      if (argc != 2)
        return grub_error (GRUB_ERR_BAD_ARGUMENT, "two arguments required");
      grub_uint8_t mapfrom = 0;
      grub_err_t err = parse_biosdisk (args[0], &mapfrom);
      if (err != GRUB_ERR_NONE)
        return grub_error (err, "invalid disk or not managed by the BIOS");

      grub_errno = GRUB_ERR_NONE;
      unsigned long mapto = grub_strtoul (args[1], 0, 0);
      if (grub_errno != GRUB_ERR_NONE)
        return grub_error (grub_errno,
                          "BIOS disk number must be between 0 and 255");
      else if (mapto == mapfrom)  /* Reset to default */      
        {
          grub_printf ("Removing the mapping for %s (%02x)", args[0], mapfrom);
          drivemap_remove (mapfrom);
        }
      else  /* Map */
        {
          grub_printf ("Mapping %s (%02x) to %02x\n", args[0], mapfrom, (grub_uint8_t)mapto);
          return drivemap_set ((grub_uint8_t)mapto, mapfrom);
        }
    }

return GRUB_ERR_NONE;
}

typedef struct __attribute__ ((packed)) int13map_node {
grub_uint8_t disknum;
grub_uint8_t mapto;
} int13map_node_t;

#define INT13H_OFFSET(x) ( ((grub_uint8_t*)(x)) - ((grub_uint8_t*)&grub_drivemap_int13_handler_base) ) #define INT13H_REBASE(x) ( (void*) (((grub_uint8_t*)handler_base) + (x)) )
#define INT13H_TONEWADDR(x) INT13H_REBASE( INT13H_OFFSET( x ) )
/* This code rests on the assumption that GRUB does not activate any kind of
* memory mapping, since it accesses realmode structures by their absolute
* addresses, like the IVT at 0 or the BDA at 0x400 */
static grub_err_t drivemap_install_int13_handler(void)
{
grub_size_t entries = 0;
drivemap_node_t *curentry = drivemap;
while (curentry) /* Count entries to prepare a contiguous map block */

    {
      entries++;
      curentry = curentry->next;
    }
  if (0 == entries)
    return GRUB_ERR_NONE;  /* No need to install the int13h handler */
  else
    {
      
      grub_uint32_t *ivtslot = (grub_uint32_t*)0x0000004c;
      
      /* Save the pointer to the old int13h handler */    
      grub_drivemap_int13_oldhandler = *ivtslot;
      grub_printf ("Old int13 handler at %08x\n", grub_drivemap_int13_oldhandler);
      /* Reserve a section of conventional memory as "BIOS memory" for handler:
       * BDA offset 0x13 contains the top of such memory */
      grub_uint16_t *bpaMemInKb = (grub_uint16_t*)0x00000413;
      grub_printf ("Top of conventional memory: %u\n", *bpaMemInKb);
      grub_size_t totalSize = grub_drivemap_int13_size
                            + (entries + 1) * sizeof(int13map_node_t);
      grub_printf ("Payload is %u bytes long, reserving %u Kb\n", totalSize, (totalSize >> 10) + (totalSize % 1024) == 0 ? 0 : 1);
      *bpaMemInKb -= (totalSize >> 10) +
                     ((totalSize % 1024) == 0) ? 0 : 1;
      /* Install the int13h handler (copy to the reserved area) */
      grub_uint8_t *handler_base = (grub_uint8_t*)(*bpaMemInKb << 10);
      grub_printf ("Copying int13 handler to: %p\n", handler_base);
      grub_memcpy (handler_base, &grub_drivemap_int13_handler_base, grub_drivemap_int13_size);
      /* Copy the mappings to the reserved area */
      curentry = drivemap;
      grub_size_t i;
      int13map_node_t *handler_map = (int13map_node_t*) INT13H_TONEWADDR(&grub_drivemap_int13_mapstart);
      grub_printf ("Target map at %p, copying mappings...\n", handler_map);
      for (i = 0; i < entries && curentry; i++, curentry = curentry->next)
        {
          handler_map[i].disknum = curentry->newdrive;
          handler_map[i].mapto = curentry->redirto;
          grub_printf ("\t#%d: 0x%02x <- 0x%02x\n", i, handler_map[i].disknum, handler_map[i].mapto);
        }
      /* Signal end-of-map */
      handler_map[i].disknum = 0;
      handler_map[i].mapto = 0;
      grub_printf ("\t#%d: 0x%02x <- 0x%02x (end)\n", i, handler_map[i].disknum, handler_map[i].mapto);
      /* Install the int13h handler (set the IVT entry for it) */
      grub_uint32_t ivtentry = ((grub_uint32_t)handler_base) << 12; /* Segment address */
      ivtentry |= (grub_uint16_t) INT13H_OFFSET(grub_drivemap_int13_handler);
      grub_printf ("New int13 handler IVT pointer: %08x\n", ivtentry);
      *ivtslot = ivtentry;
      
      return GRUB_ERR_NONE;
    }
}

#undef INT13H_TONEWADDR
#undef INT13H_REBASE
#undef INT13H_OFFSET

GRUB_MOD_INIT(drivemap)
{
(void)mod; /* To stop warning. */
grub_register_command ("drivemap", grub_cmd_drivemap, GRUB_COMMAND_FLAG_BOTH,

			 "drivemap -s | -r | (hdX) newdrivenum", "Manage the BIOS drive mappings", options);

insthandler_hook = grub_loader_register_preboot (&drivemap_install_int13_handler, 1);
}

GRUB_MOD_FINI(drivemap)
{
grub_loader_unregister_preboot (insthandler_hook); insthandler_hook = 0; grub_unregister_command ("drivemap");
}

#5 lore raw
In reply to Vesa Jääskeläinen

Ok, new version of the patch here. Followed as many of your suggestions as I could in one day: removed unused code, put verbose, unneeded printfs under a conditional compilation DEBUG symbol, removed the .mk file from the patch and moved everything out of the kernel and into the drivemap module. What I didn't do is replacing the abort_on_error bit, mostly because I don't know that to replace it with.

Cheers! (I hope I didn't leave anything out this time)

Habbit

Huge PS:

This patch may be useful, but in its current state it's no panacea. An important feature is still unimplemented, and I have no clue as to how to do so without breaking modularity: replacing the "boot drive number" passed in DL to the booted OS with its mapped version. This is not required for the system to work, but without it it's a bit tough to get it functional. An example, booting FreeDOS in QEMU:

Disk 0x80 - 64 MiB, 1 primary partition (FAT16), some random data Disk 0x81 - 2 GiB, 1 primary partition (FAT32), FreeDOS installation
with MBR, VBR and system files

(note: remember that drivemap does not affect GRUB drive numbers)

Case #1: booting without drivemap
 set root=(hd1,1)
chainloader +1
boot
In order to just boot FreeDOS, drivemap isn't even needed, as its loader obeys the 0x81 passed in DL by GRUB and loads KERNEL.SYS from the right hard disk. However, the rest of the OS expects to get its system partition as C:, which does not happen as (hd1,1) becomes D:. We get a "enter command shell" prompt, which is not a completely borked system (just entering d:\command.com solves it, though it doesn't load CONFIG.SYS or AUTOEXEC.BAT) but isn't a pleasant view either.

Case #2: booting with drivemap, simplest

    drivemap (hd1) 0x80
    chainloader (hd1,1)+1
    boot

This works fine and boots FreeDOS: I don't know what the kernel gets in DL when the root var is not set (FF, the code seems to say), but it boots and loads the OS fine. The problem is we've lost access to our first disk, since C: and D: are exactly the same partition (hd1,1) and (hd0) is nowhere.

Case #3: booting with drivemap and both disks, first try

    drivemap (hd1) 0x80
    drivemap (hd0) 0x81
    chainloader (hd1,1)+1
    boot

This doesn't work, as the kernel tries to boot from 0x81 and doesn't find KERNEL.SYS. However, as for why does it try this, I don't know, since it seems to be getting FF in DL.

Case #4: booting with drivemap and both disks, second try

    drivemap (hd1) 0x80
    drivemap (hd0) 0x81
    set root=(hd0)
    chainloader (hd1,1)+1
    boot

This gets us what we wont: a working system with (hd1,1)=C: and (hd0,1)=D:. The problem is that chainloader thinks that the true root is (hd0), so it passes 0x80 in DL (good) but no pointer to the root partition table entry as it would if root were (hd1,1). FreeDOS seems not to use this pointer, but an OS using it would be either querying the wrong table (if root=(hd0,1)) or no table at all (if root=(hd0) as in this example). Obviously, the Good Thing (tm) would be to set root to (hd1,1) and then modify the boot_drive stored by chainloader, setting it to the mapped number. However, as I stated before, I don't know how to do this without breaking modularity.

Index: commands/i386/pc/drivemap.c
===================================================================
RCS file: commands/i386/pc/drivemap.c

i386/pc/drivemap.c new file +348 -0
diff -N commands/i386/pc/drivemap.c
@@ -0,0 +1,348 @@
+/* drivemap.c - command to manage the BIOS drive mappings.  */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2008  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/normal.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/disk.h>
+#include <grub/loader.h>
+#include <grub/machine/loader.h>
+#include <grub/machine/biosdisk.h>
+
+/* Uncomment the following line to enable debugging output */
+/* #define DRIVEMAP_DEBUG */
+
+#ifdef DRIVEMAP_DEBUG
+# define DBG_PRINTF(...) grub_printf(__VA_ARGS__)
+#else
+# define DBG_PRINTF(...)
+#endif
+
+static const struct grub_arg_option options[] =
+  {
+    {"list", 'l', 0, "show the current mappings", 0, 0},
+    {"reset", 'r', 0, "reset all mappings to the default values", 0, 0},
+    {0, 0, 0, 0, 0, 0}
+  };
+
+/* ASSEMBLY SYMBOLS/VARS/FUNCS - start */
+
+/* realmode far ptr = 2 * 16b */
+extern grub_uint32_t EXPORT_VAR(grub_drivemap_int13_oldhandler);
+/* Size of the section to be copied */
+extern grub_uint16_t EXPORT_VAR(grub_drivemap_int13_size);
+
+/* NOT a typo - just need the symbol's address with &symbol */
+typedef void grub_symbol_t;
+extern grub_symbol_t EXPORT_VAR(grub_drivemap_int13_handler_base);
+extern grub_symbol_t EXPORT_VAR(grub_drivemap_int13_mapstart);
+
+void EXPORT_FUNC(grub_drivemap_int13_handler)(void);
+
+/* ASSEMBLY SYMBOLS/VARS/FUNCS - end */
+
+static grub_preboot_hookid insthandler_hook = 0;
+
+typedef struct drivemap_node
+{
+  grub_uint8_t newdrive;
+  grub_uint8_t redirto;
+  struct drivemap_node *next;
+} drivemap_node_t;
+
+static drivemap_node_t *drivemap = 0;
+static grub_err_t drivemap_install_int13_handler(void);
+
+static grub_err_t
+drivemap_set (grub_uint8_t newdrive, grub_uint8_t redirto)
+  /* Puts the specified mapping into the table, replacing an existing mapping
+   * for newdrive or adding a new one if required. */
+{
+  drivemap_node_t *mapping = 0, *search = drivemap;
+  while (search)
+    {
+      if (search->newdrive == newdrive)
+        {
+          mapping = search;
+          break;
+        }
+      search = search->next;
+    }
+
+  if (mapping)  /* There was a mapping already in place, modify it */
+    mapping->redirto = redirto;
+  else  /* Create a new mapping and add it to the head of the list */
+    {
+      mapping = grub_malloc (sizeof (drivemap_node_t));
+      if (!mapping)
+        return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate map entry");
+      mapping->newdrive = newdrive;
+      mapping->redirto = redirto;
+      mapping->next = drivemap;
+      drivemap = mapping;
+    }
+  return GRUB_ERR_NONE;
+}
+
+static void
+drivemap_remove (grub_uint8_t newdrive)
+  /* Removes the mapping for newdrive from the table. If there is no mapping,
+   * then this function behaves like a no-op on the map. */
+{
+  drivemap_node_t *mapping = 0, *search = drivemap, *previous = 0;
+  while (search)
+    {
+      if (search->newdrive == newdrive)
+        {
+          mapping = search;
+          break;
+        }
+      previous = search;
+      search = search->next;
+    }
+  if (mapping) /* Found */
+    {
+      if (previous)
+        previous->next = mapping->next;
+      else drivemap = mapping->next; /* Entry was head of list */
+      grub_free (mapping);
+    }
+}
+
+static grub_err_t parse_biosdisk (const char *name, grub_uint8_t *disknum)
+{
+  if (!name) return GRUB_ERR_BAD_ARGUMENT;
+  if (name[0] == '(')
+    name++; /* Skip the first ( in (hd0) - disk_open wants just the name! */
+  grub_disk_t disk = grub_disk_open (name);
+  if (!disk)
+    return GRUB_ERR_UNKNOWN_DEVICE;
+  else
+    {
+      enum grub_disk_dev_id id = disk->dev->id;
+      if (disknum)
+        *disknum = disk->id;   /* Only valid, of course if it's a biosdisk */
+      grub_disk_close (disk);
+      return (GRUB_DISK_DEVICE_BIOSDISK_ID != id) ?
+          GRUB_ERR_BAD_DEVICE : GRUB_ERR_NONE;
+    }
+}
+
+static grub_err_t revparse_biosdisk(const grub_uint8_t dnum, const char **output)
+{
+  grub_err_t retval = GRUB_ERR_UNKNOWN_DEVICE;
+  auto int find (const char *name);
+  int find (const char *name)
+  {
+    grub_disk_t disk = grub_disk_open (name);
+    if (!disk)
+      return 0;
+    else
+      {
+        int found = 0;
+        if (dnum == disk->id && GRUB_DISK_DEVICE_BIOSDISK_ID == disk->dev->id)
+          {
+            found = 1;
+            *output = name;
+            retval = GRUB_ERR_NONE;
+          }
+        grub_disk_close (disk);
+        return found;
+      }
+  }
+
+  grub_disk_dev_iterate (&find);
+  return retval;
+}
+
+static grub_err_t
+grub_cmd_drivemap (struct grub_arg_list *state, int argc, char **args)
+{
+  if (state[0].set) /* Show: list mappings */
+    {
+      if (!drivemap)
+        grub_printf ("No drives have been remapped");
+      else
+        {
+          grub_printf ("Showing only remapped drives. Drives that have had "
+                       "their slot assigned to another one and have not been "
+                       "themselves remapped will become inaccessible through "
+                       "the BIOS routines to the booted OS.\n\n");
+          grub_printf ("Mapped\tGRUB\n");
+          drivemap_node_t *curnode = drivemap;
+          while (curnode)
+            {
+              const char *dname = 0;
+              grub_err_t err = revparse_biosdisk (curnode->redirto, &dname);
+              if (err != GRUB_ERR_NONE)
+                return grub_error (err, "invalid mapping: non-existent disk or"
+                                        "not managed by the BIOS");
+              grub_printf("0x%02x\t%4s\n", curnode->newdrive, dname);
+              curnode = curnode->next;
+            }
+        }
+    }
+  else if (state[1].set) /* Reset: just delete all mappings */
+    {
+      if (drivemap)
+        {
+          drivemap_node_t *curnode = drivemap, *prevnode = 0;
+          while (curnode)
+            {
+              prevnode = curnode;
+              curnode = curnode->next;
+              grub_free (prevnode);
+            }
+          drivemap = 0;
+        }
+    }
+  else
+    {
+      if (argc != 2)
+        return grub_error (GRUB_ERR_BAD_ARGUMENT, "two arguments required");
+      grub_uint8_t mapfrom = 0;
+      grub_err_t err = parse_biosdisk (args[0], &mapfrom);
+      if (err != GRUB_ERR_NONE)
+        return grub_error (err, "invalid disk or not managed by the BIOS");
+
+      grub_errno = GRUB_ERR_NONE;
+      unsigned long mapto = grub_strtoul (args[1], 0, 0);
+      if (grub_errno != GRUB_ERR_NONE)
+        return grub_error (grub_errno,
+                          "BIOS disk number must be between 0 and 255");
+      else if (mapto == mapfrom)  /* Reset to default */      
+        {
+          DBG_PRINTF ("Removing the mapping for %s (%02x)", args[0], mapfrom);
+          drivemap_remove (mapfrom);
+        }
+      else  /* Map */
+        {
+          DBG_PRINTF ("Mapping %s (%02x) to %02x\n", args[0], mapfrom, mapto);
+          return drivemap_set ((grub_uint8_t)mapto, mapfrom);
+        }
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+typedef struct __attribute__ ((packed)) int13map_node
+{
+  grub_uint8_t disknum;
+  grub_uint8_t mapto;
+} int13map_node_t;
+
+/* The min amount of mem that must remain free after installing the handler.
+ * 32 KiB is just above 0x7C00-0x7E00, where the bootsector is loaded.  */
+#define MIN_FREE_MEM_KB 32
+#define INT13H_OFFSET(x) ( ((grub_uint8_t*)(x)) - ((grub_uint8_t*)&grub_drivemap_int13_handler_base) )
+#define INT13H_REBASE(x) ( (void*) (((grub_uint8_t*)handler_base) + (x)) )
+#define INT13H_TONEWADDR(x) INT13H_REBASE( INT13H_OFFSET( x ) )
+/* Int13h handler installer - reserves conventional memory for the handler,
+ * copies it over and sets the IVT entry for int13h.
+ * This code rests on the assumption that GRUB does not activate any kind of
+ * memory mapping, since it accesses realmode structures by their absolute
+ * addresses, like the IVT at 0 or the BDA at 0x400 */
+static grub_err_t drivemap_install_int13_handler(void)
+{
+  grub_size_t entries = 0;
+  drivemap_node_t *curentry = drivemap;
+  while (curentry)  /* Count entries to prepare a contiguous map block */
+    {
+      entries++;
+      curentry = curentry->next;
+    }
+  if (0 == entries)
+    {
+      DBG_PRINTF ("drivemap: No drives remapped, int13h handler not installed");
+      return GRUB_ERR_NONE;  /* No need to install the int13h handler */
+    }
+  else
+    {
+      DBG_PRINTF ("drivemap: Installing int13h handler...\n");
+      grub_uint32_t *ivtslot = (grub_uint32_t*)0x0000004c;
+      
+      /* Save the pointer to the old int13h handler */    
+      grub_drivemap_int13_oldhandler = *ivtslot;
+      DBG_PRINTF ("Old int13 handler at %04x:%04x\n",
+                  (grub_drivemap_int13_oldhandler >> 16) & 0x0ffff,
+                  grub_drivemap_int13_oldhandler & 0x0ffff);
+
+      /* Reserve a section of conventional memory as "BIOS memory" for handler:
+       * BDA offset 0x13 contains the top of such memory */
+      grub_uint16_t *bpaMemInKb = (grub_uint16_t*)0x00000413;
+      DBG_PRINTF ("Top of conventional memory: %u KiB\n", *bpaMemInKb);
+      grub_size_t totalSize = grub_drivemap_int13_size
+                            + (entries + 1) * sizeof(int13map_node_t);
+      grub_uint16_t payloadSizeKb = (totalSize >> 10) +
+                                    (((totalSize % 1024) == 0) ? 0 : 1);
+      if ((*bpaMemInKb - payloadSizeKb) < MIN_FREE_MEM_KB)
+        return grub_error (GRUB_ERR_OUT_OF_MEMORY, "refusing to install int13 handler, not enough free memory after");
+      DBG_PRINTF ("Payload is %u b long, reserving %u Kb\n", totalSize, payloadSizeKb);
+      *bpaMemInKb -= payloadSizeKb;
+
+      /* Copy int13h handler chunk to reserved area */
+      grub_uint8_t *handler_base = (grub_uint8_t*)(*bpaMemInKb << 10);
+      DBG_PRINTF ("Copying int13 handler to: %p\n", handler_base);
+      grub_memcpy (handler_base, &grub_drivemap_int13_handler_base, grub_drivemap_int13_size);
+
+      /* Copy the mappings to the reserved area */
+      curentry = drivemap;
+      grub_size_t i;
+      int13map_node_t *handler_map = (int13map_node_t*) INT13H_TONEWADDR(&grub_drivemap_int13_mapstart);
+      DBG_PRINTF ("Target map at %p, copying mappings...\n", handler_map);
+      for (i = 0; i < entries && curentry; i++, curentry = curentry->next)
+        {
+          handler_map[i].disknum = curentry->newdrive;
+          handler_map[i].mapto = curentry->redirto;
+          DBG_PRINTF ("\t#%d: 0x%02x <- 0x%02x\n", i, handler_map[i].disknum, handler_map[i].mapto);
+        }
+      /* Signal end-of-map */
+      handler_map[i].disknum = 0;
+      handler_map[i].mapto = 0;
+      DBG_PRINTF ("\t#%d: 0x%02x <- 0x%02x (end)\n", i, handler_map[i].disknum, handler_map[i].mapto);
+
+      /* Install our function as the int13h handler in the IVT */
+      grub_uint32_t ivtentry = ((grub_uint32_t)handler_base) << 12; /* Segment address */
+      ivtentry |= (grub_uint16_t) INT13H_OFFSET(grub_drivemap_int13_handler);
+      DBG_PRINTF ("New int13 handler IVT pointer: %04x:%04x\n",
+                  (ivtentry >> 16) & 0x0ffff, ivtentry & 0x0ffff);
+      *ivtslot = ivtentry;
+      
+      return GRUB_ERR_NONE;
+    }
+}
+#undef INT13H_TONEWADDR
+#undef INT13H_REBASE
+#undef INT13H_OFFSET
+
+GRUB_MOD_INIT(drivemap)
+{
+  (void)mod;			/* To stop warning. */
+  grub_register_command ("drivemap", grub_cmd_drivemap, GRUB_COMMAND_FLAG_BOTH,
+			 "drivemap -s | -r | (hdX) newdrivenum", "Manage the BIOS drive mappings", options);
+  insthandler_hook = grub_loader_register_preboot (&drivemap_install_int13_handler, 1);
+}
+
+GRUB_MOD_FINI(drivemap)
+{
+  grub_loader_unregister_preboot (insthandler_hook);
+  insthandler_hook = 0;
+  grub_unregister_command ("drivemap");
+}

Index: commands/i386/pc/drivemap_int13h.S
===================================================================
RCS file: commands/i386/pc/drivemap_int13h.S

i386/pc/drivemap_int13h.S new file +128 -0

128 lines not on this page · raw message

Index: conf/i386-pc.rmk
===================================================================
RCS file: /sources/grub/grub2/conf/i386-pc.rmk,v
retrieving revision 1.119

i386-pc.rmk +8 -1
diff -u -p -r1.119 i386-pc.rmk
@@ -152,7 +152,7 @@ pkglib_MODULES = biosdisk.mod _chain.mod
 	vbe.mod vbetest.mod vbeinfo.mod video.mod gfxterm.mod \
 	videotest.mod play.mod bitmap.mod tga.mod cpuid.mod serial.mod	\
 	ata.mod vga.mod memdisk.mod jpeg.mod png.mod pci.mod lspci.mod \
-	aout.mod _bsd.mod bsd.mod
+	aout.mod _bsd.mod bsd.mod drivemap.mod
 
 # For biosdisk.mod.
 biosdisk_mod_SOURCES = disk/i386/pc/biosdisk.c
@@ -319,4 +319,11 @@ bsd_mod_SOURCES = loader/i386/bsd_normal
 bsd_mod_CFLAGS = $(COMMON_CFLAGS)
 bsd_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For drivemap.mod.
+drivemap_mod_SOURCES = commands/i386/pc/drivemap.c \
+                       commands/i386/pc/drivemap_int13h.S
+drivemap_mod_ASFLAGS = $(COMMON_ASFLAGS)
+drivemap_mod_CFLAGS = $(COMMON_CFLAGS)
+drivemap_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 include $(srcdir)/conf/common.mk

Index: include/grub/loader.h
===================================================================
RCS file: /sources/grub/grub2/include/grub/loader.h,v retrieving revision 1.9

grub/loader.h +13 -0
diff -u -p -r1.9 loader.h
@@ -37,6 +37,19 @@ void EXPORT_FUNC(grub_loader_set) (grub_
 /* Unset current loader, if any.  */
 void EXPORT_FUNC(grub_loader_unset) (void);
 
+typedef struct hooklist_node *grub_preboot_hookid;
+
+/* Register a function to be called before the boot hook. Returns an id that
+   can be later used to unregister the preboot (i.e. if module unloaded). If
+   abort_on_error is set, the boot sequence will abort if any of the registered
+   functions return anything else than GRUB_ERR_NONE */
+grub_preboot_hookid EXPORT_FUNC(grub_loader_register_preboot)
+           (grub_err_t (*hook) (void), int abort_on_error); 
+
+/* Unregister a preboot hook by the id returned by loader_register_preboot.
+   This functions becomes a no-op if no such function is registered */
+void EXPORT_FUNC(grub_loader_unregister_preboot) (grub_preboot_hookid id);
+
 /* Call the boot hook in current loader. This may or may not return,
    depending on the setting by grub_loader_set.  */
 grub_err_t EXPORT_FUNC(grub_loader_boot) (void);

Index: kern/loader.c
===================================================================
RCS file: /sources/grub/grub2/kern/loader.c,v
retrieving revision 1.9

loader.c +67 -0

78 lines not on this page · raw message