diff options
Diffstat (limited to '')
-rw-r--r-- | libblkid/src/probe.c | 2356 |
1 files changed, 2356 insertions, 0 deletions
diff --git a/libblkid/src/probe.c b/libblkid/src/probe.c new file mode 100644 index 0000000..5acd273 --- /dev/null +++ b/libblkid/src/probe.c @@ -0,0 +1,2356 @@ +/* + * Low-level libblkid probing API + * + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: lowprobe + * @title: Low-level probing + * @short_description: low-level prober initialization + * + * The low-level probing routines always and directly read information from + * the selected (see blkid_probe_set_device()) device. + * + * The probing routines are grouped together into separate chains. Currently, + * the library provides superblocks, partitions and topology chains. + * + * The probing routines is possible to filter (enable/disable) by type (e.g. + * fstype "vfat" or partype "gpt") or by usage flags (e.g. BLKID_USAGE_RAID). + * These filters are per-chain. Note that always when you touch the chain + * filter the current probing position is reset and probing starts from + * scratch. It means that the chain filter should not be modified during + * probing, for example in loop where you call blkid_do_probe(). + * + * For more details see the chain specific documentation. + * + * The low-level API provides two ways how access to probing results. + * + * 1. The NAME=value (tag) interface. This interface is older and returns all data + * as strings. This interface is generic for all chains. + * + * 2. The binary interfaces. These interfaces return data in the native formats. + * The interface is always specific to the probing chain. + * + * Note that the previous probing result (binary or NAME=value) is always + * zeroized when a chain probing function is called. For example: + * + * <informalexample> + * <programlisting> + * blkid_probe_enable_partitions(pr, TRUE); + * blkid_probe_enable_superblocks(pr, FALSE); + * + * blkid_do_safeprobe(pr); + * </programlisting> + * </informalexample> + * + * overwrites the previous probing result for the partitions chain, the superblocks + * result is not modified. + */ + +/** + * SECTION: lowprobe-tags + * @title: Low-level tags + * @short_description: generic NAME=value interface. + * + * The probing routines inside the chain are mutually exclusive by default -- + * only few probing routines are marked as "tolerant". The "tolerant" probing + * routines are used for filesystem which can share the same device with any + * other filesystem. The blkid_do_safeprobe() checks for the "tolerant" flag. + * + * The SUPERBLOCKS chain is enabled by default. The all others chains is + * necessary to enable by blkid_probe_enable_'CHAINNAME'(). See chains specific + * documentation. + * + * The blkid_do_probe() function returns a result from only one probing + * routine, and the next call from the next probing routine. It means you need + * to call the function in loop, for example: + * + * <informalexample> + * <programlisting> + * while((blkid_do_probe(pr) == 0) + * ... use result ... + * </programlisting> + * </informalexample> + * + * The blkid_do_safeprobe() is the same as blkid_do_probe(), but returns only + * first probing result for every enabled chain. This function checks for + * ambivalent results (e.g. more "intolerant" filesystems superblocks on the + * device). + * + * The probing result is set of NAME=value pairs (the NAME is always unique). + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/types.h> +#ifdef HAVE_LINUX_CDROM_H +#include <linux/cdrom.h> +#endif +#ifdef HAVE_LINUX_BLKZONED_H +#include <linux/blkzoned.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_LINUX_FD_H +#include <linux/fd.h> +#endif +#include <inttypes.h> +#include <stdint.h> +#include <stdarg.h> +#include <limits.h> + +#include "blkidP.h" +#include "all-io.h" +#include "sysfs.h" +#include "strutils.h" +#include "list.h" +#include "fileutils.h" + +/* + * All supported chains + */ +static const struct blkid_chaindrv *chains_drvs[] = { + [BLKID_CHAIN_SUBLKS] = &superblocks_drv, + [BLKID_CHAIN_TOPLGY] = &topology_drv, + [BLKID_CHAIN_PARTS] = &partitions_drv +}; + +static void blkid_probe_reset_values(blkid_probe pr); + +/** + * blkid_new_probe: + * + * Returns: a pointer to the newly allocated probe struct or NULL in case of error. + */ +blkid_probe blkid_new_probe(void) +{ + int i; + blkid_probe pr; + + blkid_init_debug(0); + pr = calloc(1, sizeof(struct blkid_struct_probe)); + if (!pr) + return NULL; + + DBG(LOWPROBE, ul_debug("allocate a new probe")); + + /* initialize chains */ + for (i = 0; i < BLKID_NCHAINS; i++) { + pr->chains[i].driver = chains_drvs[i]; + pr->chains[i].flags = chains_drvs[i]->dflt_flags; + pr->chains[i].enabled = chains_drvs[i]->dflt_enabled; + } + INIT_LIST_HEAD(&pr->buffers); + INIT_LIST_HEAD(&pr->values); + INIT_LIST_HEAD(&pr->hints); + return pr; +} + +/* + * Clone @parent, the new clone shares all, but except: + * + * - probing result + * - buffers if another device (or offset) is set to the prober + */ +blkid_probe blkid_clone_probe(blkid_probe parent) +{ + blkid_probe pr; + + if (!parent) + return NULL; + + DBG(LOWPROBE, ul_debug("allocate a probe clone")); + + pr = blkid_new_probe(); + if (!pr) + return NULL; + + pr->fd = parent->fd; + pr->off = parent->off; + pr->size = parent->size; + pr->devno = parent->devno; + pr->disk_devno = parent->disk_devno; + pr->blkssz = parent->blkssz; + pr->flags = parent->flags; + pr->zone_size = parent->zone_size; + pr->parent = parent; + + pr->flags &= ~BLKID_FL_PRIVATE_FD; + + return pr; +} + + + +/** + * blkid_new_probe_from_filename: + * @filename: device or regular file + * + * This function is same as call open(filename), blkid_new_probe() and + * blkid_probe_set_device(pr, fd, 0, 0). + * + * The @filename is closed by blkid_free_probe() or by the + * blkid_probe_set_device() call. + * + * Returns: a pointer to the newly allocated probe struct or NULL in case of + * error. + */ +blkid_probe blkid_new_probe_from_filename(const char *filename) +{ + int fd; + blkid_probe pr = NULL; + + fd = open(filename, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return NULL; + + pr = blkid_new_probe(); + if (!pr) + goto err; + + if (blkid_probe_set_device(pr, fd, 0, 0)) + goto err; + + pr->flags |= BLKID_FL_PRIVATE_FD; + return pr; +err: + close(fd); + blkid_free_probe(pr); + return NULL; +} + +/** + * blkid_free_probe: + * @pr: probe + * + * Deallocates the probe struct, buffers and all allocated + * data that are associated with this probing control struct. + */ +void blkid_free_probe(blkid_probe pr) +{ + int i; + + if (!pr) + return; + + for (i = 0; i < BLKID_NCHAINS; i++) { + struct blkid_chain *ch = &pr->chains[i]; + + if (ch->driver->free_data) + ch->driver->free_data(pr, ch->data); + free(ch->fltr); + ch->fltr = NULL; + } + + if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0) + close(pr->fd); + blkid_probe_reset_buffers(pr); + blkid_probe_reset_values(pr); + blkid_probe_reset_hints(pr); + blkid_free_probe(pr->disk_probe); + + DBG(LOWPROBE, ul_debug("free probe")); + free(pr); +} + +void blkid_probe_free_value(struct blkid_prval *v) +{ + if (!v) + return; + + list_del(&v->prvals); + free(v->data); + + DBG(LOWPROBE, ul_debug(" free value %s", v->name)); + free(v); +} + +/* + * Removes chain values from probing result. + */ +void blkid_probe_chain_reset_values(blkid_probe pr, struct blkid_chain *chn) +{ + + struct list_head *p, *pnext; + + if (list_empty(&pr->values)) + return; + + DBG(LOWPROBE, ul_debug("Resetting %s values", chn->driver->name)); + + list_for_each_safe(p, pnext, &pr->values) { + struct blkid_prval *v = list_entry(p, + struct blkid_prval, prvals); + + if (v->chain == chn) + blkid_probe_free_value(v); + } +} + +static void blkid_probe_chain_reset_position(struct blkid_chain *chn) +{ + chn->idx = -1; +} + +/* + * Move chain values from probing result to @vals + */ +int blkid_probe_chain_save_values(blkid_probe pr, struct blkid_chain *chn, + struct list_head *vals) +{ + struct list_head *p, *pnext; + struct blkid_prval *v; + + DBG(LOWPROBE, ul_debug("saving %s values", chn->driver->name)); + + list_for_each_safe(p, pnext, &pr->values) { + + v = list_entry(p, struct blkid_prval, prvals); + if (v->chain != chn) + continue; + + list_del_init(&v->prvals); + list_add_tail(&v->prvals, vals); + } + return 0; +} + +/* + * Appends values from @vals to the probing result + */ +void blkid_probe_append_values_list(blkid_probe pr, struct list_head *vals) +{ + DBG(LOWPROBE, ul_debug("appending values")); + + list_splice(vals, &pr->values); + INIT_LIST_HEAD(vals); +} + + +void blkid_probe_free_values_list(struct list_head *vals) +{ + if (!vals) + return; + + DBG(LOWPROBE, ul_debug("freeing values list")); + + while (!list_empty(vals)) { + struct blkid_prval *v = list_entry(vals->next, struct blkid_prval, prvals); + blkid_probe_free_value(v); + } +} + +struct blkid_chain *blkid_probe_get_chain(blkid_probe pr) +{ + return pr->cur_chain; +} + +static const char *blkid_probe_get_probername(blkid_probe pr) +{ + struct blkid_chain *chn = blkid_probe_get_chain(pr); + + if (chn && chn->idx >= 0 && (unsigned)chn->idx < chn->driver->nidinfos) + return chn->driver->idinfos[chn->idx]->name; + + return NULL; +} + +void *blkid_probe_get_binary_data(blkid_probe pr, struct blkid_chain *chn) +{ + int rc, org_prob_flags; + struct blkid_chain *org_chn; + + /* save the current setting -- the binary API has to be completely + * independent on the current probing status + */ + org_chn = pr->cur_chain; + org_prob_flags = pr->prob_flags; + + pr->cur_chain = chn; + pr->prob_flags = 0; + chn->binary = TRUE; + blkid_probe_chain_reset_position(chn); + + rc = chn->driver->probe(pr, chn); + + chn->binary = FALSE; + blkid_probe_chain_reset_position(chn); + + /* restore the original setting + */ + pr->cur_chain = org_chn; + pr->prob_flags = org_prob_flags; + + if (rc != 0) + return NULL; + + DBG(LOWPROBE, ul_debug("returning %s binary data", chn->driver->name)); + return chn->data; +} + + +/** + * blkid_reset_probe: + * @pr: probe + * + * Zeroize probing results and resets the current probing (this has impact to + * blkid_do_probe() only). This function does not touch probing filters and + * keeps assigned device. + */ +void blkid_reset_probe(blkid_probe pr) +{ + int i; + + blkid_probe_reset_values(pr); + blkid_probe_set_wiper(pr, 0, 0); + + pr->cur_chain = NULL; + + for (i = 0; i < BLKID_NCHAINS; i++) + blkid_probe_chain_reset_position(&pr->chains[i]); +} + +/*** +static int blkid_probe_dump_filter(blkid_probe pr, int chain) +{ + struct blkid_chain *chn; + int i; + + if (!pr || chain < 0 || chain >= BLKID_NCHAINS) + return -1; + + chn = &pr->chains[chain]; + + if (!chn->fltr) + return -1; + + for (i = 0; i < chn->driver->nidinfos; i++) { + const struct blkid_idinfo *id = chn->driver->idinfos[i]; + + DBG(LOWPROBE, ul_debug("%d: %s: %s", + i, + id->name, + blkid_bmp_get_item(chn->fltr, i) + ? "disabled" : "enabled <--")); + } + return 0; +} +***/ + +/* + * Returns properly initialized chain filter + */ +unsigned long *blkid_probe_get_filter(blkid_probe pr, int chain, int create) +{ + struct blkid_chain *chn; + + if (chain < 0 || chain >= BLKID_NCHAINS) + return NULL; + + chn = &pr->chains[chain]; + + /* always when you touch the chain filter all indexes are reset and + * probing starts from scratch + */ + blkid_probe_chain_reset_position(chn); + pr->cur_chain = NULL; + + if (!chn->driver->has_fltr || (!chn->fltr && !create)) + return NULL; + + if (!chn->fltr) + chn->fltr = calloc(1, blkid_bmp_nbytes(chn->driver->nidinfos)); + else + memset(chn->fltr, 0, blkid_bmp_nbytes(chn->driver->nidinfos)); + + /* blkid_probe_dump_filter(pr, chain); */ + return chn->fltr; +} + +/* + * Generic private functions for filter setting + */ +int __blkid_probe_invert_filter(blkid_probe pr, int chain) +{ + size_t i; + struct blkid_chain *chn; + + chn = &pr->chains[chain]; + + if (!chn->driver->has_fltr || !chn->fltr) + return -1; + + for (i = 0; i < blkid_bmp_nwords(chn->driver->nidinfos); i++) + chn->fltr[i] = ~chn->fltr[i]; + + DBG(LOWPROBE, ul_debug("probing filter inverted")); + /* blkid_probe_dump_filter(pr, chain); */ + return 0; +} + +int __blkid_probe_reset_filter(blkid_probe pr, int chain) +{ + return blkid_probe_get_filter(pr, chain, FALSE) ? 0 : -1; +} + +int __blkid_probe_filter_types(blkid_probe pr, int chain, int flag, char *names[]) +{ + unsigned long *fltr; + struct blkid_chain *chn; + size_t i; + + fltr = blkid_probe_get_filter(pr, chain, TRUE); + if (!fltr) + return -1; + + chn = &pr->chains[chain]; + + for (i = 0; i < chn->driver->nidinfos; i++) { + int has = 0; + const struct blkid_idinfo *id = chn->driver->idinfos[i]; + char **n; + + for (n = names; *n; n++) { + if (!strcmp(id->name, *n)) { + has = 1; + break; + } + } + if (has) { + if (flag & BLKID_FLTR_NOTIN) + blkid_bmp_set_item(fltr, i); + } else if (flag & BLKID_FLTR_ONLYIN) + blkid_bmp_set_item(fltr, i); + } + + DBG(LOWPROBE, ul_debug("%s: a new probing type-filter initialized", + chn->driver->name)); + /* blkid_probe_dump_filter(pr, chain); */ + return 0; +} + +static struct blkid_bufinfo *read_buffer(blkid_probe pr, uint64_t real_off, uint64_t len) +{ + ssize_t ret; + struct blkid_bufinfo *bf = NULL; + + if (lseek(pr->fd, real_off, SEEK_SET) == (off_t) -1) { + errno = 0; + return NULL; + } + + /* someone trying to overflow some buffers? */ + if (len > ULONG_MAX - sizeof(struct blkid_bufinfo)) { + errno = ENOMEM; + return NULL; + } + + /* allocate info and space for data by one malloc call */ + bf = calloc(1, sizeof(struct blkid_bufinfo) + len); + if (!bf) { + errno = ENOMEM; + return NULL; + } + + bf->data = ((unsigned char *) bf) + sizeof(struct blkid_bufinfo); + bf->len = len; + bf->off = real_off; + INIT_LIST_HEAD(&bf->bufs); + + DBG(LOWPROBE, ul_debug("\tread: off=%"PRIu64" len=%"PRIu64"", + real_off, len)); + + ret = read(pr->fd, bf->data, len); + if (ret != (ssize_t) len) { + DBG(LOWPROBE, ul_debug("\tread failed: %m")); + free(bf); + + /* I/O errors on CDROMs are non-fatal to work with hybrid + * audio+data disks */ + if (ret >= 0 || blkid_probe_is_cdrom(pr)) + errno = 0; + return NULL; + } + + return bf; +} + +/* + * Search in buffers we already have in memory + */ +static struct blkid_bufinfo *get_cached_buffer(blkid_probe pr, uint64_t off, uint64_t len) +{ + uint64_t real_off = pr->off + off; + struct list_head *p; + + list_for_each(p, &pr->buffers) { + struct blkid_bufinfo *x = + list_entry(p, struct blkid_bufinfo, bufs); + + if (real_off >= x->off && real_off + len <= x->off + x->len) { + DBG(BUFFER, ul_debug("\treuse: off=%"PRIu64" len=%"PRIu64" (for off=%"PRIu64" len=%"PRIu64")", + x->off, x->len, real_off, len)); + return x; + } + } + return NULL; +} + +/* + * Zeroize in-memory data in already read buffer. The next blkid_probe_get_buffer() + * will return modified buffer. This is usable when you want to call the same probing + * function more than once and hide previously detected magic strings. + * + * See blkid_probe_hide_range(). + */ +static int hide_buffer(blkid_probe pr, uint64_t off, uint64_t len) +{ + uint64_t real_off = pr->off + off; + struct list_head *p; + int ct = 0; + + if (UINT64_MAX - len < off) { + DBG(BUFFER, ul_debug("\t hide-buffer overflow (ignore)")); + return -EINVAL; + } + + list_for_each(p, &pr->buffers) { + struct blkid_bufinfo *x = + list_entry(p, struct blkid_bufinfo, bufs); + unsigned char *data; + + if (real_off >= x->off && real_off + len <= x->off + x->len) { + + assert(x->off <= real_off); + assert(x->off + x->len >= real_off + len); + + data = real_off ? x->data + (real_off - x->off) : x->data; + + DBG(BUFFER, ul_debug("\thiding: off=%"PRIu64" len=%"PRIu64, + off, len)); + memset(data, 0, len); + ct++; + } + } + return ct == 0 ? -EINVAL : 0; +} + + +/* + * Note that @off is offset within probing area, the probing area is defined by + * pr->off and pr->size. + */ +unsigned char *blkid_probe_get_buffer(blkid_probe pr, uint64_t off, uint64_t len) +{ + struct blkid_bufinfo *bf = NULL; + uint64_t real_off = pr->off + off; + + /* + DBG(BUFFER, ul_debug("\t>>>> off=%ju, real-off=%ju (probe <%ju..%ju>, len=%ju", + off, real_off, pr->off, pr->off + pr->size, len)); + */ + if (pr->size == 0) { + errno = EINVAL; + return NULL; + } + + if (UINT64_MAX - len < off || UINT64_MAX - len < real_off) { + DBG(BUFFER, ul_debug("\t read-buffer overflow (ignore)")); + return NULL; + } + + if (len == 0 + || (!S_ISCHR(pr->mode) && (pr->size < off || pr->size < len)) + || (!S_ISCHR(pr->mode) && (pr->off + pr->size < real_off + len))) { + DBG(BUFFER, ul_debug("\t read-buffer out of probing area (ignore)")); + errno = 0; + return NULL; + } + + if (pr->parent && + pr->parent->devno == pr->devno && + pr->parent->off <= pr->off && + pr->parent->off + pr->parent->size >= pr->off + pr->size) { + /* + * This is a cloned prober and points to the same area as + * parent. Let's use parent's buffers. + * + * Note that pr->off (and pr->parent->off) is always from the + * begin of the device. + */ + return blkid_probe_get_buffer(pr->parent, + pr->off + off - pr->parent->off, len); + } + + /* try buffers we already have in memory or read from device */ + bf = get_cached_buffer(pr, off, len); + if (!bf) { + bf = read_buffer(pr, real_off, len); + if (!bf) + return NULL; + + list_add_tail(&bf->bufs, &pr->buffers); + } + + assert(bf->off <= real_off); + assert(bf->off + bf->len >= real_off + len); + + errno = 0; + return real_off ? bf->data + (real_off - bf->off) : bf->data; +} + +/** + * blkid_probe_reset_buffers: + * @pr: prober + * + * libblkid reuse all already read buffers from the device. The buffers may be + * modified by blkid_probe_hide_range(). This function reset and free all + * cached buffers. The next blkid_do_probe() will read all data from the + * device. + * + * Since: 2.31 + * + * Returns: <0 in case of failure, or 0 on success. + */ +int blkid_probe_reset_buffers(blkid_probe pr) +{ + uint64_t ct = 0, len = 0; + + pr->flags &= ~BLKID_FL_MODIF_BUFF; + + if (list_empty(&pr->buffers)) + return 0; + + DBG(BUFFER, ul_debug("Resetting probing buffers")); + + while (!list_empty(&pr->buffers)) { + struct blkid_bufinfo *bf = list_entry(pr->buffers.next, + struct blkid_bufinfo, bufs); + ct++; + len += bf->len; + list_del(&bf->bufs); + + DBG(BUFFER, ul_debug(" remove buffer: [off=%"PRIu64", len=%"PRIu64"]", + bf->off, bf->len)); + free(bf); + } + + DBG(LOWPROBE, ul_debug(" buffers summary: %"PRIu64" bytes by %"PRIu64" read() calls", + len, ct)); + + INIT_LIST_HEAD(&pr->buffers); + + return 0; +} + +/** + * blkid_probe_hide_range: + * @pr: prober + * @off: start of the range + * @len: size of the range + * + * This function modifies in-memory cached data from the device. The specified + * range is zeroized. This is usable together with blkid_probe_step_back(). + * The next blkid_do_probe() will not see specified area. + * + * Note that this is usable for already (by library) read data, and this + * function is not a way how to hide any large areas on your device. + * + * The function blkid_probe_reset_buffers() reverts all. + * + * Since: 2.31 + * + * Returns: <0 in case of failure, or 0 on success. + */ +int blkid_probe_hide_range(blkid_probe pr, uint64_t off, uint64_t len) +{ + int rc = hide_buffer(pr, off, len); + + if (rc == 0) + pr->flags |= BLKID_FL_MODIF_BUFF; + return rc; +} + + +static void blkid_probe_reset_values(blkid_probe pr) +{ + if (list_empty(&pr->values)) + return; + + DBG(LOWPROBE, ul_debug("resetting results")); + + while (!list_empty(&pr->values)) { + struct blkid_prval *v = list_entry(pr->values.next, + struct blkid_prval, prvals); + blkid_probe_free_value(v); + } + + INIT_LIST_HEAD(&pr->values); +} + +/* + * Small devices need a special care. + */ +int blkid_probe_is_tiny(blkid_probe pr) +{ + return (pr->flags & BLKID_FL_TINY_DEV); +} + +/* + * CDROMs may fail when probed for RAID (last sector problem) + */ +int blkid_probe_is_cdrom(blkid_probe pr) +{ + return (pr->flags & BLKID_FL_CDROM_DEV); +} + +#ifdef CDROM_GET_CAPABILITY + +static int is_sector_readable(int fd, uint64_t sector) +{ + char buf[512]; + ssize_t sz; + + if (lseek(fd, sector * 512, SEEK_SET) == (off_t) -1) + goto failed; + + sz = read(fd, buf, sizeof(buf)); + if (sz != (ssize_t) sizeof(buf)) + goto failed; + + return 1; +failed: + DBG(LOWPROBE, ul_debug("CDROM: read sector %"PRIu64" failed %m", sector)); + errno = 0; + return 0; +} + +/* + * Linux kernel reports (BLKGETSIZE) cdrom device size greater than area + * readable by read(2). We have to reduce the probing area to avoid unwanted + * I/O errors in probing functions. It seems that unreadable are always last 2 + * or 3 CD blocks (CD block size is 2048 bytes, it means 12 in 512-byte + * sectors). Linux kernel reports (CDROM_LAST_WRITTEN) also location of last + * written block, so we will reduce size based on it too. + */ +static void cdrom_size_correction(blkid_probe pr, uint64_t last_written) +{ + uint64_t n, nsectors = pr->size >> 9; + + if (last_written && nsectors > ((last_written+1) << 2)) + nsectors = (last_written+1) << 2; + + for (n = nsectors - 12; n < nsectors; n++) { + if (!is_sector_readable(pr->fd, n)) + goto failed; + } + + DBG(LOWPROBE, ul_debug("CDROM: full size available")); + return; +failed: + /* 'n' is the failed sector, reduce device size to n-1; */ + DBG(LOWPROBE, ul_debug("CDROM: reduce size from %ju to %ju.", + (uintmax_t) pr->size, + (uintmax_t) n << 9)); + pr->size = n << 9; +} + +#endif + +/** + * blkid_probe_set_device: + * @pr: probe + * @fd: device file descriptor + * @off: begin of probing area + * @size: size of probing area (zero means whole device/file) + * + * Assigns the device to probe control struct, resets internal buffers, resets + * the current probing, and close previously associated device (if open by + * libblkid). + * + * If @fd is < 0 than only resets the prober and returns 1. Note that + * blkid_reset_probe() keeps the device associated with the prober, but + * blkid_probe_set_device() does complete reset. + * + * Returns: -1 in case of failure, 0 on success and 1 on reset. + */ +int blkid_probe_set_device(blkid_probe pr, int fd, + blkid_loff_t off, blkid_loff_t size) +{ + struct stat sb; + uint64_t devsiz = 0; + char *dm_uuid = NULL; + int is_floppy = 0; + + blkid_reset_probe(pr); + blkid_probe_reset_buffers(pr); + + if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0) + close(pr->fd); + + if (pr->disk_probe) { + blkid_free_probe(pr->disk_probe); + pr->disk_probe = NULL; + } + + pr->flags &= ~BLKID_FL_PRIVATE_FD; + pr->flags &= ~BLKID_FL_TINY_DEV; + pr->flags &= ~BLKID_FL_CDROM_DEV; + pr->prob_flags = 0; + pr->fd = fd; + pr->off = (uint64_t) off; + pr->size = 0; + pr->devno = 0; + pr->disk_devno = 0; + pr->mode = 0; + pr->blkssz = 0; + pr->wipe_off = 0; + pr->wipe_size = 0; + pr->wipe_chain = NULL; + pr->zone_size = 0; + + if (fd < 0) + return 1; + + +#if defined(POSIX_FADV_RANDOM) && defined(HAVE_POSIX_FADVISE) + /* Disable read-ahead */ + posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM); +#endif + if (fstat(fd, &sb)) + goto err; + + if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode) && !S_ISREG(sb.st_mode)) { + errno = EINVAL; + goto err; + } + + pr->mode = sb.st_mode; + if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) + pr->devno = sb.st_rdev; + + if (S_ISBLK(sb.st_mode)) { + if (blkdev_get_size(fd, (unsigned long long *) &devsiz)) { + DBG(LOWPROBE, ul_debug("failed to get device size")); + goto err; + } + } else if (S_ISCHR(sb.st_mode)) { + char buf[PATH_MAX]; + + if (!sysfs_chrdev_devno_to_devname(sb.st_rdev, buf, sizeof(buf)) + || strncmp(buf, "ubi", 3) != 0) { + DBG(LOWPROBE, ul_debug("no UBI char device")); + errno = EINVAL; + goto err; + } + devsiz = 1; /* UBI devices are char... */ + } else if (S_ISREG(sb.st_mode)) + devsiz = sb.st_size; /* regular file */ + + pr->size = size ? (uint64_t)size : devsiz; + + if (off && size == 0) + /* only offset without size specified */ + pr->size -= (uint64_t) off; + + if (pr->off + pr->size > devsiz) { + DBG(LOWPROBE, ul_debug("area specified by offset and size is bigger than device")); + errno = EINVAL; + goto err; + } + + if (pr->size <= 1440 * 1024 && !S_ISCHR(sb.st_mode)) + pr->flags |= BLKID_FL_TINY_DEV; + +#ifdef FDGETFDCSTAT + if (S_ISBLK(sb.st_mode)) { + /* + * Re-open without O_NONBLOCK for floppy device. + * + * Since kernel commit c7e9d0020361f4308a70cdfd6d5335e273eb8717 + * floppy drive works bad when opened with O_NONBLOCK. + */ + struct floppy_fdc_state flst; + + if (ioctl(fd, FDGETFDCSTAT, &flst) >= 0) { + int flags = fcntl(fd, F_GETFL, 0); + + if (flags < 0) + goto err; + if (flags & O_NONBLOCK) { + flags &= ~O_NONBLOCK; + + fd = ul_reopen(fd, flags | O_CLOEXEC); + if (fd < 0) + goto err; + + pr->flags |= BLKID_FL_PRIVATE_FD; + pr->fd = fd; + } + is_floppy = 1; + } + errno = 0; + } +#endif + if (S_ISBLK(sb.st_mode) && + !is_floppy && + sysfs_devno_is_dm_private(sb.st_rdev, &dm_uuid)) { + DBG(LOWPROBE, ul_debug("ignore private device mapper device")); + pr->flags |= BLKID_FL_NOSCAN_DEV; + } + +#ifdef CDROM_GET_CAPABILITY + else if (S_ISBLK(sb.st_mode) && + !blkid_probe_is_tiny(pr) && + !dm_uuid && + !is_floppy && + blkid_probe_is_wholedisk(pr)) { + + long last_written = 0; + + /* + * pktcdvd.ko accepts only these ioctls: + * CDROMEJECT CDROMMULTISESSION CDROMREADTOCENTRY + * CDROM_LAST_WRITTEN CDROM_SEND_PACKET SCSI_IOCTL_SEND_COMMAND + * So CDROM_GET_CAPABILITY cannot be used for detecting pktcdvd + * devices. But CDROM_GET_CAPABILITY and CDROM_DRIVE_STATUS are + * fast so use them for detecting if medium is present. In any + * case use last written block form CDROM_LAST_WRITTEN. + */ + + if (ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0) { +# ifdef CDROM_DRIVE_STATUS + switch (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)) { + case CDS_TRAY_OPEN: + case CDS_NO_DISC: + errno = ENOMEDIUM; + goto err; + } +# endif + pr->flags |= BLKID_FL_CDROM_DEV; + } + +# ifdef CDROM_LAST_WRITTEN + if (ioctl(fd, CDROM_LAST_WRITTEN, &last_written) == 0) { + pr->flags |= BLKID_FL_CDROM_DEV; + } else { + if (errno == ENOMEDIUM) + goto err; + } +# endif + + if (pr->flags & BLKID_FL_CDROM_DEV) { + cdrom_size_correction(pr, last_written); + +# ifdef CDROMMULTISESSION + if (!pr->off && blkid_probe_get_hint(pr, "session_offset", NULL) < 0) { + struct cdrom_multisession multisession = { .addr_format = CDROM_LBA }; + if (ioctl(fd, CDROMMULTISESSION, &multisession) == 0 && multisession.xa_flag) + blkid_probe_set_hint(pr, "session_offset", (multisession.addr.lba << 11)); + } +# endif + } + } +#endif + free(dm_uuid); + +# ifdef BLKGETZONESZ + if (S_ISBLK(sb.st_mode) && !is_floppy) { + uint32_t zone_size_sector; + + if (!ioctl(pr->fd, BLKGETZONESZ, &zone_size_sector)) + pr->zone_size = zone_size_sector << 9; + } +# endif + + DBG(LOWPROBE, ul_debug("ready for low-probing, offset=%"PRIu64", size=%"PRIu64", zonesize=%"PRIu64, + pr->off, pr->size, pr->zone_size)); + DBG(LOWPROBE, ul_debug("whole-disk: %s, regfile: %s", + blkid_probe_is_wholedisk(pr) ?"YES" : "NO", + S_ISREG(pr->mode) ? "YES" : "NO")); + + return 0; +err: + DBG(LOWPROBE, ul_debug("failed to prepare a device for low-probing")); + return -1; + +} + +int blkid_probe_get_dimension(blkid_probe pr, uint64_t *off, uint64_t *size) +{ + *off = pr->off; + *size = pr->size; + return 0; +} + +int blkid_probe_set_dimension(blkid_probe pr, uint64_t off, uint64_t size) +{ + DBG(LOWPROBE, ul_debug( + "changing probing area: size=%"PRIu64", off=%"PRIu64" " + "-to-> size=%"PRIu64", off=%"PRIu64"", + pr->size, pr->off, size, off)); + + pr->off = off; + pr->size = size; + pr->flags &= ~BLKID_FL_TINY_DEV; + + if (pr->size <= 1440ULL * 1024ULL && !S_ISCHR(pr->mode)) + pr->flags |= BLKID_FL_TINY_DEV; + + blkid_probe_reset_buffers(pr); + + return 0; +} + +unsigned char *_blkid_probe_get_sb(blkid_probe pr, const struct blkid_idmag *mag, size_t size) +{ + uint64_t hint_offset; + + if (!mag->hoff || blkid_probe_get_hint(pr, mag->hoff, &hint_offset) < 0) + hint_offset = 0; + + return blkid_probe_get_buffer(pr, hint_offset + (mag->kboff << 10), size); +} + +/* + * Check for matching magic value. + * Returns BLKID_PROBE_OK if found, BLKID_PROBE_NONE if not found + * or no magic present, or negative value on error. + */ +int blkid_probe_get_idmag(blkid_probe pr, const struct blkid_idinfo *id, + uint64_t *offset, const struct blkid_idmag **res) +{ + const struct blkid_idmag *mag = NULL; + uint64_t off = 0; + + if (id) + mag = &id->magics[0]; + if (res) + *res = NULL; + + /* try to detect by magic string */ + while(mag && mag->magic) { + unsigned char *buf; + uint64_t kboff; + uint64_t hint_offset; + + if (!mag->hoff || blkid_probe_get_hint(pr, mag->hoff, &hint_offset) < 0) + hint_offset = 0; + + /* If the magic is for zoned device, skip non-zoned device */ + if (mag->is_zoned && !pr->zone_size) { + mag++; + continue; + } + + if (!mag->is_zoned) + kboff = mag->kboff; + else + kboff = ((mag->zonenum * pr->zone_size) >> 10) + mag->kboff_inzone; + + off = hint_offset + ((kboff + (mag->sboff >> 10)) << 10); + buf = blkid_probe_get_buffer(pr, off, 1024); + + if (!buf && errno) + return -errno; + + if (buf && !memcmp(mag->magic, + buf + (mag->sboff & 0x3ff), mag->len)) { + + DBG(LOWPROBE, ul_debug("\tmagic sboff=%u, kboff=%" PRIu64, + mag->sboff, kboff)); + if (offset) + *offset = off + (mag->sboff & 0x3ff); + if (res) + *res = mag; + return BLKID_PROBE_OK; + } + mag++; + } + + if (id && id->magics[0].magic) + /* magic string(s) defined, but not found */ + return BLKID_PROBE_NONE; + + return BLKID_PROBE_OK; +} + +static inline void blkid_probe_start(blkid_probe pr) +{ + DBG(LOWPROBE, ul_debug("start probe")); + pr->cur_chain = NULL; + pr->prob_flags = 0; + blkid_probe_set_wiper(pr, 0, 0); +} + +static inline void blkid_probe_end(blkid_probe pr) +{ + DBG(LOWPROBE, ul_debug("end probe")); + pr->cur_chain = NULL; + pr->prob_flags = 0; + blkid_probe_set_wiper(pr, 0, 0); +} + +/** + * blkid_do_probe: + * @pr: prober + * + * Calls probing functions in all enabled chains. The superblocks chain is + * enabled by default. The blkid_do_probe() stores result from only one + * probing function. It's necessary to call this routine in a loop to get + * results from all probing functions in all chains. The probing is reset + * by blkid_reset_probe() or by filter functions. + * + * This is string-based NAME=value interface only. + * + * <example> + * <title>basic case - use the first result only</title> + * <programlisting> + * if (blkid_do_probe(pr) == 0) { + * int nvals = blkid_probe_numof_values(pr); + * for (n = 0; n < nvals; n++) { + * if (blkid_probe_get_value(pr, n, &name, &data, &len) == 0) + * printf("%s = %s\n", name, data); + * } + * } + * </programlisting> + * </example> + * + * <example> + * <title>advanced case - probe for all signatures</title> + * <programlisting> + * while (blkid_do_probe(pr) == 0) { + * int nvals = blkid_probe_numof_values(pr); + * ... + * } + * </programlisting> + * </example> + * + * See also blkid_reset_probe(). + * + * Returns: 0 on success, 1 when probing is done and -1 in case of error. + */ +int blkid_do_probe(blkid_probe pr) +{ + int rc = 1; + + if (pr->flags & BLKID_FL_NOSCAN_DEV) + return 1; + + do { + struct blkid_chain *chn = pr->cur_chain; + + if (!chn) { + blkid_probe_start(pr); + chn = pr->cur_chain = &pr->chains[0]; + } + /* we go to the next chain only when the previous probing + * result was nothing (rc == 1) and when the current chain is + * disabled or we are at end of the current chain (chain->idx + + * 1 == sizeof chain) or the current chain bailed out right at + * the start (chain->idx == -1) + */ + else if (rc == 1 && (chn->enabled == FALSE || + chn->idx + 1 == (int) chn->driver->nidinfos || + chn->idx == -1)) { + + size_t idx = chn->driver->id + 1; + + if (idx < BLKID_NCHAINS) + chn = pr->cur_chain = &pr->chains[idx]; + else { + blkid_probe_end(pr); + return 1; /* all chains already probed */ + } + } + + chn->binary = FALSE; /* for sure... */ + + DBG(LOWPROBE, ul_debug("chain probe %s %s (idx=%d)", + chn->driver->name, + chn->enabled? "ENABLED" : "DISABLED", + chn->idx)); + + if (!chn->enabled) + continue; + + /* rc: -1 = error, 0 = success, 1 = no result */ + rc = chn->driver->probe(pr, chn); + + } while (rc == 1); + + return rc; +} + +#ifdef HAVE_LINUX_BLKZONED_H +static int is_conventional(blkid_probe pr, uint64_t offset) +{ + struct blk_zone_report *rep = NULL; + int ret; + uint64_t zone_mask; + + if (!pr->zone_size) + return 1; + + zone_mask = ~(pr->zone_size - 1); + rep = blkdev_get_zonereport(blkid_probe_get_fd(pr), + (offset & zone_mask) >> 9, 1); + if (!rep) + return -1; + + if (rep->zones[0].type == BLK_ZONE_TYPE_CONVENTIONAL) + ret = 1; + else + ret = 0; + + free(rep); + + return ret; +} +#else +static inline int is_conventional(blkid_probe pr __attribute__((__unused__)), + uint64_t offset __attribute__((__unused__))) +{ + return 1; +} +#endif + +/** + * blkid_do_wipe: + * @pr: prober + * @dryrun: if TRUE then don't touch the device. + * + * This function erases the current signature detected by @pr. The @pr has to + * be open in O_RDWR mode, BLKID_SUBLKS_MAGIC or/and BLKID_PARTS_MAGIC flags + * has to be enabled (if you want to erase also superblock with broken check + * sums then use BLKID_SUBLKS_BADCSUM too). + * + * After successful signature removing the @pr prober will be moved one step + * back and the next blkid_do_probe() call will again call previously called + * probing function. All in-memory cached data from the device are always + * reset. + * + * <example> + * <title>wipe all filesystems or raids from the device</title> + * <programlisting> + * fd = open(devname, O_RDWR|O_CLOEXEC); + * blkid_probe_set_device(pr, fd, 0, 0); + * + * blkid_probe_enable_superblocks(pr, 1); + * blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC); + * + * while (blkid_do_probe(pr) == 0) + * blkid_do_wipe(pr, FALSE); + * </programlisting> + * </example> + * + * See also blkid_probe_step_back() if you cannot use this built-in wipe + * function, but you want to use libblkid probing as a source for wiping. + * + * Returns: 0 on success, and -1 in case of error. + */ +int blkid_do_wipe(blkid_probe pr, int dryrun) +{ + const char *off = NULL; + size_t len = 0; + uint64_t offset, magoff; + int conventional; + char buf[BUFSIZ]; + int fd, rc = 0; + struct blkid_chain *chn; + + chn = pr->cur_chain; + if (!chn) + return -1; + + switch (chn->driver->id) { + case BLKID_CHAIN_SUBLKS: + rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL); + if (!rc) + rc = blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len); + break; + case BLKID_CHAIN_PARTS: + rc = blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &off, NULL); + if (!rc) + rc = blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len); + break; + default: + return 0; + } + + if (rc || len == 0 || off == NULL) + return 0; + + errno = 0; + magoff = strtoumax(off, NULL, 10); + if (errno) + return 0; + + offset = magoff + pr->off; + fd = blkid_probe_get_fd(pr); + if (fd < 0) + return -1; + + if (len > sizeof(buf)) + len = sizeof(buf); + + rc = is_conventional(pr, offset); + if (rc < 0) + return rc; + conventional = rc == 1; + + DBG(LOWPROBE, ul_debug( + "do_wipe [offset=0x%"PRIx64" (%"PRIu64"), len=%zu, chain=%s, idx=%d, dryrun=%s]\n", + offset, offset, len, chn->driver->name, chn->idx, dryrun ? "yes" : "not")); + + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) + return -1; + + if (!dryrun && len) { + if (conventional) { + memset(buf, 0, len); + + /* wipen on device */ + if (write_all(fd, buf, len)) + return -1; + fsync(fd); + } else { +#ifdef HAVE_LINUX_BLKZONED_H + uint64_t zone_mask = ~(pr->zone_size - 1); + struct blk_zone_range range = { + .sector = (offset & zone_mask) >> 9, + .nr_sectors = pr->zone_size >> 9, + }; + + rc = ioctl(fd, BLKRESETZONE, &range); + if (rc < 0) + return -1; +#else + /* Should not reach here */ + assert(0); +#endif + } + + pr->flags &= ~BLKID_FL_MODIF_BUFF; /* be paranoid */ + + return blkid_probe_step_back(pr); + + } + + if (dryrun) { + /* wipe in memory only */ + blkid_probe_hide_range(pr, magoff, len); + return blkid_probe_step_back(pr); + } + + return 0; +} + +/** + * blkid_probe_step_back: + * @pr: prober + * + * This function move pointer to the probing chain one step back -- it means + * that the previously used probing function will be called again in the next + * blkid_do_probe() call. + * + * This is necessary for example if you erase or modify on-disk superblock + * according to the current libblkid probing result. + * + * Note that blkid_probe_hide_range() changes semantic of this function and + * cached buffers are not reset, but library uses in-memory modified + * buffers to call the next probing function. + * + * <example> + * <title>wipe all superblock, but use libblkid only for probing</title> + * <programlisting> + * pr = blkid_new_probe_from_filename(devname); + * + * blkid_probe_enable_superblocks(pr, 1); + * blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC); + * + * blkid_probe_enable_partitions(pr, 1); + * blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC); + * + * while (blkid_do_probe(pr) == 0) { + * const char *ostr = NULL; + * size_t len = 0; + * + * // superblocks + * if (blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &ostr, NULL) == 0) + * blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len); + * + * // partition tables + * if (len == 0 && blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &ostr, NULL) == 0) + * blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len); + * + * if (!len || !str) + * continue; + * + * // convert ostr to the real offset by off = strtoll(ostr, NULL, 10); + * // use your stuff to erase @len bytes at the @off + * .... + * + * // retry the last probing to check for backup superblocks ..etc. + * blkid_probe_step_back(pr); + * } + * </programlisting> + * </example> + * + * Returns: 0 on success, and -1 in case of error. + */ +int blkid_probe_step_back(blkid_probe pr) +{ + struct blkid_chain *chn; + + chn = pr->cur_chain; + if (!chn) + return -1; + + if (!(pr->flags & BLKID_FL_MODIF_BUFF)) + blkid_probe_reset_buffers(pr); + + if (chn->idx >= 0) { + chn->idx--; + DBG(LOWPROBE, ul_debug("step back: moving %s chain index to %d", + chn->driver->name, + chn->idx)); + } + + if (chn->idx == -1) { + /* blkid_do_probe() goes to the next chain if the index + * of the current chain is -1, so we have to set the + * chain pointer to the previous chain. + */ + size_t idx = chn->driver->id > 0 ? chn->driver->id - 1 : 0; + + DBG(LOWPROBE, ul_debug("step back: moving to previous chain")); + + if (idx > 0) + pr->cur_chain = &pr->chains[idx]; + else if (idx == 0) + pr->cur_chain = NULL; + } + + return 0; +} + +/** + * blkid_do_safeprobe: + * @pr: prober + * + * This function gathers probing results from all enabled chains and checks + * for ambivalent results (e.g. more filesystems on the device). + * + * This is string-based NAME=value interface only. + * + * Note about superblocks chain -- the function does not check for filesystems + * when a RAID signature is detected. The function also does not check for + * collision between RAIDs. The first detected RAID is returned. The function + * checks for collision between partition table and RAID signature -- it's + * recommended to enable partitions chain together with superblocks chain. + * + * Returns: 0 on success, 1 if nothing is detected, -2 if ambivalent result is + * detected and -1 on case of error. + */ +int blkid_do_safeprobe(blkid_probe pr) +{ + int i, count = 0, rc = 0; + + if (pr->flags & BLKID_FL_NOSCAN_DEV) + return 1; + + blkid_probe_start(pr); + + for (i = 0; i < BLKID_NCHAINS; i++) { + struct blkid_chain *chn; + + chn = pr->cur_chain = &pr->chains[i]; + chn->binary = FALSE; /* for sure... */ + + DBG(LOWPROBE, ul_debug("chain safeprobe %s %s", + chn->driver->name, + chn->enabled? "ENABLED" : "DISABLED")); + + if (!chn->enabled) + continue; + + blkid_probe_chain_reset_position(chn); + + rc = chn->driver->safeprobe(pr, chn); + + blkid_probe_chain_reset_position(chn); + + /* rc: -2 ambivalent, -1 = error, 0 = success, 1 = no result */ + if (rc < 0) + goto done; /* error */ + if (rc == 0) + count++; /* success */ + } + +done: + blkid_probe_end(pr); + if (rc < 0) + return rc; + return count ? 0 : 1; +} + +/** + * blkid_do_fullprobe: + * @pr: prober + * + * This function gathers probing results from all enabled chains. Same as + * blkid_do_safeprobe() but does not check for collision between probing + * result. + * + * This is string-based NAME=value interface only. + * + * Returns: 0 on success, 1 if nothing is detected or -1 on case of error. + */ +int blkid_do_fullprobe(blkid_probe pr) +{ + int i, count = 0, rc = 0; + + if (pr->flags & BLKID_FL_NOSCAN_DEV) + return 1; + + blkid_probe_start(pr); + + for (i = 0; i < BLKID_NCHAINS; i++) { + struct blkid_chain *chn; + + chn = pr->cur_chain = &pr->chains[i]; + chn->binary = FALSE; /* for sure... */ + + DBG(LOWPROBE, ul_debug("chain fullprobe %s: %s", + chn->driver->name, + chn->enabled? "ENABLED" : "DISABLED")); + + if (!chn->enabled) + continue; + + blkid_probe_chain_reset_position(chn); + + rc = chn->driver->probe(pr, chn); + + blkid_probe_chain_reset_position(chn); + + /* rc: -1 = error, 0 = success, 1 = no result */ + if (rc < 0) + goto done; /* error */ + if (rc == 0) + count++; /* success */ + } + +done: + blkid_probe_end(pr); + if (rc < 0) + return rc; + return count ? 0 : 1; +} + +/* same sa blkid_probe_get_buffer() but works with 512-sectors */ +unsigned char *blkid_probe_get_sector(blkid_probe pr, unsigned int sector) +{ + return blkid_probe_get_buffer(pr, ((uint64_t) sector) << 9, 0x200); +} + +struct blkid_prval *blkid_probe_assign_value(blkid_probe pr, const char *name) +{ + struct blkid_prval *v; + + v = calloc(1, sizeof(struct blkid_prval)); + if (!v) + return NULL; + + INIT_LIST_HEAD(&v->prvals); + v->name = name; + v->chain = pr->cur_chain; + list_add_tail(&v->prvals, &pr->values); + + DBG(LOWPROBE, ul_debug("assigning %s [%s]", name, v->chain->driver->name)); + return v; +} + +/* Note that value data is always terminated by zero to keep things robust, + * this extra zero is not count to the value length. It's caller responsibility + * to set proper value length (for strings we count terminator to the length, + * for binary data it's without terminator). + */ +int blkid_probe_value_set_data(struct blkid_prval *v, + const unsigned char *data, size_t len) +{ + v->data = calloc(1, len + 1); /* always terminate by \0 */ + + if (!v->data) + return -ENOMEM; + memcpy(v->data, data, len); + v->len = len; + return 0; +} + +int blkid_probe_set_value(blkid_probe pr, const char *name, + const unsigned char *data, size_t len) +{ + struct blkid_prval *v; + + v = blkid_probe_assign_value(pr, name); + if (!v) + return -1; + + return blkid_probe_value_set_data(v, data, len); +} + +int blkid_probe_vsprintf_value(blkid_probe pr, const char *name, + const char *fmt, va_list ap) +{ + struct blkid_prval *v; + ssize_t len; + + v = blkid_probe_assign_value(pr, name); + if (!v) + return -ENOMEM; + + len = vasprintf((char **) &v->data, fmt, ap); + + if (len <= 0) { + blkid_probe_free_value(v); + return len == 0 ? -EINVAL : -ENOMEM; + } + v->len = len + 1; + return 0; +} + +int blkid_probe_sprintf_value(blkid_probe pr, const char *name, + const char *fmt, ...) +{ + int rc; + va_list ap; + + va_start(ap, fmt); + rc = blkid_probe_vsprintf_value(pr, name, fmt, ap); + va_end(ap); + + return rc; +} + +int blkid_probe_set_magic(blkid_probe pr, uint64_t offset, + size_t len, const unsigned char *magic) +{ + int rc = 0; + struct blkid_chain *chn = blkid_probe_get_chain(pr); + + if (!chn || !len || chn->binary) + return 0; + + switch (chn->driver->id) { + case BLKID_CHAIN_SUBLKS: + if (!(chn->flags & BLKID_SUBLKS_MAGIC)) + return 0; + rc = blkid_probe_set_value(pr, "SBMAGIC", magic, len); + if (!rc) + rc = blkid_probe_sprintf_value(pr, + "SBMAGIC_OFFSET", "%llu", (unsigned long long)offset); + break; + case BLKID_CHAIN_PARTS: + if (!(chn->flags & BLKID_PARTS_MAGIC)) + return 0; + rc = blkid_probe_set_value(pr, "PTMAGIC", magic, len); + if (!rc) + rc = blkid_probe_sprintf_value(pr, + "PTMAGIC_OFFSET", "%llu", (unsigned long long)offset); + break; + default: + break; + } + + return rc; +} + +int blkid_probe_verify_csum(blkid_probe pr, uint64_t csum, uint64_t expected) +{ + if (csum != expected) { + struct blkid_chain *chn = blkid_probe_get_chain(pr); + + DBG(LOWPROBE, ul_debug( + "incorrect checksum for type %s," + " got %"PRIX64", expected %"PRIX64"", + blkid_probe_get_probername(pr), + csum, expected)); + /* + * Accept bad checksum if BLKID_SUBLKS_BADCSUM flags is set + */ + if (chn->driver->id == BLKID_CHAIN_SUBLKS + && (chn->flags & BLKID_SUBLKS_BADCSUM)) { + blkid_probe_set_value(pr, "SBBADCSUM", (unsigned char *) "1", 2); + goto accept; + } + return 0; /* bad checksum */ + } + +accept: + return 1; +} + +/** + * blkid_probe_get_devno: + * @pr: probe + * + * Returns: block device number, or 0 for regular files. + */ +dev_t blkid_probe_get_devno(blkid_probe pr) +{ + return pr->devno; +} + +/** + * blkid_probe_get_wholedisk_devno: + * @pr: probe + * + * Returns: device number of the wholedisk, or 0 for regular files. + */ +dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr) +{ + if (!pr->disk_devno) { + dev_t devno, disk_devno = 0; + + devno = blkid_probe_get_devno(pr); + if (!devno) + return 0; + + if (blkid_devno_to_wholedisk(devno, NULL, 0, &disk_devno) == 0) + pr->disk_devno = disk_devno; + } + return pr->disk_devno; +} + +/** + * blkid_probe_is_wholedisk: + * @pr: probe + * + * Returns: 1 if the device is whole-disk or 0. + */ +int blkid_probe_is_wholedisk(blkid_probe pr) +{ + dev_t devno, disk_devno; + + devno = blkid_probe_get_devno(pr); + if (!devno) + return 0; + + disk_devno = blkid_probe_get_wholedisk_devno(pr); + if (!disk_devno) + return 0; + + return devno == disk_devno; +} + +blkid_probe blkid_probe_get_wholedisk_probe(blkid_probe pr) +{ + dev_t disk; + + if (blkid_probe_is_wholedisk(pr)) + return NULL; /* this is not partition */ + + if (pr->parent) + /* this is cloned blkid_probe, use parent's stuff */ + return blkid_probe_get_wholedisk_probe(pr->parent); + + disk = blkid_probe_get_wholedisk_devno(pr); + + if (pr->disk_probe && pr->disk_probe->devno != disk) { + /* we have disk prober, but for another disk... close it */ + blkid_free_probe(pr->disk_probe); + pr->disk_probe = NULL; + } + + if (!pr->disk_probe) { + /* Open a new disk prober */ + char *disk_path = blkid_devno_to_devname(disk); + + if (!disk_path) + return NULL; + + DBG(LOWPROBE, ul_debug("allocate a wholedisk probe")); + + pr->disk_probe = blkid_new_probe_from_filename(disk_path); + + free(disk_path); + + if (!pr->disk_probe) + return NULL; /* ENOMEM? */ + } + + return pr->disk_probe; +} + +/** + * blkid_probe_get_size: + * @pr: probe + * + * This function returns size of probing area as defined by blkid_probe_set_device(). + * If the size of the probing area is unrestricted then this function returns + * the real size of device. See also blkid_get_dev_size(). + * + * Returns: size in bytes or -1 in case of error. + */ +blkid_loff_t blkid_probe_get_size(blkid_probe pr) +{ + return (blkid_loff_t) pr->size; +} + +/** + * blkid_probe_get_offset: + * @pr: probe + * + * This function returns offset of probing area as defined by blkid_probe_set_device(). + * + * Returns: offset in bytes or -1 in case of error. + */ +blkid_loff_t blkid_probe_get_offset(blkid_probe pr) +{ + return (blkid_loff_t) pr->off; +} + +/** + * blkid_probe_get_fd: + * @pr: probe + * + * Returns: file descriptor for assigned device/file or -1 in case of error. + */ +int blkid_probe_get_fd(blkid_probe pr) +{ + return pr->fd; +} + +/** + * blkid_probe_get_sectorsize: + * @pr: probe or NULL (for NULL returns 512) + * + * Returns: block device logical sector size (BLKSSZGET ioctl, default 512). + */ +unsigned int blkid_probe_get_sectorsize(blkid_probe pr) +{ + if (pr->blkssz) + return pr->blkssz; + + if (S_ISBLK(pr->mode) && + blkdev_get_sector_size(pr->fd, (int *) &pr->blkssz) == 0) + return pr->blkssz; + + pr->blkssz = DEFAULT_SECTOR_SIZE; + return pr->blkssz; +} + +/** + * blkid_probe_set_sectorsize: + * @pr: probe + * @sz: new size (to overwrite system default) + * + * Note that blkid_probe_set_device() resets this setting. Use it after + * blkid_probe_set_device() and before any probing call. + * + * Since: 2.30 + * + * Returns: 0 or <0 in case of error + */ +int blkid_probe_set_sectorsize(blkid_probe pr, unsigned int sz) +{ + pr->blkssz = sz; + return 0; +} + +/** + * blkid_probe_get_sectors: + * @pr: probe + * + * Returns: 512-byte sector count or -1 in case of error. + */ +blkid_loff_t blkid_probe_get_sectors(blkid_probe pr) +{ + return (blkid_loff_t) (pr->size >> 9); +} + +/** + * blkid_probe_numof_values: + * @pr: probe + * + * Returns: number of values in probing result or -1 in case of error. + */ +int blkid_probe_numof_values(blkid_probe pr) +{ + int i = 0; + struct list_head *p; + + list_for_each(p, &pr->values) + ++i; + return i; +} + +/** + * blkid_probe_get_value: + * @pr: probe + * @num: wanted value in range 0..N, where N is blkid_probe_numof_values() - 1 + * @name: pointer to return value name or NULL + * @data: pointer to return value data or NULL + * @len: pointer to return value length or NULL + * + * Note, the @len returns length of the @data, including the terminating + * '\0' character. + * + * Returns: 0 on success, or -1 in case of error. + */ +int blkid_probe_get_value(blkid_probe pr, int num, const char **name, + const char **data, size_t *len) +{ + struct blkid_prval *v = __blkid_probe_get_value(pr, num); + + if (!v) + return -1; + if (name) + *name = v->name; + if (data) + *data = (char *) v->data; + if (len) + *len = v->len; + + DBG(LOWPROBE, ul_debug("returning %s value", v->name)); + return 0; +} + +/** + * blkid_probe_lookup_value: + * @pr: probe + * @name: name of value + * @data: pointer to return value data or NULL + * @len: pointer to return value length or NULL + * + * Note, the @len returns length of the @data, including the terminating + * '\0' character. + * + * Returns: 0 on success, or -1 in case of error. + */ +int blkid_probe_lookup_value(blkid_probe pr, const char *name, + const char **data, size_t *len) +{ + struct blkid_prval *v = __blkid_probe_lookup_value(pr, name); + + if (!v) + return -1; + if (data) + *data = (char *) v->data; + if (len) + *len = v->len; + return 0; +} + +/** + * blkid_probe_has_value: + * @pr: probe + * @name: name of value + * + * Returns: 1 if value exist in probing result, otherwise 0. + */ +int blkid_probe_has_value(blkid_probe pr, const char *name) +{ + if (blkid_probe_lookup_value(pr, name, NULL, NULL) == 0) + return 1; + return 0; +} + +struct blkid_prval *__blkid_probe_get_value(blkid_probe pr, int num) +{ + int i = 0; + struct list_head *p; + + if (num < 0) + return NULL; + + list_for_each(p, &pr->values) { + if (i++ != num) + continue; + return list_entry(p, struct blkid_prval, prvals); + } + return NULL; +} + +struct blkid_prval *__blkid_probe_lookup_value(blkid_probe pr, const char *name) +{ + struct list_head *p; + + if (list_empty(&pr->values)) + return NULL; + + list_for_each(p, &pr->values) { + struct blkid_prval *v = list_entry(p, struct blkid_prval, + prvals); + + if (v->name && strcmp(name, v->name) == 0) { + DBG(LOWPROBE, ul_debug("returning %s value", v->name)); + return v; + } + } + return NULL; +} + + +/* converts DCE UUID (uuid[16]) to human readable string + * - the @len should be always 37 */ +void blkid_unparse_uuid(const unsigned char *uuid, char *str, size_t len) +{ + snprintf(str, len, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]); +} + +/* like uuid_is_null() from libuuid, but works with arbitrary size of UUID */ +int blkid_uuid_is_empty(const unsigned char *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + if (buf[i]) + return 0; + return 1; +} + +/* Removes whitespace from the right-hand side of a string (trailing + * whitespace). + * + * Returns size of the new string (without \0). + */ +size_t blkid_rtrim_whitespace(unsigned char *str) +{ + return rtrim_whitespace(str); +} + +/* Removes whitespace from the left-hand side of a string. + * + * Returns size of the new string (without \0). + */ +size_t blkid_ltrim_whitespace(unsigned char *str) +{ + return ltrim_whitespace(str); +} + +/* + * Some mkfs-like utils wipe some parts (usually begin) of the device. + * For example LVM (pvcreate) or mkswap(8). This information could be used + * for later resolution to conflicts between superblocks. + * + * For example we found valid LVM superblock, LVM wipes 8KiB at the begin of + * the device. If we found another signature (for example MBR) within the + * wiped area then the signature has been added later and LVM superblock + * should be ignore. + * + * Note that this heuristic is not 100% reliable, for example "pvcreate --zero n" + * can be used to keep the begin of the device unmodified. It's probably better + * to use this heuristic for conflicts between superblocks and partition tables + * than for conflicts between filesystem superblocks -- existence of unwanted + * partition table is very unusual, because PT is pretty visible (parsed and + * interpreted by kernel). + * + * Note that we usually expect only one signature on the device, it means that + * we have to remember only one wiped area from previously successfully + * detected signature. + * + * blkid_probe_set_wiper() -- defines wiped area (e.g. LVM) + * blkid_probe_use_wiper() -- try to use area (e.g. MBR) + * + * Note that there is not relation between _wiper and blkid_to_wipe(). + * + */ +void blkid_probe_set_wiper(blkid_probe pr, uint64_t off, uint64_t size) +{ + struct blkid_chain *chn; + + if (!size) { + DBG(LOWPROBE, ul_debug("zeroize wiper")); + pr->wipe_size = pr->wipe_off = 0; + pr->wipe_chain = NULL; + return; + } + + chn = pr->cur_chain; + + if (!chn || !chn->driver || + chn->idx < 0 || (size_t) chn->idx >= chn->driver->nidinfos) + return; + + pr->wipe_size = size; + pr->wipe_off = off; + pr->wipe_chain = chn; + + DBG(LOWPROBE, + ul_debug("wiper set to %s::%s off=%"PRIu64" size=%"PRIu64"", + chn->driver->name, + chn->driver->idinfos[chn->idx]->name, + pr->wipe_off, pr->wipe_size)); +} + +/* + * Returns 1 if the <@off,@size> area was wiped + */ +int blkid_probe_is_wiped(blkid_probe pr, struct blkid_chain **chn, uint64_t off, uint64_t size) +{ + if (!size) + return 0; + + if (pr->wipe_off <= off && off + size <= pr->wipe_off + pr->wipe_size) { + *chn = pr->wipe_chain; + return 1; + } + return 0; +} + +/* + * Try to use any area -- if the area has been previously wiped then the + * previous probing result should be ignored (reset). + */ +void blkid_probe_use_wiper(blkid_probe pr, uint64_t off, uint64_t size) +{ + struct blkid_chain *chn = NULL; + + if (blkid_probe_is_wiped(pr, &chn, off, size) && chn) { + DBG(LOWPROBE, ul_debug("previously wiped area modified " + " -- ignore previous results")); + blkid_probe_set_wiper(pr, 0, 0); + blkid_probe_chain_reset_values(pr, chn); + } +} + +static struct blkid_hint *get_hint(blkid_probe pr, const char *name) +{ + struct list_head *p; + + if (list_empty(&pr->hints)) + return NULL; + + list_for_each(p, &pr->hints) { + struct blkid_hint *h = list_entry(p, struct blkid_hint, hints); + + if (h->name && strcmp(name, h->name) == 0) + return h; + } + return NULL; +} + +/** + * blkid_probe_set_hint: + * @pr: probe + * @name: hint name or NAME=value + * @value: offset or another number + * + * Sets extra hint for low-level prober. If the hint is set by NAME=value + * notation than @value is ignored. The functions blkid_probe_set_device() + * and blkid_reset_probe() resets all hints. + * + * The hints are optional way how to force libblkid probing functions to check + * for example another location. + * + * Returns: 0 on success, or -1 in case of error. + */ +int blkid_probe_set_hint(blkid_probe pr, const char *name, uint64_t value) +{ + struct blkid_hint *hint = NULL; + char *n = NULL, *v = NULL; + + if (strchr(name, '=')) { + char *end = NULL; + + if (blkid_parse_tag_string(name, &n, &v) != 0) + goto done; + + errno = 0; + value = strtoumax(v, &end, 10); + + if (errno || v == end || (end && *end)) + goto done; + } + + hint = get_hint(pr, n ? n : name); + if (hint) { + /* alter old hint */ + hint->value = value; + DBG(LOWPROBE, + ul_debug("updated hint '%s' to %"PRIu64"", hint->name, hint->value)); + } else { + /* add a new hint */ + if (!n) { + n = strdup(name); + if (!n) + goto done; + } + hint = malloc(sizeof(*hint)); + if (!hint) + goto done; + + hint->name = n; + hint->value = value; + + INIT_LIST_HEAD(&hint->hints); + list_add_tail(&hint->hints, &pr->hints); + + DBG(LOWPROBE, + ul_debug("new hint '%s' is %"PRIu64"", hint->name, hint->value)); + n = NULL; + } +done: + free(n); + free(v); + + if (!hint) + return errno ? -errno : -EINVAL; + return 0; +} + +int blkid_probe_get_hint(blkid_probe pr, const char *name, uint64_t *value) +{ + struct blkid_hint *h = get_hint(pr, name); + + if (!h) + return -EINVAL; + if (value) + *value = h->value; + return 0; +} + +/** + * blkid_probe_reset_hints: + * @pr: probe + * + * Removes all previously defined probinig hints. See also blkid_probe_set_hint(). + */ +void blkid_probe_reset_hints(blkid_probe pr) +{ + if (list_empty(&pr->hints)) + return; + + DBG(LOWPROBE, ul_debug("resetting hints")); + + while (!list_empty(&pr->hints)) { + struct blkid_hint *h = list_entry(pr->hints.next, + struct blkid_hint, hints); + list_del(&h->hints); + free(h->name); + free(h); + } + + INIT_LIST_HEAD(&pr->hints); +} |