/* loopback.c - command to add loopback devices. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2005,2006,2007 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 . */ #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); struct grub_loopback { char *devname; grub_file_t file; struct grub_loopback *next; unsigned long id; }; static struct grub_loopback *loopback_list; static unsigned long last_id = 0; static const struct grub_arg_option options[] = { /* TRANSLATORS: The disk is simply removed from the list of available ones, not wiped, avoid to scare user. */ {"delete", 'd', 0, N_("Delete the specified loopback drive."), 0, 0}, {0, 0, 0, 0, 0, 0} }; /* Delete the loopback device NAME. */ static grub_err_t delete_loopback (const char *name) { struct grub_loopback *dev; struct grub_loopback **prev; /* Search for the device. */ for (dev = loopback_list, prev = &loopback_list; dev; prev = &dev->next, dev = dev->next) if (grub_strcmp (dev->devname, name) == 0) break; if (! dev) return grub_error (GRUB_ERR_BAD_DEVICE, "device not found"); /* Remove the device from the list. */ *prev = dev->next; grub_free (dev->devname); grub_file_close (dev->file); grub_free (dev); return 0; } /* The command to add and remove loopback devices. */ static grub_err_t grub_cmd_loopback (grub_extcmd_context_t ctxt, int argc, char **args) { struct grub_arg_list *state = ctxt->state; grub_file_t file; struct grub_loopback *newdev; grub_err_t ret; if (argc < 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required"); /* Check if `-d' was used. */ if (state[0].set) return delete_loopback (args[0]); if (argc < 2) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); file = grub_file_open (args[1], GRUB_FILE_TYPE_LOOPBACK | GRUB_FILE_TYPE_NO_DECOMPRESS); if (! file) return grub_errno; /* First try to replace the old device. */ for (newdev = loopback_list; newdev; newdev = newdev->next) if (grub_strcmp (newdev->devname, args[0]) == 0) break; if (newdev) { grub_file_close (newdev->file); newdev->file = file; return 0; } /* Unable to replace it, make a new entry. */ newdev = grub_malloc (sizeof (struct grub_loopback)); if (! newdev) goto fail; newdev->devname = grub_strdup (args[0]); if (! newdev->devname) { grub_free (newdev); goto fail; } newdev->file = file; newdev->id = last_id++; /* Add the new entry to the list. */ newdev->next = loopback_list; loopback_list = newdev; return 0; fail: ret = grub_errno; grub_file_close (file); return ret; } static int grub_loopback_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, grub_disk_pull_t pull) { struct grub_loopback *d; if (pull != GRUB_DISK_PULL_NONE) return 0; for (d = loopback_list; d; d = d->next) { if (hook (d->devname, hook_data)) return 1; } return 0; } static grub_err_t grub_loopback_open (const char *name, grub_disk_t disk) { struct grub_loopback *dev; for (dev = loopback_list; dev; dev = dev->next) if (grub_strcmp (dev->devname, name) == 0) break; if (! dev) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device"); /* Use the filesize for the disk size, round up to a complete sector. */ if (dev->file->size != GRUB_FILE_SIZE_UNKNOWN) disk->total_sectors = ((dev->file->size + GRUB_DISK_SECTOR_SIZE - 1) / GRUB_DISK_SECTOR_SIZE); else disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN; /* Avoid reading more than 512M. */ disk->max_agglomerate = 1 << (29 - GRUB_DISK_SECTOR_BITS - GRUB_DISK_CACHE_BITS); disk->id = dev->id; disk->data = dev; return 0; } static grub_err_t grub_loopback_read (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t size, char *buf) { grub_file_t file = ((struct grub_loopback *) disk->data)->file; grub_off_t pos; grub_file_seek (file, sector << GRUB_DISK_SECTOR_BITS); grub_file_read (file, buf, size << GRUB_DISK_SECTOR_BITS); if (grub_errno) return grub_errno; /* In case there is more data read than there is available, in case of files that are not a multiple of GRUB_DISK_SECTOR_SIZE, fill the rest with zeros. */ pos = (sector + size) << GRUB_DISK_SECTOR_BITS; if (pos > file->size) { grub_size_t amount = pos - file->size; grub_memset (buf + (size << GRUB_DISK_SECTOR_BITS) - amount, 0, amount); } return 0; } static grub_err_t grub_loopback_write (grub_disk_t disk __attribute ((unused)), grub_disk_addr_t sector __attribute ((unused)), grub_size_t size __attribute ((unused)), const char *buf __attribute ((unused))) { return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "loopback write is not supported"); } static struct grub_disk_dev grub_loopback_dev = { .name = "loopback", .id = GRUB_DISK_DEVICE_LOOPBACK_ID, .iterate = grub_loopback_iterate, .open = grub_loopback_open, .read = grub_loopback_read, .write = grub_loopback_write, .next = 0 }; static grub_extcmd_t cmd; GRUB_MOD_INIT(loopback) { cmd = grub_register_extcmd ("loopback", grub_cmd_loopback, 0, N_("[-d] DEVICENAME FILE."), /* TRANSLATORS: The file itself is not destroyed or transformed into drive. */ N_("Make a virtual drive from a file."), options); grub_disk_dev_register (&grub_loopback_dev); } GRUB_MOD_FINI(loopback) { grub_unregister_extcmd (cmd); grub_disk_dev_unregister (&grub_loopback_dev); }