/*
* superblocks.c - reads information from filesystem and raid superblocks
*
* Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
*
* This file may be redistributed under the terms of the
* GNU Lesser General Public License.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdint.h>
#include <stdarg.h>
#include "superblocks.h"
/**
* SECTION:superblocks
* @title: Superblocks probing
* @short_description: filesystems and raids superblocks probing.
*
* The library API has been originally designed for superblocks probing only.
* This is reason why some *deprecated* superblock specific functions don't use
* '_superblocks_' namespace in the function name. Please, don't use these
* functions in new code.
*
* The 'superblocks' probers support NAME=value (tags) interface only. The
* superblocks probing is enabled by default (and controlled by
* blkid_probe_enable_superblocks()).
*
* Currently supported tags:
*
* @TYPE: filesystem type
*
* @SEC_TYPE: secondary filesystem type
*
* @LABEL: filesystem label
*
* @LABEL_RAW: raw label from FS superblock
*
* @UUID: filesystem UUID (lower case)
*
* @UUID_SUB: subvolume uuid (e.g. btrfs)
*
* @UUID_RAW: raw UUID from FS superblock
*
* @EXT_JOURNAL: external journal UUID
*
* @USAGE: usage string: "raid", "filesystem", ...
*
* @VERSION: filesystem version
*
* @MOUNT: cluster mount name (?) -- ocfs only
*
* @SBMAGIC: super block magic string
*
* @SBMAGIC_OFFSET: offset of SBMAGIC
*
* @FSSIZE: size of filessystem [not-implemented yet]
*
* @SYSTEM_ID: ISO9660 system identifier
*
* @PUBLISHER_ID: ISO9660 publisher identifier
*
* @APPLICATION_ID: ISO9660 application identifier
*
* @BOOT_SYSTEM_ID: ISO9660 boot system identifier
*/
static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn);
static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn);
static int blkid_probe_set_usage(blkid_probe pr, int usage);
/*
* Superblocks chains probing functions
*/
static const struct blkid_idinfo *idinfos[] =
{
/* RAIDs */
&linuxraid_idinfo,
&ddfraid_idinfo,
&iswraid_idinfo,
&lsiraid_idinfo,
&viaraid_idinfo,
&silraid_idinfo,
&nvraid_idinfo,
&pdcraid_idinfo,
&highpoint45x_idinfo,
&highpoint37x_idinfo,
&adraid_idinfo,
&jmraid_idinfo,
&drbd_idinfo,
&drbdproxy_datalog_idinfo,
&lvm2_idinfo,
&lvm1_idinfo,
&snapcow_idinfo,
&verity_hash_idinfo,
&luks_idinfo,
&vmfs_volume_idinfo,
/* Filesystems */
&vfat_idinfo,
&swsuspend_idinfo,
&swap_idinfo,
&xfs_idinfo,
&ext4dev_idinfo,
&ext4_idinfo,
&ext3_idinfo,
&ext2_idinfo,
&jbd_idinfo,
&reiser_idinfo,
&reiser4_idinfo,
&jfs_idinfo,
&udf_idinfo,
&iso9660_idinfo,
&zfs_idinfo,
&hfsplus_idinfo,
&hfs_idinfo,
&ufs_idinfo,
&hpfs_idinfo,
&sysv_idinfo,
&xenix_idinfo,
&ntfs_idinfo,
&cramfs_idinfo,
&romfs_idinfo,
&minix_idinfo,
&gfs_idinfo,
&gfs2_idinfo,
&ocfs_idinfo,
&ocfs2_idinfo,
&oracleasm_idinfo,
&vxfs_idinfo,
&squashfs_idinfo,
&netware_idinfo,
&btrfs_idinfo,
&ubifs_idinfo,
&bfs_idinfo,
&vmfs_fs_idinfo,
&befs_idinfo,
&nilfs2_idinfo,
&exfat_idinfo,
&f2fs_idinfo
};
/*
* Driver definition
*/
const struct blkid_chaindrv superblocks_drv = {
.id = BLKID_CHAIN_SUBLKS,
.name = "superblocks",
.dflt_enabled = TRUE,
.dflt_flags = BLKID_SUBLKS_DEFAULT,
.idinfos = idinfos,
.nidinfos = ARRAY_SIZE(idinfos),
.has_fltr = TRUE,
.probe = superblocks_probe,
.safeprobe = superblocks_safeprobe,
};
/**
* blkid_probe_enable_superblocks:
* @pr: probe
* @enable: TRUE/FALSE
*
* Enables/disables the superblocks probing for non-binary interface.
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_enable_superblocks(blkid_probe pr, int enable)
{
if (!pr)
return -1;
pr->chains[BLKID_CHAIN_SUBLKS].enabled = enable;
return 0;
}
/**
* blkid_probe_set_superblocks_flags:
* @pr: prober
* @flags: BLKID_SUBLKS_* flags
*
* Sets probing flags to the superblocks prober. This function is optional, the
* default are BLKID_SUBLKS_DEFAULTS flags.
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags)
{
if (!pr)
return -1;
pr->chains[BLKID_CHAIN_SUBLKS].flags = flags;
return 0;
}
/**
* blkid_probe_reset_superblocks_filter:
* @pr: prober
*
* Resets superblocks probing filter
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_reset_superblocks_filter(blkid_probe pr)
{
return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
}
/**
* blkid_probe_invert_superblocks_filter:
* @pr: prober
*
* Inverts superblocks probing filter
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_invert_superblocks_filter(blkid_probe pr)
{
return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
}
/**
* blkid_probe_filter_superblocks_type:
* @pr: prober
* @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
* @names: NULL terminated array of probing function names (e.g. "vfat").
*
* %BLKID_FLTR_NOTIN - probe for all items which are NOT IN @names;
*
* %BLKID_FLTR_ONLYIN - probe for items which are IN @names
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[])
{
return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
}
/**
* blkid_probe_filter_superblocks_usage:
* @pr: prober
* @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
* @usage: BLKID_USAGE_* flags
*
* %BLKID_FLTR_NOTIN - probe for all items which are NOT IN @usage;
*
* %BLKID_FLTR_ONLYIN - probe for items which are IN @usage
*
* Returns: 0 on success, or -1 in case of error.
*/
int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage)
{
unsigned long *fltr;
struct blkid_chain *chn;
size_t i;
fltr = blkid_probe_get_filter(pr, BLKID_CHAIN_SUBLKS, TRUE);
if (!fltr)
return -1;
chn = &pr->chains[BLKID_CHAIN_SUBLKS];
for (i = 0; i < chn->driver->nidinfos; i++) {
const struct blkid_idinfo *id = chn->driver->idinfos[i];
if (id->usage & usage) {
if (flag & BLKID_FLTR_NOTIN)
blkid_bmp_set_item(chn->fltr, i);
} else if (flag & BLKID_FLTR_ONLYIN)
blkid_bmp_set_item(chn->fltr, i);
}
DBG(DEBUG_LOWPROBE, printf("a new probing usage-filter initialized\n"));
return 0;
}
/**
* blkid_known_fstype:
* @fstype: filesystem name
*
* Returns: 1 for known filesytems, or 0 for unknown filesystem.
*/
int blkid_known_fstype(const char *fstype)
{
size_t i;
if (!fstype)
return 0;
for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
const struct blkid_idinfo *id = idinfos[i];
if (strcmp(id->name, fstype) == 0)
return 1;
}
return 0;
}
/**
* blkid_superblocks_get_name:
* @idx: number >= 0
* @name: returns name of supported filesystem/raid (optional)
* @usage: returns BLKID_USAGE_* flags, (optional)
*
* Returns: -1 if @idx is out of range, or 0 on success.
*/
int blkid_superblocks_get_name(size_t idx, const char **name, int *usage)
{
if (idx < ARRAY_SIZE(idinfos)) {
if (name)
*name = idinfos[idx]->name;
if (usage)
*usage = idinfos[idx]->usage;
return 0;
}
return -1;
}
/*
* The blkid_do_probe() backend.
*/
static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn)
{
size_t i;
if (!pr || chn->idx < -1)
return -1;
blkid_probe_chain_reset_vals(pr, chn);
DBG(DEBUG_LOWPROBE,
printf("--> starting probing loop [SUBLKS idx=%d]\n",
chn->idx));
if (pr->size <= 0 || (pr->size <= 1024 && !S_ISCHR(pr->mode)))
/* Ignore very very small block devices or regular files (e.g.
* extended partitions). Note that size of the UBI char devices
* is 1 byte */
goto nothing;
i = chn->idx < 0 ? 0 : chn->idx + 1U;
for ( ; i < ARRAY_SIZE(idinfos); i++) {
const struct blkid_idinfo *id;
const struct blkid_idmag *mag = NULL;
blkid_loff_t off = 0;
int rc = 0;
chn->idx = i;
id = idinfos[i];
if (chn->fltr && blkid_bmp_get_item(chn->fltr, i)) {
DBG(DEBUG_LOWPROBE, printf("filter out: %s\n", id->name));
continue;
}
if (id->minsz && id->minsz > pr->size)
continue; /* the device is too small */
/* don't probe for RAIDs, swap or journal on CD/DVDs */
if ((id->usage & (BLKID_USAGE_RAID | BLKID_USAGE_OTHER)) &&
blkid_probe_is_cdrom(pr))
continue;
/* don't probe for RAIDs on floppies */
if ((id->usage & BLKID_USAGE_RAID) && blkid_probe_is_tiny(pr))
continue;
DBG(DEBUG_LOWPROBE, printf("[%zd] %s:\n", i, id->name));
if (blkid_probe_get_idmag(pr, id, &off, &mag))
continue;
/* final check by probing function */
if (id->probefunc) {
DBG(DEBUG_LOWPROBE, printf("\tcall probefunc()\n"));
if (id->probefunc(pr, mag) != 0) {
blkid_probe_chain_reset_vals(pr, chn);
continue;
}
}
/* all cheks passed */
if (chn->flags & BLKID_SUBLKS_TYPE)
rc = blkid_probe_set_value(pr, "TYPE",
(unsigned char *) id->name,
strlen(id->name) + 1);
if (!rc)
rc = blkid_probe_set_usage(pr, id->usage);
if (!rc && mag)
rc = blkid_probe_set_magic(pr, off, mag->len,
(unsigned char *) mag->magic);
if (rc) {
blkid_probe_chain_reset_vals(pr, chn);
DBG(DEBUG_LOWPROBE, printf("failed to set result -- ingnore\n"));
continue;
}
DBG(DEBUG_LOWPROBE,
printf("<-- leaving probing loop (type=%s) [SUBLKS idx=%d]\n",
id->name, chn->idx));
return 0;
}
nothing:
DBG(DEBUG_LOWPROBE,
printf("<-- leaving probing loop (failed) [SUBLKS idx=%d]\n",
chn->idx));
return 1;
}
/*
* This is the same function as blkid_do_probe(), but returns only one result
* (cannot be used in while()) and checks for ambivalen results (more
* filesystems on the device) -- in such case returns -2.
*
* The function does not check for filesystems when a RAID or crypto signature
* is detected. The function also does not check for collision between RAIDs
* and crypto devices. The first detected RAID or crypto device is returned.
*
* The function does not probe for ambivalent results on very small devices
* (e.g. floppies), on small devices the first detected filesystem is returned.
*/
static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn)
{
struct blkid_prval vals[BLKID_NVALS_SUBLKS];
int nvals = BLKID_NVALS_SUBLKS;
int idx = -1;
int count = 0;
int intol = 0;
int rc;
while ((rc = superblocks_probe(pr, chn)) == 0) {
if (blkid_probe_is_tiny(pr) && !count)
/* floppy or so -- returns the first result. */
return 0;
count++;
if (chn->idx >= 0 &&
idinfos[chn->idx]->usage & (BLKID_USAGE_RAID | BLKID_USAGE_CRYPTO))
break;
if (chn->idx >= 0 &&
!(idinfos[chn->idx]->flags & BLKID_IDINFO_TOLERANT))
intol++;
if (count == 1) {
/* save the first result */
nvals = blkid_probe_chain_copy_vals(pr, chn, vals, nvals);
idx = chn->idx;
}
}
if (rc < 0)
return rc; /* error */
if (count > 1 && intol) {
DBG(DEBUG_LOWPROBE,
printf("ERROR: superblocks chain: "
"ambivalent result detected (%d filesystems)!\n",
count));
return -2; /* error, ambivalent result (more FS) */
}
if (!count)
return 1; /* nothing detected */
if (idx != -1) {
/* restore the first result */
blkid_probe_chain_reset_vals(pr, chn);
blkid_probe_append_vals(pr, vals, nvals);
chn->idx = idx;
}
/*
* The RAID device could be partitioned. The problem are RAID1 devices
* where the partition table is visible from underlaying devices. We
* have to ignore such partition tables.
*/
if (chn->idx >= 0 && idinfos[chn->idx]->usage & BLKID_USAGE_RAID)
pr->prob_flags |= BLKID_PROBE_FL_IGNORE_PT;
return 0;
}
int blkid_probe_set_version(blkid_probe pr, const char *version)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
if (chn->flags & BLKID_SUBLKS_VERSION)
return blkid_probe_set_value(pr, "VERSION",
(unsigned char *) version, strlen(version) + 1);
return 0;
}
int blkid_probe_sprintf_version(blkid_probe pr, const char *fmt, ...)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
int rc = 0;
if (chn->flags & BLKID_SUBLKS_VERSION) {
va_list ap;
va_start(ap, fmt);
rc = blkid_probe_vsprintf_value(pr, "VERSION", fmt, ap);
va_end(ap);
}
return rc;
}
static int blkid_probe_set_usage(blkid_probe pr, int usage)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
char *u = NULL;
if (!(chn->flags & BLKID_SUBLKS_USAGE))
return 0;
if (usage & BLKID_USAGE_FILESYSTEM)
u = "filesystem";
else if (usage & BLKID_USAGE_RAID)
u = "raid";
else if (usage & BLKID_USAGE_CRYPTO)
u = "crypto";
else if (usage & BLKID_USAGE_OTHER)
u = "other";
else
u = "unknown";
return blkid_probe_set_value(pr, "USAGE", (unsigned char *) u, strlen(u) + 1);
}
int blkid_probe_set_id_label(blkid_probe pr, const char *name,
unsigned char *data, size_t len)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
struct blkid_prval *v;
if (!(chn->flags & BLKID_SUBLKS_LABEL))
return 0;
v = blkid_probe_assign_value(pr, name);
if (!v)
return -1;
if (len >= BLKID_PROBVAL_BUFSIZ)
len = BLKID_PROBVAL_BUFSIZ - 1; /* make a space for \0 */
memcpy(v->data, data, len);
v->data[len] = '\0';
/* remove white spaces */
v->len = blkid_rtrim_whitespace(v->data) + 1;
if (v->len > 1)
v->len = blkid_ltrim_whitespace(v->data) + 1;
if (v->len <= 1)
blkid_probe_reset_last_value(pr); /* ignore empty */
return 0;
}
int blkid_probe_set_label(blkid_probe pr, unsigned char *label, size_t len)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
struct blkid_prval *v;
if (len > BLKID_PROBVAL_BUFSIZ)
len = BLKID_PROBVAL_BUFSIZ;
if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
blkid_probe_set_value(pr, "LABEL_RAW", label, len) < 0)
return -1;
if (!(chn->flags & BLKID_SUBLKS_LABEL))
return 0;
v = blkid_probe_assign_value(pr, "LABEL");
if (!v)
return -1;
if (len == BLKID_PROBVAL_BUFSIZ)
len--; /* make a space for \0 */
memcpy(v->data, label, len);
v->data[len] = '\0';
v->len = blkid_rtrim_whitespace(v->data) + 1;
if (v->len == 1)
blkid_probe_reset_last_value(pr);
return 0;
}
int blkid_probe_set_utf8label(blkid_probe pr, unsigned char *label,
size_t len, int enc)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
struct blkid_prval *v;
if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
blkid_probe_set_value(pr, "LABEL_RAW", label, len) < 0)
return -1;
if (!(chn->flags & BLKID_SUBLKS_LABEL))
return 0;
v = blkid_probe_assign_value(pr, "LABEL");
if (!v)
return -1;
blkid_encode_to_utf8(enc, v->data, sizeof(v->data), label, len);
v->len = blkid_rtrim_whitespace(v->data) + 1;
if (v->len == 1)
blkid_probe_reset_last_value(pr);
return 0;
}
/* like uuid_is_null() from libuuid, but works with arbitrary size of UUID */
static int 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;
}
int blkid_probe_sprintf_uuid(blkid_probe pr, unsigned char *uuid,
size_t len, const char *fmt, ...)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
int rc = -1;
va_list ap;
if (len > BLKID_PROBVAL_BUFSIZ)
len = BLKID_PROBVAL_BUFSIZ;
if (uuid_is_empty(uuid, len))
return 0;
if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
blkid_probe_set_value(pr, "UUID_RAW", uuid, len) < 0)
return -1;
if (!(chn->flags & BLKID_SUBLKS_UUID))
return 0;
va_start(ap, fmt);
rc = blkid_probe_vsprintf_value(pr, "UUID", fmt, ap);
va_end(ap);
/* convert to lower case (..be paranoid) */
if (!rc) {
size_t i;
struct blkid_prval *v = __blkid_probe_get_value(pr,
blkid_probe_numof_values(pr));
if (v) {
for (i = 0; i < v->len; i++)
if (v->data[i] >= 'A' && v->data[i] <= 'F')
v->data[i] = (v->data[i] - 'A') + 'a';
}
}
return rc;
}
/* function to set UUIDs that are in suberblocks stored as strings */
int blkid_probe_strncpy_uuid(blkid_probe pr, unsigned char *str, size_t len)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
struct blkid_prval *v;
if (str == NULL || *str == '\0')
return -1;
if (!len)
len = strlen((char *) str);
if (len > BLKID_PROBVAL_BUFSIZ)
len = BLKID_PROBVAL_BUFSIZ;
if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
blkid_probe_set_value(pr, "UUID_RAW", str, len) < 0)
return -1;
if (!(chn->flags & BLKID_SUBLKS_UUID))
return 0;
v = blkid_probe_assign_value(pr, "UUID");
if (v) {
if (len == BLKID_PROBVAL_BUFSIZ)
len--; /* make a space for \0 */
memcpy((char *) v->data, str, len);
v->data[len] = '\0';
v->len = len + 1;
return 0;
}
return -1;
}
/* default _set_uuid function to set DCE UUIDs */
int blkid_probe_set_uuid_as(blkid_probe pr, unsigned char *uuid, const char *name)
{
struct blkid_chain *chn = blkid_probe_get_chain(pr);
struct blkid_prval *v;
if (uuid_is_empty(uuid, 16))
return 0;
if (!name) {
if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
blkid_probe_set_value(pr, "UUID_RAW", uuid, 16) < 0)
return -1;
if (!(chn->flags & BLKID_SUBLKS_UUID))
return 0;
v = blkid_probe_assign_value(pr, "UUID");
} else
v = blkid_probe_assign_value(pr, name);
blkid_unparse_uuid(uuid, (char *) v->data, sizeof(v->data));
v->len = 37;
return 0;
}
int blkid_probe_set_uuid(blkid_probe pr, unsigned char *uuid)
{
return blkid_probe_set_uuid_as(pr, uuid, NULL);
}
/**
* blkid_probe_set_request:
* @pr: probe
* @flags: BLKID_PROBREQ_* (deprecated) or BLKID_SUBLKS_* flags
*
* Returns: 0 on success, or -1 in case of error.
*
* Deprecated: Use blkid_probe_set_superblocks_flags().
*/
int blkid_probe_set_request(blkid_probe pr, int flags)
{
return blkid_probe_set_superblocks_flags(pr, flags);
}
/**
* blkid_probe_reset_filter:
* @pr: prober
*
* Returns: 0 on success, or -1 in case of error.
*
* Deprecated: Use blkid_probe_reset_superblocks_filter().
*/
int blkid_probe_reset_filter(blkid_probe pr)
{
return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
}
/**
* blkid_probe_invert_filter:
* @pr: prober
*
* Returns: 0 on success, or -1 in case of error.
*
* Deprecated: Use blkid_probe_invert_superblocks_filter().
*/
int blkid_probe_invert_filter(blkid_probe pr)
{
return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
}
/**
* blkid_probe_filter_types
* @pr: prober
* @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
* @names: NULL terminated array of probing function names (e.g. "vfat").
*
* Returns: 0 on success, or -1 in case of error.
*
* Deprecated: Use blkid_probe_filter_superblocks_types().
*/
int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
{
return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
}
/**
* blkid_probe_filter_usage
* @pr: prober
* @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
* @usage: BLKID_USAGE_* flags
*
* Returns: 0 on success, or -1 in case of error.
*
* Deprecated: Use blkid_probe_filter_superblocks_usage().
*/
int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
{
return blkid_probe_filter_superblocks_usage(pr, flag, usage);
}