1 /* ----------------------------------------------------------------------- *
3 * Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
9 * Boston MA 02111-1307, USA; either version 2 of the License, or
10 * (at your option) any later version; incorporated herein by reference.
12 * ----------------------------------------------------------------------- */
17 * Install the syslinux boot block on an fat, ext2/3/4 and btrfs filesystem
20 #define _GNU_SOURCE /* Enable everything */
22 /* This is needed to deal with the kernel headers imported into glibc 3.3.3. */
38 #include <sys/ioctl.h>
40 #include <sys/types.h>
41 #include <sys/mount.h>
44 #include "linuxioctl.h"
48 #include "../version.h"
50 #include "syslxcom.h" /* common functions shared with extlinux and syslinux */
52 #include "syslxopt.h" /* unified options */
55 # define dprintf printf
57 # define dprintf(...) ((void)0)
60 #ifndef EXT2_SUPER_OFFSET
61 #define EXT2_SUPER_OFFSET 1024
64 /* the btrfs partition first 64K blank area is used to store boot sector and
65 boot image, the boot sector is from 0~512, the boot image starts after */
66 #define BTRFS_BOOTSECT_AREA 65536
67 #define BTRFS_EXTLINUX_OFFSET SECTOR_SIZE
68 #define BTRFS_SUBVOL_OPT "subvol="
69 #define BTRFS_SUBVOL_MAX 256 /* By btrfs specification */
70 static char subvol
[BTRFS_SUBVOL_MAX
];
72 #define BTRFS_ADV_OFFSET (BTRFS_BOOTSECT_AREA - 2 * ADV_SIZE)
75 * Get the size of a block device
77 uint64_t get_size(int devfd
)
84 if (!ioctl(devfd
, BLKGETSIZE64
, &bytes
))
87 if (!ioctl(devfd
, BLKGETSIZE
, §s
))
88 return (uint64_t) sects
<< 9;
89 else if (!fstat(devfd
, &st
) && st
.st_size
)
96 * Get device geometry and partition offset
98 struct geometry_table
{
100 struct hd_geometry g
;
103 /* Standard floppy disk geometries, plus LS-120. Zipdisk geometry
104 (x/64/32) is the final fallback. I don't know what LS-240 has
105 as its geometry, since I don't have one and don't know anyone that does,
106 and Google wasn't helpful... */
107 static const struct geometry_table standard_geometries
[] = {
108 {360 * 1024, {2, 9, 40, 0}},
109 {720 * 1024, {2, 9, 80, 0}},
110 {1200 * 1024, {2, 15, 80, 0}},
111 {1440 * 1024, {2, 18, 80, 0}},
112 {1680 * 1024, {2, 21, 80, 0}},
113 {1722 * 1024, {2, 21, 80, 0}},
114 {2880 * 1024, {2, 36, 80, 0}},
115 {3840 * 1024, {2, 48, 80, 0}},
116 {123264 * 1024, {8, 32, 963, 0}}, /* LS120 */
120 int get_geometry(int devfd
, uint64_t totalbytes
, struct hd_geometry
*geo
)
122 struct floppy_struct fd_str
;
124 struct loop_info64 li64
;
125 const struct geometry_table
*gp
;
127 memset(geo
, 0, sizeof *geo
);
129 if (!ioctl(devfd
, HDIO_GETGEO
, &geo
)) {
131 } else if (!ioctl(devfd
, FDGETPRM
, &fd_str
)) {
132 geo
->heads
= fd_str
.head
;
133 geo
->sectors
= fd_str
.sect
;
134 geo
->cylinders
= fd_str
.track
;
139 /* Didn't work. Let's see if this is one of the standard geometries */
140 for (gp
= standard_geometries
; gp
->bytes
; gp
++) {
141 if (gp
->bytes
== totalbytes
) {
142 memcpy(geo
, &gp
->g
, sizeof *geo
);
147 /* Didn't work either... assign a geometry of 64 heads, 32 sectors; this is
148 what zipdisks use, so this would help if someone has a USB key that
149 they're booting in USB-ZIP mode. */
151 geo
->heads
= opt
.heads
? : 64;
152 geo
->sectors
= opt
.sectors
? : 32;
153 geo
->cylinders
= totalbytes
/ (geo
->heads
* geo
->sectors
<< SECTOR_SHIFT
);
156 if (!opt
.sectors
&& !opt
.heads
)
158 "Warning: unable to obtain device geometry (defaulting to %d heads, %d sectors)\n"
159 " (on hard disks, this is usually harmless.)\n",
160 geo
->heads
, geo
->sectors
);
162 /* If this is a loopback device, try to set the start */
163 if (!ioctl(devfd
, LOOP_GET_STATUS64
, &li64
))
164 geo
->start
= li64
.lo_offset
>> SECTOR_SHIFT
;
165 else if (!ioctl(devfd
, LOOP_GET_STATUS
, &li
))
166 geo
->start
= (unsigned int)li
.lo_offset
>> SECTOR_SHIFT
;
172 * Query the device geometry and put it into the boot sector.
173 * Map the file and put the map in the boot sector and file.
174 * Stick the "current directory" inode number into the file.
176 * Returns the number of modified bytes in the boot file.
178 int patch_file_and_bootblock(int fd
, const char *dir
, int devfd
)
180 struct stat dirst
, xdst
;
181 struct hd_geometry geo
;
183 uint64_t totalbytes
, totalsectors
;
185 struct boot_sector
*sbs
;
186 char *dirpath
, *subpath
, *xdirpath
, *xsubpath
;
189 dirpath
= realpath(dir
, NULL
);
190 if (!dirpath
|| stat(dir
, &dirst
)) {
191 perror("accessing install directory");
192 exit(255); /* This should never happen */
195 if (lstat(dirpath
, &xdst
) ||
196 dirst
.st_ino
!= xdst
.st_ino
||
197 dirst
.st_dev
!= xdst
.st_dev
) {
198 perror("realpath returned nonsense");
202 subpath
= strchr(dirpath
, '\0');
204 if (*subpath
== '/') {
205 if (subpath
> dirpath
) {
207 xsubpath
= subpath
+1;
213 if (lstat(xdirpath
, &xdst
) || dirst
.st_dev
!= xdst
.st_dev
) {
214 subpath
= strchr(subpath
+1, '/');
216 subpath
= "/"; /* It's the root of the filesystem */
222 if (subpath
== dirpath
)
228 /* Now subpath should contain the path relative to the fs base */
229 dprintf("subpath = %s\n", subpath
);
231 totalbytes
= get_size(devfd
);
232 get_geometry(devfd
, totalbytes
, &geo
);
235 geo
.heads
= opt
.heads
;
237 geo
.sectors
= opt
.sectors
;
239 /* Patch this into a fake FAT superblock. This isn't because
240 FAT is a good format in any way, it's because it lets the
241 early bootstrap share code with the FAT version. */
242 dprintf("heads = %u, sect = %u\n", geo
.heads
, geo
.sectors
);
244 sbs
= (struct boot_sector
*)syslinux_bootsect
;
246 totalsectors
= totalbytes
>> SECTOR_SHIFT
;
247 if (totalsectors
>= 65536) {
248 set_16(&sbs
->bsSectors
, 0);
250 set_16(&sbs
->bsSectors
, totalsectors
);
252 set_32(&sbs
->bsHugeSectors
, totalsectors
);
254 set_16(&sbs
->bsBytesPerSec
, SECTOR_SIZE
);
255 set_16(&sbs
->bsSecPerTrack
, geo
.sectors
);
256 set_16(&sbs
->bsHeads
, geo
.heads
);
257 set_32(&sbs
->bsHiddenSecs
, geo
.start
);
259 /* Construct the boot file map */
261 dprintf("directory inode = %lu\n", (unsigned long)dirst
.st_ino
);
262 nsect
= (boot_image_len
+ SECTOR_SIZE
- 1) >> SECTOR_SHIFT
;
263 nsect
+= 2; /* Two sectors for the ADV */
264 sectp
= alloca(sizeof(sector_t
) * nsect
);
265 if (fs_type
== EXT2
|| fs_type
== VFAT
) {
266 if (sectmap(fd
, sectp
, nsect
)) {
270 } else if (fs_type
== BTRFS
) {
272 sector_t
*sp
= sectp
;
274 for (i
= 0; i
< nsect
- 2; i
++)
275 *sp
++ = BTRFS_EXTLINUX_OFFSET
/SECTOR_SIZE
+ i
;
276 for (i
= 0; i
< 2; i
++)
277 *sp
++ = BTRFS_ADV_OFFSET
/SECTOR_SIZE
+ i
;
280 /* Create the modified image in memory */
281 rv
= syslinux_patch(sectp
, nsect
, opt
.stupid_mode
,
282 opt
.raid_mode
, subpath
, subvol
);
289 * Install the boot block on the specified device.
290 * Must be run AFTER install_file()!
292 int install_bootblock(int fd
, const char *device
)
294 struct ext2_super_block sb
;
295 struct btrfs_super_block sb2
;
296 struct boot_sector sb3
;
299 if (fs_type
== EXT2
) {
300 if (xpread(fd
, &sb
, sizeof sb
, EXT2_SUPER_OFFSET
) != sizeof sb
) {
301 perror("reading superblock");
304 if (sb
.s_magic
== EXT2_SUPER_MAGIC
)
306 } else if (fs_type
== BTRFS
) {
307 if (xpread(fd
, &sb2
, sizeof sb2
, BTRFS_SUPER_INFO_OFFSET
)
309 perror("reading superblock");
312 if (sb2
.magic
== *(u64
*)BTRFS_MAGIC
)
314 } else if (fs_type
== VFAT
) {
315 if (xpread(fd
, &sb3
, sizeof sb3
, 0) != sizeof sb3
) {
316 perror("reading fat superblock");
319 if (sb3
.bsResSectors
&& sb3
.bsFATs
&&
320 (strstr(sb3
.bs16
.FileSysType
, "FAT") ||
321 strstr(sb3
.bs32
.FileSysType
, "FAT")))
325 fprintf(stderr
, "no fat, ext2/3/4 or btrfs superblock found on %s\n",
329 if (fs_type
== VFAT
) {
330 struct boot_sector
*sbs
= (struct boot_sector
*)syslinux_bootsect
;
331 if (xpwrite(fd
, &sbs
->bsHead
, bsHeadLen
, 0) != bsHeadLen
||
332 xpwrite(fd
, &sbs
->bsCode
, bsCodeLen
,
333 offsetof(struct boot_sector
, bsCode
)) != bsCodeLen
) {
334 perror("writing fat bootblock");
338 if (xpwrite(fd
, syslinux_bootsect
, syslinux_bootsect_len
, 0)
339 != syslinux_bootsect_len
) {
340 perror("writing bootblock");
348 int ext2_fat_install_file(const char *path
, int devfd
, struct stat
*rst
)
350 char *file
, *oldfile
;
351 int fd
= -1, dirfd
= -1;
355 r1
= asprintf(&file
, "%s%sldlinux.sys",
356 path
, path
[0] && path
[strlen(path
) - 1] == '/' ? "" : "/");
357 r2
= asprintf(&oldfile
, "%s%sextlinux.sys",
358 path
, path
[0] && path
[strlen(path
) - 1] == '/' ? "" : "/");
359 if (r1
< 0 || !file
|| r2
< 0 || !oldfile
) {
364 dirfd
= open(path
, O_RDONLY
| O_DIRECTORY
);
370 fd
= open(file
, O_RDONLY
);
372 if (errno
!= ENOENT
) {
377 clear_attributes(fd
);
381 fd
= open(file
, O_WRONLY
| O_TRUNC
| O_CREAT
| O_SYNC
,
382 S_IRUSR
| S_IRGRP
| S_IROTH
);
388 /* Write it the first time */
389 if (xpwrite(fd
, boot_image
, boot_image_len
, 0) != boot_image_len
||
390 xpwrite(fd
, syslinux_adv
, 2 * ADV_SIZE
,
391 boot_image_len
) != 2 * ADV_SIZE
) {
392 fprintf(stderr
, "%s: write failure on %s\n", program
, file
);
396 /* Map the file, and patch the initial sector accordingly */
397 modbytes
= patch_file_and_bootblock(fd
, path
, devfd
);
399 /* Write the patch area again - this relies on the file being
400 overwritten in place! */
401 if (xpwrite(fd
, boot_image
, modbytes
, 0) != modbytes
) {
402 fprintf(stderr
, "%s: write failure on %s\n", program
, file
);
406 /* Attempt to set immutable flag and remove all write access */
407 /* Only set immutable flag if file is owned by root */
410 if (fstat(fd
, rst
)) {
418 /* Look if we have the old filename */
419 fd
= open(oldfile
, O_RDONLY
);
421 clear_attributes(fd
);
441 /* btrfs has to install the ldlinux.sys in the first 64K blank area, which
442 is not managered by btrfs tree, so actually this is not installed as files.
443 since the cow feature of btrfs will move the ldlinux.sys every where */
444 int btrfs_install_file(const char *path
, int devfd
, struct stat
*rst
)
446 patch_file_and_bootblock(-1, path
, devfd
);
447 if (xpwrite(devfd
, boot_image
, boot_image_len
, BTRFS_EXTLINUX_OFFSET
)
449 perror("writing bootblock");
452 dprintf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET
);
453 if (xpwrite(devfd
, syslinux_adv
, 2 * ADV_SIZE
, BTRFS_ADV_OFFSET
)
455 perror("writing adv");
458 dprintf("write adv to 0x%x\n", BTRFS_ADV_OFFSET
);
459 if (stat(path
, rst
)) {
466 int install_file(const char *path
, int devfd
, struct stat
*rst
)
468 if (fs_type
== EXT2
|| fs_type
== VFAT
)
469 return ext2_fat_install_file(path
, devfd
, rst
);
470 else if (fs_type
== BTRFS
)
471 return btrfs_install_file(path
, devfd
, rst
);
476 * SYSLINUX installs the string 'SYSLINUX' at offset 3 in the boot
477 * sector; this is consistent with FAT filesystems. Earlier versions
478 * would install the string "EXTLINUX" instead, handle both.
480 int already_installed(int devfd
)
484 xpread(devfd
, buffer
, 8, 3);
485 return !memcmp(buffer
, "SYSLINUX", 8) || !memcmp(buffer
, "EXTLINUX", 8);
489 static char devname_buf
[64];
491 static void device_cleanup(void)
497 /* Verify that a device fd and a pathname agree.
498 Return 0 on valid, -1 on error. */
499 static int validate_device(const char *path
, int devfd
)
501 struct stat pst
, dst
;
504 if (stat(path
, &pst
) || fstat(devfd
, &dst
) || statfs(path
, &sfs
))
506 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
507 if (fs_type
== BTRFS
&& sfs
.f_type
== BTRFS_SUPER_MAGIC
)
509 return (pst
.st_dev
== dst
.st_rdev
) ? 0 : -1;
513 static const char *find_device(const char *mtab_file
, dev_t dev
)
518 const char *devname
= NULL
;
521 mtab
= setmntent(mtab_file
, "r");
526 while ((mnt
= getmntent(mtab
))) {
527 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
530 if (!strcmp(mnt
->mnt_type
, "btrfs") &&
531 !stat(mnt
->mnt_dir
, &dst
) &&
533 char *opt
= strstr(mnt
->mnt_opts
, BTRFS_SUBVOL_OPT
);
539 strcpy(subvol
, opt
+ sizeof(BTRFS_SUBVOL_OPT
) - 1);
540 tmp
= strchr(subvol
, 32);
544 break; /* should break and let upper layer try again */
550 if ((!strcmp(mnt
->mnt_type
, "ext2") ||
551 !strcmp(mnt
->mnt_type
, "ext3") ||
552 !strcmp(mnt
->mnt_type
, "ext4")) &&
553 !stat(mnt
->mnt_fsname
, &dst
) &&
554 dst
.st_rdev
== dev
) {
559 if ((!strcmp(mnt
->mnt_type
, "vfat")) &&
560 !stat(mnt
->mnt_fsname
, &dst
) &&
561 dst
.st_rdev
== dev
) {
569 devname
= strdup(mnt
->mnt_fsname
);
579 static const char *get_devname(const char *path
)
581 const char *devname
= NULL
;
585 if (stat(path
, &st
) || !S_ISDIR(st
.st_mode
)) {
586 fprintf(stderr
, "%s: Not a directory: %s\n", program
, path
);
589 if (statfs(path
, &sfs
)) {
590 fprintf(stderr
, "%s: statfs %s: %s\n", program
, path
, strerror(errno
));
595 /* klibc doesn't have getmntent and friends; instead, just create
596 a new device with the appropriate device type */
597 snprintf(devname_buf
, sizeof devname_buf
, "/tmp/dev-%u:%u",
598 major(st
.st_dev
), minor(st
.st_dev
));
600 if (mknod(devname_buf
, S_IFBLK
| 0600, st
.st_dev
)) {
601 fprintf(stderr
, "%s: cannot create device %s\n", program
, devname
);
605 atexit(device_cleanup
); /* unlink the device node on exit */
606 devname
= devname_buf
;
610 /* check /etc/mtab first, since btrfs subvol info is only in here */
611 devname
= find_device("/etc/mtab", st
.st_dev
);
612 if (subvol
[0] && !devname
) { /* we just find it is a btrfs subvol */
616 strcpy(parent
, path
);
617 tmp
= strrchr(parent
, '/');
620 fprintf(stderr
, "%s is subvol, try its parent dir %s\n", path
, parent
);
621 devname
= get_devname(parent
);
626 /* Didn't find it in /etc/mtab, try /proc/mounts */
627 devname
= find_device("/proc/mounts", st
.st_dev
);
630 fprintf(stderr
, "%s: cannot find device for path %s\n", program
, path
);
634 fprintf(stderr
, "%s is device %s\n", path
, devname
);
639 static int open_device(const char *path
, struct stat
*st
, const char **_devname
)
642 const char *devname
= NULL
;
646 if (stat(path
, st
) || !S_ISDIR(st
->st_mode
)) {
647 fprintf(stderr
, "%s: Not a directory: %s\n", program
, path
);
651 if (statfs(path
, &sfs
)) {
652 fprintf(stderr
, "%s: statfs %s: %s\n", program
, path
, strerror(errno
));
655 if (sfs
.f_type
== EXT2_SUPER_MAGIC
)
657 else if (sfs
.f_type
== BTRFS_SUPER_MAGIC
)
659 else if (sfs
.f_type
== MSDOS_SUPER_MAGIC
)
663 fprintf(stderr
, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n",
669 devname
= get_devname(path
);
673 if ((devfd
= open(devname
, O_RDWR
| O_SYNC
)) < 0) {
674 fprintf(stderr
, "%s: cannot open device %s\n", program
, devname
);
678 /* Verify that the device we opened is the device intended */
679 if (validate_device(path
, devfd
)) {
680 fprintf(stderr
, "%s: path %s doesn't match device %s\n",
681 program
, path
, devname
);
688 static int btrfs_read_adv(int devfd
)
690 if (xpread(devfd
, syslinux_adv
, 2 * ADV_SIZE
, BTRFS_ADV_OFFSET
)
694 return syslinux_validate_adv(syslinux_adv
) ? 1 : 0;
697 static int ext_read_adv(const char *path
, int devfd
, const char **namep
)
702 if (fs_type
== BTRFS
) {
703 /* btrfs "ldlinux.sys" is in 64k blank area */
704 return btrfs_read_adv(devfd
);
706 err
= read_adv(path
, name
= "ldlinux.sys");
707 if (err
== 2) /* ldlinux.sys does not exist */
708 err
= read_adv(path
, name
= "extlinux.sys");
715 static int ext_write_adv(const char *path
, const char *cfg
, int devfd
)
717 if (fs_type
== BTRFS
) { /* btrfs "ldlinux.sys" is in 64k blank area */
718 if (xpwrite(devfd
, syslinux_adv
, 2 * ADV_SIZE
,
719 BTRFS_ADV_OFFSET
) != 2 * ADV_SIZE
) {
720 perror("writing adv");
725 return write_adv(path
, cfg
);
728 int install_loader(const char *path
, int update_only
)
734 devfd
= open_device(path
, &st
, &devname
);
738 if (update_only
&& !already_installed(devfd
)) {
739 fprintf(stderr
, "%s: no previous syslinux boot sector found\n",
745 /* Read a pre-existing ADV, if already installed */
747 syslinux_reset_adv(syslinux_adv
);
748 } else if (ext_read_adv(path
, devfd
, NULL
) < 0) {
753 if (modify_adv() < 0) {
758 /* Install ldlinux.sys */
759 if (install_file(path
, devfd
, &fst
)) {
763 if (fst
.st_dev
!= st
.st_dev
) {
764 fprintf(stderr
, "%s: file system changed under us - aborting!\n",
771 rv
= install_bootblock(devfd
, devname
);
779 * Modify the ADV of an existing installation
781 int modify_existing_adv(const char *path
)
783 const char *filename
;
786 devfd
= open_device(path
, NULL
, NULL
);
791 syslinux_reset_adv(syslinux_adv
);
792 else if (ext_read_adv(path
, devfd
, &filename
) < 0) {
796 if (modify_adv() < 0) {
800 if (ext_write_adv(path
, filename
, devfd
) < 0) {
808 int main(int argc
, char *argv
[])
810 parse_options(argc
, argv
, MODE_EXTLINUX
);
815 if (opt
.update_only
== -1) {
816 if (opt
.reset_adv
|| opt
.set_once
|| opt
.menu_save
)
817 return modify_existing_adv(opt
.directory
);
819 usage(EX_USAGE
, MODE_EXTLINUX
);
822 return install_loader(opt
.directory
, opt
.update_only
);