#include "libzfs_jni_diskmgt.h"
#include "libzfs_jni_util.h"
#include <strings.h>
#include <libzfs.h>
#include <sys/mnttab.h>
static char *get_device_name(dm_descriptor_t device, int *error);
static dmgt_disk_t *get_disk(dm_descriptor_t disk, int *error);
static char **get_disk_aliases(dm_descriptor_t disk, char *name, int *error);
static int get_disk_online(dm_descriptor_t disk, int *error);
static void remove_slice_from_list(dmgt_slice_t **slices, int index);
static dmgt_slice_t **get_disk_slices(dm_descriptor_t media,
const char *name, uint32_t blocksize, int *error);
static dmgt_slice_t **get_disk_usable_slices(dm_descriptor_t media,
const char *name, uint32_t blocksize, int *in_use, int *error);
static void get_disk_size(dm_descriptor_t media, char *name,
uint64_t *size, uint32_t *blocksize, int *error);
static void get_slice_use(dm_descriptor_t slice, char *name,
char **used_name, char **used_by, int *error);
static dmgt_slice_t *get_slice(
dm_descriptor_t slice, uint32_t blocksize, int *error);
static void handle_error(const char *format, ...);
static int slice_in_use(dmgt_slice_t *slice, int *error);
static int slice_too_small(dmgt_slice_t *slice);
static void (*error_func)(const char *, va_list);
static char *
get_device_name(dm_descriptor_t device, int *error)
{
char *dup = NULL;
char *name;
*error = 0;
name = dm_get_name(device, error);
if (*error) {
handle_error("could not determine name of device");
} else {
dup = strdup(name);
if (dup == NULL) {
handle_error("out of memory");
*error = -1;
}
dm_free_name(name);
}
return (dup);
}
static dmgt_disk_t *
get_disk(dm_descriptor_t disk, int *error)
{
dmgt_disk_t *dp;
*error = 0;
dp = (dmgt_disk_t *)calloc(1, sizeof (dmgt_disk_t));
if (dp == NULL) {
handle_error("out of memory");
*error = -1;
} else {
dp->name = get_device_name(disk, error);
if (!*error) {
dp->aliases = get_disk_aliases(disk, dp->name, error);
if (!*error) {
dm_descriptor_t *media =
dm_get_associated_descriptors(disk,
DM_MEDIA, error);
if (*error != 0 || media == NULL ||
*media == 0) {
handle_error(
"could not get media from disk %s",
dp->name);
*error = -1;
} else {
get_disk_size(media[0], dp->name,
&(dp->size), &(dp->blocksize),
error);
if (!*error) {
dp->slices =
get_disk_usable_slices(
media[0], dp->name,
dp->blocksize,
&(dp->in_use), error);
}
dm_free_descriptors(media);
}
}
}
}
if (*error) {
*error = -1;
if (dp != NULL) {
dmgt_free_disk(dp);
dp = NULL;
}
}
return (dp);
}
static char **
get_disk_aliases(dm_descriptor_t disk, char *name, int *error)
{
char **names = NULL;
dm_descriptor_t *aliases;
*error = 0;
aliases = dm_get_associated_descriptors(disk, DM_ALIAS, error);
if (*error || aliases == NULL) {
*error = -1;
handle_error("could not get aliases for disk %s", name);
} else {
int j;
for (j = 0; aliases[j] != 0; j++)
;
names = (char **)calloc(j + 1, sizeof (char *));
if (names == NULL) {
*error = -1;
handle_error("out of memory");
} else {
for (j = 0; *error == 0 && aliases[j] != 0; j++) {
dm_descriptor_t alias = aliases[j];
char *aname = dm_get_name(alias, error);
if (*error) {
handle_error("could not get alias %d "
"for disk %s", (j + 1), name);
} else {
names[j] = strdup(aname);
if (names[j] == NULL) {
*error = -1;
handle_error("out of memory");
}
dm_free_name(aname);
}
}
}
dm_free_descriptors(aliases);
}
if (*error && names != NULL) {
zjni_free_array((void **)names, free);
}
return (names);
}
static int
get_disk_online(dm_descriptor_t disk, int *error)
{
uint32_t status = 0;
nvlist_t *attrs;
*error = 0;
attrs = dm_get_attributes(disk, error);
if (*error) {
handle_error("could not get disk attributes for disk");
} else {
nvpair_t *match = zjni_nvlist_walk_nvpair(
attrs, DM_STATUS, DATA_TYPE_UINT32, NULL);
if (match == NULL || nvpair_value_uint32(match, &status)) {
handle_error("could not get status of disk");
*error = 1;
}
nvlist_free(attrs);
}
return (status != 0);
}
static dmgt_slice_t **
get_disk_slices(dm_descriptor_t media, const char *name, uint32_t blocksize,
int *error)
{
dm_descriptor_t *slices;
dmgt_slice_t **sap = NULL;
*error = 0;
slices = dm_get_associated_descriptors(media, DM_SLICE, error);
if (*error != 0) {
handle_error("could not get slices of disk %s", name);
} else {
int j;
int nslices = 0;
for (j = 0; *error == 0 &&
slices != NULL && slices[j] != 0; j++) {
dmgt_slice_t *slice =
get_slice(slices[j], blocksize, error);
if (!*error) {
dmgt_slice_t **mem =
(dmgt_slice_t **)realloc(sap,
(nslices + 2) * sizeof (dmgt_slice_t *));
if (mem == NULL) {
handle_error("out of memory");
*error = -1;
} else {
sap = mem;
sap[nslices] = slice;
sap[nslices + 1] = NULL;
nslices++;
}
}
}
dm_free_descriptors(slices);
}
if (*error) {
*error = -1;
if (sap != NULL) {
zjni_free_array((void **)sap,
(zjni_free_f)dmgt_free_slice);
sap = NULL;
}
}
return (sap);
}
static void
remove_slice_from_list(dmgt_slice_t **slices, int index)
{
int i;
for (i = index; slices[i] != NULL; i++) {
slices[i] = slices[i + 1];
}
}
static int
slices_overlap(dmgt_slice_t *slice1, dmgt_slice_t *slice2)
{
uint64_t start1 = slice1->start;
uint64_t end1 = start1 + slice1->size - 1;
uint64_t start2 = slice2->start;
uint64_t end2 = start2 + slice2->size - 1;
int overlap = (start2 <= end1 && start1 <= end2);
#ifdef DEBUG
if (overlap) {
(void) fprintf(stderr, "can't use %s: overlaps with %s\n",
slice2->name, slice1->name);
(void) fprintf(stderr, " 1: start: %llu - %llu\n",
(unsigned long long)start1, (unsigned long long)end1);
(void) fprintf(stderr, " 2: start: %llu - %llu\n",
(unsigned long long)start2, (unsigned long long)end2);
}
#endif
return (overlap);
}
static dmgt_slice_t **
get_disk_usable_slices(dm_descriptor_t media, const char *name,
uint32_t blocksize, int *in_use, int *error)
{
dmgt_slice_t **slices = get_disk_slices(media, name, blocksize, error);
if (*error) {
slices = NULL;
}
*in_use = 0;
if (slices != NULL) {
int i, nslices;
for (nslices = 0; slices[nslices] != NULL; nslices++)
;
for (i = nslices - 1; i >= 0; i--) {
dmgt_slice_t *slice = slices[i];
int s_in_use;
if (slice == NULL) {
continue;
}
s_in_use = slice_in_use(slice, error);
if (*error) {
break;
}
if (s_in_use) {
int j;
remove_slice_from_list(slices, i);
*in_use = 1;
for (j = nslices - 1; j >= 0; j--) {
dmgt_slice_t *slice2 = slices[j];
if (slice2 != NULL &&
slices_overlap(slice, slice2)) {
remove_slice_from_list(slices,
j);
dmgt_free_slice(slice2);
}
}
dmgt_free_slice(slice);
} else if (slice_too_small(slice)) {
remove_slice_from_list(slices, i);
dmgt_free_slice(slice);
}
}
}
if (*error) {
*error = -1;
if (slices != NULL) {
zjni_free_array((void **)slices,
(zjni_free_f)dmgt_free_slice);
slices = NULL;
}
}
return (slices);
}
static void
get_disk_size(dm_descriptor_t media, char *name, uint64_t *size,
uint32_t *blocksize, int *error)
{
nvlist_t *attrs;
*size = 0;
*error = 0;
attrs = dm_get_attributes(media, error);
if (*error) {
handle_error("could not get media attributes from disk: %s",
name);
} else {
nvpair_t *match = zjni_nvlist_walk_nvpair(
attrs, DM_NACCESSIBLE, DATA_TYPE_UINT64, NULL);
if (match == NULL || nvpair_value_uint64(match, size)) {
match = zjni_nvlist_walk_nvpair(
attrs, DM_SIZE, DATA_TYPE_UINT64, NULL);
if (match == NULL || nvpair_value_uint64(match, size)) {
handle_error("could not get size of disk: %s",
name);
*error = 1;
}
}
if (*error == 0) {
match = zjni_nvlist_walk_nvpair(
attrs, DM_BLOCKSIZE, DATA_TYPE_UINT32, NULL);
if (match == NULL ||
nvpair_value_uint32(match, blocksize)) {
handle_error("could not get "
"block size of disk: %s", name);
*error = 1;
} else {
*size *= *blocksize;
}
}
nvlist_free(attrs);
}
}
static void
get_slice_use(dm_descriptor_t slice, char *name, char **used_name,
char **used_by, int *error)
{
nvlist_t *stats = dm_get_stats(slice, DM_SLICE_STAT_USE, error);
if (*error != 0) {
handle_error("could not get stats of slice %s", name);
} else {
*used_name = NULL;
*used_by = NULL;
if (stats != NULL) {
char *tmp;
nvpair_t *match;
match = zjni_nvlist_walk_nvpair(
stats, DM_USED_BY, DATA_TYPE_STRING, NULL);
if (match != NULL &&
nvpair_value_string(match, &tmp) == 0) {
*used_name = strdup(tmp);
if (*used_name == NULL) {
*error = -1;
handle_error("out of memory");
} else {
match =
zjni_nvlist_walk_nvpair(stats,
DM_USED_NAME, DATA_TYPE_STRING,
NULL);
if (match != NULL &&
nvpair_value_string(match, &tmp) ==
0) {
*used_by = strdup(tmp);
if (*used_by == NULL) {
*error = -1;
handle_error(
"out of memory");
}
}
}
}
nvlist_free(stats);
}
}
}
static dmgt_slice_t *
get_slice(dm_descriptor_t slice, uint32_t blocksize, int *error)
{
dmgt_slice_t *sp;
*error = 0;
sp = (dmgt_slice_t *)calloc(1, sizeof (dmgt_slice_t));
if (sp == NULL) {
*error = -1;
handle_error("out of memory");
} else {
sp->name = get_device_name(slice, error);
if (!*error) {
nvlist_t *attrs = dm_get_attributes(slice, error);
if (*error) {
handle_error("could not get "
"attributes from slice: %s", sp->name);
} else {
nvpair_t *match = zjni_nvlist_walk_nvpair(
attrs, DM_SIZE, DATA_TYPE_UINT64, NULL);
uint64_t size_blocks;
sp->size = 0;
if (match == NULL ||
nvpair_value_uint64(match, &size_blocks)) {
handle_error("could not get "
"size of slice: %s", sp->name);
*error = 1;
} else {
uint64_t start_blocks;
sp->size = blocksize * size_blocks;
match = zjni_nvlist_walk_nvpair(
attrs, DM_START, DATA_TYPE_UINT64,
NULL);
if (match == NULL ||
nvpair_value_uint64(match,
&start_blocks)) {
handle_error(
"could not get "
"start block of slice: %s",
sp->name);
*error = 1;
} else {
sp->start =
blocksize * start_blocks;
get_slice_use(slice, sp->name,
&(sp->used_name),
&(sp->used_by), error);
}
}
}
}
}
if (*error && sp != NULL) {
dmgt_free_slice(sp);
}
return (sp);
}
static void
handle_error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
if (error_func != NULL) {
error_func(format, ap);
}
va_end(ap);
}
static int
slice_too_small(dmgt_slice_t *slice)
{
if (slice->size < SPA_MINDEVSIZE) {
#ifdef DEBUG
(void) fprintf(stderr, "can't use %s: slice too small: %llu\n",
slice->name, (unsigned long long)slice->size);
#endif
return (1);
}
return (0);
}
static int
slice_in_use(dmgt_slice_t *slice, int *error)
{
char *msg = NULL;
int in_use;
in_use = dm_inuse(slice->name, &msg, DM_WHO_ZPOOL_FORCE, error);
if (*error) {
handle_error("%s: could not determine usage", slice->name);
}
#ifdef DEBUG
if (in_use) {
(void) fprintf(stderr,
"can't use %s: used name: %s: used by: %s\n message: %s\n",
slice->name, slice->used_name, slice->used_by, msg);
}
#endif
if (msg != NULL) {
free(msg);
}
return (in_use);
}
int
dmgt_avail_disk_iter(dmgt_disk_iter_f func, void *data)
{
int error = 0;
int filter[] = { DM_DT_FIXED, -1 };
dm_descriptor_t *disks = dm_get_descriptors(DM_DRIVE, filter, &error);
if (error) {
handle_error("unable to communicate with libdiskmgt");
} else {
int i;
for (i = 0; disks != NULL && disks[i] != 0; i++) {
dm_descriptor_t disk = (dm_descriptor_t)disks[i];
int online;
error = 0;
online = get_disk_online(disk, &error);
if (!error && online) {
dmgt_disk_t *dp = get_disk(disk, &error);
if (!error) {
if (!dp->in_use ||
zjni_count_elements(
(void **)dp->slices) != 0) {
if (func(dp, data)) {
error = -1;
}
dmgt_free_disk(dp);
#ifdef DEBUG
} else {
(void) fprintf(stderr, "disk "
"has no available slices: "
"%s\n", dp->name);
#endif
}
}
}
}
dm_free_descriptors(disks);
}
return (error);
}
void
dmgt_free_disk(dmgt_disk_t *disk)
{
if (disk != NULL) {
free(disk->name);
zjni_free_array((void **)disk->aliases, free);
zjni_free_array((void **)disk->slices,
(zjni_free_f)dmgt_free_slice);
free(disk);
}
}
void
dmgt_free_slice(dmgt_slice_t *slice)
{
if (slice != NULL) {
free(slice->name);
free(slice->used_name);
free(slice->used_by);
free(slice);
}
}
void
dmgt_set_error_handler(void (*func)(const char *, va_list))
{
error_func = func;
}