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 static int sysfs_get_offset(int devfd
, unsigned long *start
)
106 char sysfs_name
[128];
110 if (fstat(devfd
, &st
))
113 if ((size_t)snprintf(sysfs_name
, sizeof sysfs_name
,
114 "/sys/dev/block/%u:%u/start",
115 major(st
.st_dev
), minor(st
.st_dev
))
116 >= sizeof sysfs_name
)
119 f
= fopen(sysfs_name
, "r");
123 rv
= fscanf(f
, "%lu", start
);
126 return (rv
== 1) ? 0 : -1;
129 /* Standard floppy disk geometries, plus LS-120. Zipdisk geometry
130 (x/64/32) is the final fallback. I don't know what LS-240 has
131 as its geometry, since I don't have one and don't know anyone that does,
132 and Google wasn't helpful... */
133 static const struct geometry_table standard_geometries
[] = {
134 {360 * 1024, {2, 9, 40, 0}},
135 {720 * 1024, {2, 9, 80, 0}},
136 {1200 * 1024, {2, 15, 80, 0}},
137 {1440 * 1024, {2, 18, 80, 0}},
138 {1680 * 1024, {2, 21, 80, 0}},
139 {1722 * 1024, {2, 21, 80, 0}},
140 {2880 * 1024, {2, 36, 80, 0}},
141 {3840 * 1024, {2, 48, 80, 0}},
142 {123264 * 1024, {8, 32, 963, 0}}, /* LS120 */
146 int get_geometry(int devfd
, uint64_t totalbytes
, struct hd_geometry
*geo
)
148 struct floppy_struct fd_str
;
150 struct loop_info64 li64
;
151 const struct geometry_table
*gp
;
154 memset(geo
, 0, sizeof *geo
);
156 if (!ioctl(devfd
, HDIO_GETGEO
, &geo
)) {
158 } else if (!ioctl(devfd
, FDGETPRM
, &fd_str
)) {
159 geo
->heads
= fd_str
.head
;
160 geo
->sectors
= fd_str
.sect
;
161 geo
->cylinders
= fd_str
.track
;
166 /* Didn't work. Let's see if this is one of the standard geometries */
167 for (gp
= standard_geometries
; gp
->bytes
; gp
++) {
168 if (gp
->bytes
== totalbytes
) {
169 memcpy(geo
, &gp
->g
, sizeof *geo
);
174 /* Didn't work either... assign a geometry of 64 heads, 32 sectors; this is
175 what zipdisks use, so this would help if someone has a USB key that
176 they're booting in USB-ZIP mode. */
178 geo
->heads
= opt
.heads
? : 64;
179 geo
->sectors
= opt
.sectors
? : 32;
180 geo
->cylinders
= totalbytes
/ (geo
->heads
* geo
->sectors
<< SECTOR_SHIFT
);
183 if (!opt
.sectors
&& !opt
.heads
) {
185 "Warning: unable to obtain device geometry (defaulting to %d heads, %d sectors)\n"
186 " (on hard disks, this is usually harmless.)\n",
187 geo
->heads
, geo
->sectors
);
188 rv
= 1; /* Suboptimal result */
192 /* If this is a loopback device, try to set the start */
193 if (!ioctl(devfd
, LOOP_GET_STATUS64
, &li64
))
194 geo
->start
= li64
.lo_offset
>> SECTOR_SHIFT
;
195 else if (!ioctl(devfd
, LOOP_GET_STATUS
, &li
))
196 geo
->start
= (unsigned int)li
.lo_offset
>> SECTOR_SHIFT
;
197 else if (!sysfs_get_offset(devfd
, &geo
->start
)) {
205 * Query the device geometry and put it into the boot sector.
206 * Map the file and put the map in the boot sector and file.
207 * Stick the "current directory" inode number into the file.
209 * Returns the number of modified bytes in the boot file.
211 int patch_file_and_bootblock(int fd
, const char *dir
, int devfd
)
213 struct stat dirst
, xdst
;
214 struct hd_geometry geo
;
216 uint64_t totalbytes
, totalsectors
;
218 struct boot_sector
*sbs
;
219 char *dirpath
, *subpath
, *xdirpath
;
222 dirpath
= realpath(dir
, NULL
);
223 if (!dirpath
|| stat(dir
, &dirst
)) {
224 perror("accessing install directory");
225 exit(255); /* This should never happen */
228 if (lstat(dirpath
, &xdst
) ||
229 dirst
.st_ino
!= xdst
.st_ino
||
230 dirst
.st_dev
!= xdst
.st_dev
) {
231 perror("realpath returned nonsense");
235 subpath
= strchr(dirpath
, '\0');
237 if (*subpath
== '/') {
238 if (subpath
> dirpath
) {
244 if (lstat(xdirpath
, &xdst
) || dirst
.st_dev
!= xdst
.st_dev
) {
245 subpath
= strchr(subpath
+1, '/');
247 subpath
= "/"; /* It's the root of the filesystem */
253 if (subpath
== dirpath
)
259 /* Now subpath should contain the path relative to the fs base */
260 dprintf("subpath = %s\n", subpath
);
262 totalbytes
= get_size(devfd
);
263 get_geometry(devfd
, totalbytes
, &geo
);
266 geo
.heads
= opt
.heads
;
268 geo
.sectors
= opt
.sectors
;
270 /* Patch this into a fake FAT superblock. This isn't because
271 FAT is a good format in any way, it's because it lets the
272 early bootstrap share code with the FAT version. */
273 dprintf("heads = %u, sect = %u\n", geo
.heads
, geo
.sectors
);
275 sbs
= (struct boot_sector
*)syslinux_bootsect
;
277 totalsectors
= totalbytes
>> SECTOR_SHIFT
;
278 if (totalsectors
>= 65536) {
279 set_16(&sbs
->bsSectors
, 0);
281 set_16(&sbs
->bsSectors
, totalsectors
);
283 set_32(&sbs
->bsHugeSectors
, totalsectors
);
285 set_16(&sbs
->bsBytesPerSec
, SECTOR_SIZE
);
286 set_16(&sbs
->bsSecPerTrack
, geo
.sectors
);
287 set_16(&sbs
->bsHeads
, geo
.heads
);
288 set_32(&sbs
->bsHiddenSecs
, geo
.start
);
290 /* Construct the boot file map */
292 dprintf("directory inode = %lu\n", (unsigned long)dirst
.st_ino
);
293 nsect
= (boot_image_len
+ SECTOR_SIZE
- 1) >> SECTOR_SHIFT
;
294 nsect
+= 2; /* Two sectors for the ADV */
295 sectp
= alloca(sizeof(sector_t
) * nsect
);
296 if (fs_type
== EXT2
|| fs_type
== VFAT
) {
297 if (sectmap(fd
, sectp
, nsect
)) {
301 } else if (fs_type
== BTRFS
) {
303 sector_t
*sp
= sectp
;
305 for (i
= 0; i
< nsect
- 2; i
++)
306 *sp
++ = BTRFS_EXTLINUX_OFFSET
/SECTOR_SIZE
+ i
;
307 for (i
= 0; i
< 2; i
++)
308 *sp
++ = BTRFS_ADV_OFFSET
/SECTOR_SIZE
+ i
;
311 /* Create the modified image in memory */
312 rv
= syslinux_patch(sectp
, nsect
, opt
.stupid_mode
,
313 opt
.raid_mode
, subpath
, subvol
);
320 * Install the boot block on the specified device.
321 * Must be run AFTER install_file()!
323 int install_bootblock(int fd
, const char *device
)
325 struct ext2_super_block sb
;
326 struct btrfs_super_block sb2
;
327 struct boot_sector sb3
;
330 if (fs_type
== EXT2
) {
331 if (xpread(fd
, &sb
, sizeof sb
, EXT2_SUPER_OFFSET
) != sizeof sb
) {
332 perror("reading superblock");
335 if (sb
.s_magic
== EXT2_SUPER_MAGIC
)
337 } else if (fs_type
== BTRFS
) {
338 if (xpread(fd
, &sb2
, sizeof sb2
, BTRFS_SUPER_INFO_OFFSET
)
340 perror("reading superblock");
343 if (sb2
.magic
== *(u64
*)BTRFS_MAGIC
)
345 } else if (fs_type
== VFAT
) {
346 if (xpread(fd
, &sb3
, sizeof sb3
, 0) != sizeof sb3
) {
347 perror("reading fat superblock");
350 if (sb3
.bsResSectors
&& sb3
.bsFATs
&&
351 (strstr(sb3
.bs16
.FileSysType
, "FAT") ||
352 strstr(sb3
.bs32
.FileSysType
, "FAT")))
356 fprintf(stderr
, "no fat, ext2/3/4 or btrfs superblock found on %s\n",
360 if (fs_type
== VFAT
) {
361 struct boot_sector
*sbs
= (struct boot_sector
*)syslinux_bootsect
;
362 if (xpwrite(fd
, &sbs
->bsHead
, bsHeadLen
, 0) != bsHeadLen
||
363 xpwrite(fd
, &sbs
->bsCode
, bsCodeLen
,
364 offsetof(struct boot_sector
, bsCode
)) != bsCodeLen
) {
365 perror("writing fat bootblock");
369 if (xpwrite(fd
, syslinux_bootsect
, syslinux_bootsect_len
, 0)
370 != syslinux_bootsect_len
) {
371 perror("writing bootblock");
379 int ext2_fat_install_file(const char *path
, int devfd
, struct stat
*rst
)
381 char *file
, *oldfile
;
382 int fd
= -1, dirfd
= -1;
386 r1
= asprintf(&file
, "%s%sldlinux.sys",
387 path
, path
[0] && path
[strlen(path
) - 1] == '/' ? "" : "/");
388 r2
= asprintf(&oldfile
, "%s%sextlinux.sys",
389 path
, path
[0] && path
[strlen(path
) - 1] == '/' ? "" : "/");
390 if (r1
< 0 || !file
|| r2
< 0 || !oldfile
) {
395 dirfd
= open(path
, O_RDONLY
| O_DIRECTORY
);
401 fd
= open(file
, O_RDONLY
);
403 if (errno
!= ENOENT
) {
408 clear_attributes(fd
);
412 fd
= open(file
, O_WRONLY
| O_TRUNC
| O_CREAT
| O_SYNC
,
413 S_IRUSR
| S_IRGRP
| S_IROTH
);
419 /* Write it the first time */
420 if (xpwrite(fd
, boot_image
, boot_image_len
, 0) != boot_image_len
||
421 xpwrite(fd
, syslinux_adv
, 2 * ADV_SIZE
,
422 boot_image_len
) != 2 * ADV_SIZE
) {
423 fprintf(stderr
, "%s: write failure on %s\n", program
, file
);
427 /* Map the file, and patch the initial sector accordingly */
428 modbytes
= patch_file_and_bootblock(fd
, path
, devfd
);
430 /* Write the patch area again - this relies on the file being
431 overwritten in place! */
432 if (xpwrite(fd
, boot_image
, modbytes
, 0) != modbytes
) {
433 fprintf(stderr
, "%s: write failure on %s\n", program
, file
);
437 /* Attempt to set immutable flag and remove all write access */
438 /* Only set immutable flag if file is owned by root */
441 if (fstat(fd
, rst
)) {
449 /* Look if we have the old filename */
450 fd
= open(oldfile
, O_RDONLY
);
452 clear_attributes(fd
);
472 /* btrfs has to install the ldlinux.sys in the first 64K blank area, which
473 is not managered by btrfs tree, so actually this is not installed as files.
474 since the cow feature of btrfs will move the ldlinux.sys every where */
475 int btrfs_install_file(const char *path
, int devfd
, struct stat
*rst
)
477 patch_file_and_bootblock(-1, path
, devfd
);
478 if (xpwrite(devfd
, boot_image
, boot_image_len
, BTRFS_EXTLINUX_OFFSET
)
480 perror("writing bootblock");
483 dprintf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET
);
484 if (xpwrite(devfd
, syslinux_adv
, 2 * ADV_SIZE
, BTRFS_ADV_OFFSET
)
486 perror("writing adv");
489 dprintf("write adv to 0x%x\n", BTRFS_ADV_OFFSET
);
490 if (stat(path
, rst
)) {
497 int install_file(const char *path
, int devfd
, struct stat
*rst
)
499 if (fs_type
== EXT2
|| fs_type
== VFAT
)
500 return ext2_fat_install_file(path
, devfd
, rst
);
501 else if (fs_type
== BTRFS
)
502 return btrfs_install_file(path
, devfd
, rst
);
507 static char devname_buf
[64];
509 static void device_cleanup(void)
515 /* Verify that a device fd and a pathname agree.
516 Return 0 on valid, -1 on error. */
517 static int validate_device(const char *path
, int devfd
)
519 struct stat pst
, dst
;
522 if (stat(path
, &pst
) || fstat(devfd
, &dst
) || statfs(path
, &sfs
))
524 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
525 if (fs_type
== BTRFS
&& sfs
.f_type
== BTRFS_SUPER_MAGIC
)
527 return (pst
.st_dev
== dst
.st_rdev
) ? 0 : -1;
531 static const char *find_device(const char *mtab_file
, dev_t dev
)
536 const char *devname
= NULL
;
539 mtab
= setmntent(mtab_file
, "r");
544 while ((mnt
= getmntent(mtab
))) {
545 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
548 if (!strcmp(mnt
->mnt_type
, "btrfs") &&
549 !stat(mnt
->mnt_dir
, &dst
) &&
551 char *opt
= strstr(mnt
->mnt_opts
, BTRFS_SUBVOL_OPT
);
557 strcpy(subvol
, opt
+ sizeof(BTRFS_SUBVOL_OPT
) - 1);
558 tmp
= strchr(subvol
, 32);
562 break; /* should break and let upper layer try again */
568 if ((!strcmp(mnt
->mnt_type
, "ext2") ||
569 !strcmp(mnt
->mnt_type
, "ext3") ||
570 !strcmp(mnt
->mnt_type
, "ext4")) &&
571 !stat(mnt
->mnt_fsname
, &dst
) &&
572 dst
.st_rdev
== dev
) {
577 if ((!strcmp(mnt
->mnt_type
, "vfat")) &&
578 !stat(mnt
->mnt_fsname
, &dst
) &&
579 dst
.st_rdev
== dev
) {
587 devname
= strdup(mnt
->mnt_fsname
);
597 static const char *get_devname(const char *path
)
599 const char *devname
= NULL
;
603 if (stat(path
, &st
) || !S_ISDIR(st
.st_mode
)) {
604 fprintf(stderr
, "%s: Not a directory: %s\n", program
, path
);
607 if (statfs(path
, &sfs
)) {
608 fprintf(stderr
, "%s: statfs %s: %s\n", program
, path
, strerror(errno
));
613 /* klibc doesn't have getmntent and friends; instead, just create
614 a new device with the appropriate device type */
615 snprintf(devname_buf
, sizeof devname_buf
, "/tmp/dev-%u:%u",
616 major(st
.st_dev
), minor(st
.st_dev
));
618 if (mknod(devname_buf
, S_IFBLK
| 0600, st
.st_dev
)) {
619 fprintf(stderr
, "%s: cannot create device %s\n", program
, devname
);
623 atexit(device_cleanup
); /* unlink the device node on exit */
624 devname
= devname_buf
;
628 /* check /etc/mtab first, since btrfs subvol info is only in here */
629 devname
= find_device("/etc/mtab", st
.st_dev
);
630 if (subvol
[0] && !devname
) { /* we just find it is a btrfs subvol */
634 strcpy(parent
, path
);
635 tmp
= strrchr(parent
, '/');
638 fprintf(stderr
, "%s is subvol, try its parent dir %s\n", path
, parent
);
639 devname
= get_devname(parent
);
644 /* Didn't find it in /etc/mtab, try /proc/mounts */
645 devname
= find_device("/proc/mounts", st
.st_dev
);
648 fprintf(stderr
, "%s: cannot find device for path %s\n", program
, path
);
652 fprintf(stderr
, "%s is device %s\n", path
, devname
);
657 static int open_device(const char *path
, struct stat
*st
, const char **_devname
)
660 const char *devname
= NULL
;
664 if (stat(path
, st
) || !S_ISDIR(st
->st_mode
)) {
665 fprintf(stderr
, "%s: Not a directory: %s\n", program
, path
);
669 if (statfs(path
, &sfs
)) {
670 fprintf(stderr
, "%s: statfs %s: %s\n", program
, path
, strerror(errno
));
673 if (sfs
.f_type
== EXT2_SUPER_MAGIC
)
675 else if (sfs
.f_type
== BTRFS_SUPER_MAGIC
)
677 else if (sfs
.f_type
== MSDOS_SUPER_MAGIC
)
681 fprintf(stderr
, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n",
687 devname
= get_devname(path
);
691 if ((devfd
= open(devname
, O_RDWR
| O_SYNC
)) < 0) {
692 fprintf(stderr
, "%s: cannot open device %s\n", program
, devname
);
696 /* Verify that the device we opened is the device intended */
697 if (validate_device(path
, devfd
)) {
698 fprintf(stderr
, "%s: path %s doesn't match device %s\n",
699 program
, path
, devname
);
706 static int btrfs_read_adv(int devfd
)
708 if (xpread(devfd
, syslinux_adv
, 2 * ADV_SIZE
, BTRFS_ADV_OFFSET
)
712 return syslinux_validate_adv(syslinux_adv
) ? 1 : 0;
715 static int ext_read_adv(const char *path
, int devfd
, const char **namep
)
720 if (fs_type
== BTRFS
) {
721 /* btrfs "ldlinux.sys" is in 64k blank area */
722 return btrfs_read_adv(devfd
);
724 err
= read_adv(path
, name
= "ldlinux.sys");
725 if (err
== 2) /* ldlinux.sys does not exist */
726 err
= read_adv(path
, name
= "extlinux.sys");
733 static int ext_write_adv(const char *path
, const char *cfg
, int devfd
)
735 if (fs_type
== BTRFS
) { /* btrfs "ldlinux.sys" is in 64k blank area */
736 if (xpwrite(devfd
, syslinux_adv
, 2 * ADV_SIZE
,
737 BTRFS_ADV_OFFSET
) != 2 * ADV_SIZE
) {
738 perror("writing adv");
743 return write_adv(path
, cfg
);
746 int install_loader(const char *path
, int update_only
)
752 devfd
= open_device(path
, &st
, &devname
);
756 if (update_only
&& !syslinux_already_installed(devfd
)) {
757 fprintf(stderr
, "%s: no previous syslinux boot sector found\n",
763 /* Read a pre-existing ADV, if already installed */
765 syslinux_reset_adv(syslinux_adv
);
766 } else if (ext_read_adv(path
, devfd
, NULL
) < 0) {
771 if (modify_adv() < 0) {
776 /* Install ldlinux.sys */
777 if (install_file(path
, devfd
, &fst
)) {
781 if (fst
.st_dev
!= st
.st_dev
) {
782 fprintf(stderr
, "%s: file system changed under us - aborting!\n",
789 rv
= install_bootblock(devfd
, devname
);
797 * Modify the ADV of an existing installation
799 int modify_existing_adv(const char *path
)
801 const char *filename
;
804 devfd
= open_device(path
, NULL
, NULL
);
809 syslinux_reset_adv(syslinux_adv
);
810 else if (ext_read_adv(path
, devfd
, &filename
) < 0) {
814 if (modify_adv() < 0) {
818 if (ext_write_adv(path
, filename
, devfd
) < 0) {
826 int main(int argc
, char *argv
[])
828 parse_options(argc
, argv
, MODE_EXTLINUX
);
830 if (!opt
.directory
|| opt
.install_mbr
|| opt
.activate_partition
)
833 if (opt
.update_only
== -1) {
834 if (opt
.reset_adv
|| opt
.set_once
|| opt
.menu_save
)
835 return modify_existing_adv(opt
.directory
);
837 usage(EX_USAGE
, MODE_EXTLINUX
);
840 return install_loader(opt
.directory
, opt
.update_only
);