/*
 * Copyright (C) 2025 Genexis Sweden AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 */

#include <libbbfdm-api/bbfdm_api.h>
#include <libbbfdm-api/dmcommon.h>
#include <dirent.h>
#include <sys/statvfs.h>
#include <syslog.h>

typedef struct mount_entry {
	struct list_head list;
	char device[256];
	char mount_point[256];
	char fs_type[64];
} mount_entry;

typedef struct partition_entry {
	struct list_head list;
	char device[256];      /* e.g., /dev/mmcblk0p4 */
	char mount_point[256]; /* "" if not mounted */
	char fs_type[64];      /* "" if not mounted */
	int partition_number;
	bool is_mounted;
	/* Space stats cached at enumerate time; 0 if not mounted */
	unsigned long long capacity;
	unsigned long long used;
	unsigned long long available;
} partition_entry;

typedef struct physical_disk_entry {
	struct list_head list;
	struct list_head partitions;
	char device[256]; /* e.g., /dev/mmcblk0 */
	char model[256];
	unsigned long long size; /* bytes */
	int partition_count;
} physical_disk_entry;

static LIST_HEAD(storage_list);
static LIST_HEAD(mount_table);

/*************************************************************
 * SYSFS HELPERS
 *************************************************************/
static unsigned long long read_sysfs_ull(const char *path)
{
	FILE *fp;
	unsigned long long val = 0;

	fp = fopen(path, "r");
	if (fp) {
		fscanf(fp, "%llu", &val);
		fclose(fp);
	}
	return val;
}

static void read_sysfs_str(const char *path, char *buf, size_t len)
{
	FILE *fp;
	char *p;

	buf[0] = '\0';
	fp = fopen(path, "r");
	if (!fp)
		return;
	if (fgets(buf, len, fp)) {
		p = buf + strlen(buf) - 1;
		while (p >= buf && (*p == '\n' || *p == '\r' || *p == ' '))
			*p-- = '\0';
	}
	fclose(fp);
}

static void get_disk_model(const char *disk_name, char *model, size_t model_size)
{
	char path[256];

	snprintf(path, sizeof(path), "/sys/block/%s/device/model", disk_name);
	read_sysfs_str(path, model, model_size);
	if (model[0])
		return;

	snprintf(path, sizeof(path), "/sys/block/%s/device/name", disk_name);
	read_sysfs_str(path, model, model_size);
	if (model[0])
		return;

	snprintf(model, model_size, "Unknown");
}

/*************************************************************
 * STATVFS HELPER
 *************************************************************/
static int get_disk_stats(const char *mount_point, unsigned long long *capacity,
			  unsigned long long *used, unsigned long long *available)
{
	struct statvfs s;
	unsigned long long blksize;

	if (statvfs(mount_point, &s) != 0)
		return -1;

	blksize    = s.f_frsize ? s.f_frsize : s.f_bsize;
	*capacity  = s.f_blocks * blksize;
	*used      = (s.f_blocks - s.f_bfree) * blksize;
	*available = s.f_bavail * blksize;
	return 0;
}

/*************************************************************
 * MOUNT TABLE
 *************************************************************/
static void free_mount_table(void)
{
	mount_entry *e, *tmp;

	list_for_each_entry_safe(e, tmp, &mount_table, list) {
		list_del(&e->list);
		BBFDM_FREE(e);
	}
}

static void load_mount_table(void)
{
	FILE *fp;
	char line[512];

	free_mount_table();

	fp = fopen("/proc/mounts", "r");
	if (!fp)
		return;

	while (fgets(line, sizeof(line), fp)) {
		char device[256] = {0}, mount_point[256] = {0}, fs_type[64] = {0};
		mount_entry *e;

		if (sscanf(line, "%255s %255s %63s", device, mount_point, fs_type) != 3)
			continue;
		/* Accept block devices under /dev/ and UBI volumes (ubiN:volname) */
		if (strncmp(device, "/dev/", 5) != 0 &&
		    !(strncmp(device, "ubi", 3) == 0 && strchr(device, ':') != NULL))
			continue;

		e = calloc(1, sizeof(mount_entry));
		if (!e) {
			syslog(LOG_ERR, "Storage: Failed to allocate memory for mount entry");
			continue;
		}

		strncpy(e->device, device, sizeof(e->device) - 1);
		strncpy(e->mount_point, mount_point, sizeof(e->mount_point) - 1);
		strncpy(e->fs_type, fs_type, sizeof(e->fs_type) - 1);
		list_add_tail(&e->list, &mount_table);
	}
	fclose(fp);
}

static mount_entry *find_mount(const char *device)
{
	mount_entry *e;

	list_for_each_entry(e, &mount_table, list) {
		if (strcmp(e->device, device) == 0)
			return e;
	}
	return NULL;
}

/*************************************************************
 * STORAGE LIST MANAGEMENT
 *************************************************************/
static void free_storage_list(void)
{
	physical_disk_entry *disk, *disk_tmp;
	partition_entry *part, *part_tmp;

	list_for_each_entry_safe(disk, disk_tmp, &storage_list, list) {
		list_for_each_entry_safe(part, part_tmp, &disk->partitions, list) {
			list_del(&part->list);
			BBFDM_FREE(part);
		}
		list_del(&disk->list);
		BBFDM_FREE(disk);
	}
}

static bool is_physical_disk(const char *name)
{
	const char *p;

	if (strncmp(name, "mmcblk", 6) == 0) {
		/* Accept only mmcblkN — reject mmcblkNboot0, mmcblkNrpmb, etc. */
		p = name + 6;
		while (*p && isdigit_str(p))
			p++;
		return *p == '\0';
	}

	return strncmp(name, "sd", 2) == 0 ||
	       strncmp(name, "hd", 2) == 0 ||
	       strncmp(name, "vd", 2) == 0 ||
	       strncmp(name, "nvme", 4) == 0;
}

static void enumerate_ubi_devices(void)
{
	DIR *ubi_dir;
	struct dirent *de;
	struct dirent **entries = NULL;
	int n_entries = 0, i;
	char path[256];

	ubi_dir = opendir("/sys/class/ubi");
	if (!ubi_dir)
		return; /* No UBI support on this system */

	/* Collect all directory entries first */
	while ((de = readdir(ubi_dir)) != NULL) {
		struct dirent **tmp = realloc(entries, (n_entries + 1) * sizeof(struct dirent *));
		if (!tmp) {
			syslog(LOG_ERR, "Storage: Failed to allocate memory for UBI entries");
			goto cleanup;
		}
		entries = tmp;
		entries[n_entries] = malloc(sizeof(struct dirent));
		if (!entries[n_entries]) {
			syslog(LOG_ERR, "Storage: Failed to allocate memory for UBI entry");
			goto cleanup;
		}
		memcpy(entries[n_entries], de, sizeof(struct dirent));
		n_entries++;
	}
	closedir(ubi_dir);

	/* Process UBI devices */
	for (i = 0; i < n_entries; i++) {
		const char *ubi_name = entries[i]->d_name;
		const char *p;
		physical_disk_entry *disk;
		unsigned long long total_lebs, leb_size;
		char vol_prefix[32];
		int j;

		/* Match ubiN only (not ubiN_M volume entries) */
		if (strncmp(ubi_name, "ubi", 3) != 0)
			continue;
		p = ubi_name + 3;
		if (!*p || !isdigit((unsigned char)*p))
			continue;
		while (*p && isdigit((unsigned char)*p))
			p++;
		if (*p != '\0')
			continue;

		disk = calloc(1, sizeof(physical_disk_entry));
		if (!disk) {
			syslog(LOG_ERR, "Storage: Failed to allocate memory for UBI disk %s", ubi_name);
			continue;
		}

		INIT_LIST_HEAD(&disk->partitions);
		snprintf(disk->device, sizeof(disk->device), "/dev/%s", ubi_name);
		snprintf(disk->model, sizeof(disk->model), "NAND Flash (UBI)");

		snprintf(path, sizeof(path), "/sys/class/ubi/%s/total_eraseblocks", ubi_name);
		total_lebs = read_sysfs_ull(path);
		snprintf(path, sizeof(path), "/sys/class/ubi/%s/eraseblock_size", ubi_name);
		leb_size = read_sysfs_ull(path);
		disk->size = total_lebs * leb_size;

		/* Enumerate volumes: ubiN_M entries */
		snprintf(vol_prefix, sizeof(vol_prefix), "%s_", ubi_name);
		for (j = 0; j < n_entries; j++) {
			const char *vol_entry = entries[j]->d_name;
			char vol_name[128] = {0};
			char ubi_dev[256];
			partition_entry *part;
			mount_entry *mnt;
			unsigned long long usable_leb, reserved_ebs;

			if (strncmp(vol_entry, vol_prefix, strlen(vol_prefix)) != 0)
				continue;

			part = calloc(1, sizeof(partition_entry));
			if (!part) {
				syslog(LOG_ERR, "Storage: Failed to allocate memory for UBI volume %s", vol_entry);
				continue;
			}

			/* Volume ID from the suffix after "ubiN_" */
			part->partition_number = atoi(vol_entry + strlen(vol_prefix));

			/* Volume name from sysfs (e.g., "boarddata") */
			snprintf(path, sizeof(path), "/sys/class/ubi/%s/name", vol_entry);
			read_sysfs_str(path, vol_name, sizeof(vol_name));

			/* Ensure volume name is valid to prevent buffer issues */
			if (vol_name[0] == '\0') {
				syslog(LOG_WARNING, "Storage: Empty volume name for %s", vol_entry);
				BBFDM_FREE(part);
				continue;
			}

			/* Device name as "ubiN:volname" — matches /proc/mounts */
			snprintf(ubi_dev, sizeof(ubi_dev), "%s:%s", ubi_name, vol_name);
			snprintf(part->device, sizeof(part->device), "%s", ubi_dev);

			/* Capacity from sysfs */
			snprintf(path, sizeof(path), "/sys/class/ubi/%s/usable_leb_size", vol_entry);
			usable_leb = read_sysfs_ull(path);
			snprintf(path, sizeof(path), "/sys/class/ubi/%s/reserved_ebs", vol_entry);
			reserved_ebs = read_sysfs_ull(path);
			part->capacity = usable_leb * reserved_ebs;

			mnt = find_mount(ubi_dev);
			if (mnt) {
				part->is_mounted = true;
				strncpy(part->mount_point, mnt->mount_point,
					sizeof(part->mount_point) - 1);
				strncpy(part->fs_type, mnt->fs_type,
					sizeof(part->fs_type) - 1);
				/* Override with live statvfs where available */
				get_disk_stats(part->mount_point, &part->capacity,
					       &part->used, &part->available);
			} else {
				/* For static volumes, data_bytes reflects actual content */
				snprintf(path, sizeof(path), "/sys/class/ubi/%s/data_bytes", vol_entry);
				part->used = read_sysfs_ull(path);
			}

			list_add_tail(&part->list, &disk->partitions);
			disk->partition_count++;
		}

		list_add_tail(&disk->list, &storage_list);
		syslog(LOG_DEBUG, "Storage: ubi=%s size=%llu volumes=%d",
		       disk->device, disk->size, disk->partition_count);
	}

cleanup:
	for (i = 0; i < n_entries; i++)
		free(entries[i]);
	free(entries);
}

static void enumerate_storage(void)
{
	DIR *block_dir, *disk_dir;
	struct dirent *disk_de, *part_de;
	char path[256];

	free_storage_list();
	load_mount_table();

	block_dir = opendir("/sys/block");
	if (!block_dir) {
		syslog(LOG_ERR, "Storage: Failed to open /sys/block");
		return;
	}

	while ((disk_de = readdir(block_dir)) != NULL) {
		const char *disk_name = disk_de->d_name;
		physical_disk_entry *disk;

		if (!is_physical_disk(disk_name))
			continue;

		disk = calloc(1, sizeof(physical_disk_entry));
		if (!disk) {
			syslog(LOG_ERR, "Storage: Failed to allocate memory for disk %s", disk_name);
			continue;
		}

		INIT_LIST_HEAD(&disk->partitions);
		snprintf(disk->device, sizeof(disk->device), "/dev/%s", disk_name);

		snprintf(path, sizeof(path), "/sys/block/%s/size", disk_name);
		disk->size = read_sysfs_ull(path) * 512;

		get_disk_model(disk_name, disk->model, sizeof(disk->model));

		/* Walk /sys/block/<disk>/ for partitions (identified by 'partition' file) */
		snprintf(path, sizeof(path), "/sys/block/%s", disk_name);
		disk_dir = opendir(path);
		if (disk_dir) {
			while ((part_de = readdir(disk_dir)) != NULL) {
				const char *part_name = part_de->d_name;
				char part_sysfs[256];
				partition_entry *part;
				mount_entry *mnt;

				/* Partitions start with the disk name */
				if (strncmp(part_name, disk_name, strlen(disk_name)) != 0)
					continue;

				/* Confirm it is a partition via the sysfs 'partition' file */
				snprintf(part_sysfs, sizeof(part_sysfs),
					 "/sys/block/%s/%s/partition", disk_name, part_name);
				if (!bbfdm_file_exists(part_sysfs))
					continue;

				part = calloc(1, sizeof(partition_entry));
				if (!part) {
					syslog(LOG_ERR, "Storage: Failed to allocate memory for partition %s", part_name);
					continue;
				}

				snprintf(part->device, sizeof(part->device), "/dev/%s", part_name);
				part->partition_number = (int)read_sysfs_ull(part_sysfs);

				mnt = find_mount(part->device);
				if (mnt) {
					part->is_mounted = true;
					strncpy(part->mount_point, mnt->mount_point,
						sizeof(part->mount_point) - 1);
					strncpy(part->fs_type, mnt->fs_type,
						sizeof(part->fs_type) - 1);
					get_disk_stats(part->mount_point, &part->capacity,
						       &part->used, &part->available);
				} else {
					snprintf(part_sysfs, sizeof(part_sysfs),
						 "/sys/block/%s/%s/size", disk_name, part_name);
					part->capacity = read_sysfs_ull(part_sysfs) * 512;
				}

				list_add_tail(&part->list, &disk->partitions);
				disk->partition_count++;
			}
			closedir(disk_dir);
		}

		list_add_tail(&disk->list, &storage_list);
		syslog(LOG_DEBUG, "Storage: disk=%s model=%s size=%llu partitions=%d",
		       disk->device, disk->model, disk->size, disk->partition_count);
	}

	closedir(block_dir);

	enumerate_ubi_devices();

	free_mount_table();
}

/*************************************************************
 * GET PARAMS - Storage.{i}.
 *************************************************************/
static int get_storage_name(char *refparam, struct dmctx *ctx, void *data,
			    char *instance, char **value)
{
	physical_disk_entry *e = (physical_disk_entry *)((struct dm_data *)data)->additional_data;
	*value = e ? dmstrdup(e->device) : dmstrdup("");
	return 0;
}

static int get_storage_model(char *refparam, struct dmctx *ctx, void *data,
			     char *instance, char **value)
{
	physical_disk_entry *e = (physical_disk_entry *)((struct dm_data *)data)->additional_data;
	*value = e ? dmstrdup(e->model) : dmstrdup("Unknown");
	return 0;
}

static int get_storage_capacity(char *refparam, struct dmctx *ctx, void *data,
				char *instance, char **value)
{
	physical_disk_entry *e = (physical_disk_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%llu", e ? e->size : 0ULL);
	return 0;
}

static int get_storage_status(char *refparam, struct dmctx *ctx, void *data,
			      char *instance, char **value)
{
	physical_disk_entry *e = (physical_disk_entry *)((struct dm_data *)data)->additional_data;
	*value = (e && e->size > 0) ? dmstrdup("Online") : dmstrdup("Offline");
	return 0;
}

static int get_storage_partition_number_of_entries(char *refparam, struct dmctx *ctx,
						   void *data, char *instance, char **value)
{
	physical_disk_entry *e = (physical_disk_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%d", e ? e->partition_count : 0);
	return 0;
}

/*************************************************************
 * GET PARAMS - Storage.{i}.Partition.{i}.
 *************************************************************/
static int get_partition_name(char *refparam, struct dmctx *ctx, void *data,
			      char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	*value = e ? dmstrdup(e->device) : dmstrdup("");
	return 0;
}

static int get_partition_number(char *refparam, struct dmctx *ctx, void *data,
				char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%d", e ? e->partition_number : 0);
	return 0;
}

static int get_partition_filesystem(char *refparam, struct dmctx *ctx, void *data,
				    char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	*value = e ? dmstrdup(e->fs_type) : dmstrdup("");
	return 0;
}

static int get_partition_mount_point(char *refparam, struct dmctx *ctx, void *data,
				     char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	*value = e ? dmstrdup(e->mount_point) : dmstrdup("");
	return 0;
}

static int get_partition_status(char *refparam, struct dmctx *ctx, void *data,
				char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	*value = (e && e->is_mounted) ? dmstrdup("Mounted") : dmstrdup("Unmounted");
	return 0;
}

static int get_partition_capacity(char *refparam, struct dmctx *ctx, void *data,
				  char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%llu", e ? e->capacity : 0ULL);
	return 0;
}

static int get_partition_used_space(char *refparam, struct dmctx *ctx, void *data,
				    char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%llu", e ? e->used : 0ULL);
	return 0;
}

static int get_partition_available_space(char *refparam, struct dmctx *ctx, void *data,
					 char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	dmasprintf(value, "%llu", e ? e->available : 0ULL);
	return 0;
}

static int get_partition_used_percentage(char *refparam, struct dmctx *ctx, void *data,
					 char *instance, char **value)
{
	partition_entry *e = (partition_entry *)((struct dm_data *)data)->additional_data;
	unsigned int pct = 0;

	if (e && e->capacity > 0)
		pct = (unsigned int)((e->used * 100) / e->capacity);
	dmasprintf(value, "%u", pct);
	return 0;
}

/*************************************************************
 * NUMBER OF ENTRIES
 *************************************************************/
static int get_storage_number_of_entries(char *refparam, struct dmctx *ctx, void *data,
					 char *instance, char **value)
{
	physical_disk_entry *e;
	int count = 0;

	enumerate_storage();
	list_for_each_entry(e, &storage_list, list)
		count++;
	dmasprintf(value, "%d", count);
	return 0;
}

/*************************************************************
 * BROWSE
 *************************************************************/
static int browseStorageInst(struct dmctx *dmctx, DMNODE *parent_node,
			     void *prev_data, char *prev_instance)
{
	physical_disk_entry *e;
	struct dm_data curr_data = {0};
	char *inst;
	int id = 0;

	enumerate_storage();

	list_for_each_entry(e, &storage_list, list) {
		curr_data.additional_data = e;
		inst = handle_instance_without_section(dmctx, parent_node, ++id);
		if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
			break;
	}
	return 0;
}

static int browsePartitionInst(struct dmctx *dmctx, DMNODE *parent_node,
			       void *prev_data, char *prev_instance)
{
	physical_disk_entry *disk = (physical_disk_entry *)((struct dm_data *)prev_data)->additional_data;
	partition_entry *e;
	struct dm_data curr_data = {0};
	char *inst;
	int id = 0;

	if (!disk)
		return 0;

	list_for_each_entry(e, &disk->partitions, list) {
		curr_data.additional_data = e;
		inst = handle_instance_without_section(dmctx, parent_node, ++id);
		if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
			break;
	}
	return 0;
}

/*************************************************************
 * INIT / CLEAN
 *************************************************************/
void sysmngr_disk_init(void)
{
	enumerate_storage();
}

void sysmngr_disk_clean(void)
{
	free_storage_list();
}

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/

/* *** Device.DeviceInfo.X_*_Storage.{i}.Partition.{i}. *** */
DMLEAF tDeviceInfoStoragePartitionParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Name",            &DMREAD, DMT_STRING, get_partition_name,            NULL, BBFDM_BOTH},
{"PartitionNumber", &DMREAD, DMT_UNINT,  get_partition_number,          NULL, BBFDM_BOTH},
{"FileSystem",      &DMREAD, DMT_STRING, get_partition_filesystem,      NULL, BBFDM_BOTH},
{"MountPoint",      &DMREAD, DMT_STRING, get_partition_mount_point,     NULL, BBFDM_BOTH},
{"Status",          &DMREAD, DMT_STRING, get_partition_status,          NULL, BBFDM_BOTH},
{"Capacity",        &DMREAD, DMT_UNLONG, get_partition_capacity,        NULL, BBFDM_BOTH},
{"UsedSpace",       &DMREAD, DMT_UNLONG, get_partition_used_space,      NULL, BBFDM_BOTH},
{"AvailableSpace",  &DMREAD, DMT_UNLONG, get_partition_available_space, NULL, BBFDM_BOTH},
{"UsedPercentage",  &DMREAD, DMT_UNINT,  get_partition_used_percentage, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DeviceInfo.X_*_Storage.{i}. *** */
DMOBJ tDeviceInfoStorageInstanceObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{"Partition", &DMREAD, NULL, NULL, NULL, browsePartitionInst, NULL, NULL, NULL, tDeviceInfoStoragePartitionParams, NULL, BBFDM_BOTH},
{0}
};

DMLEAF tDeviceInfoStorageInstanceParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Name",                     &DMREAD, DMT_STRING, get_storage_name,                        NULL, BBFDM_BOTH},
{"Model",                    &DMREAD, DMT_STRING, get_storage_model,                       NULL, BBFDM_BOTH},
{"Capacity",                 &DMREAD, DMT_UNLONG, get_storage_capacity,                    NULL, BBFDM_BOTH},
{"Status",                   &DMREAD, DMT_STRING, get_storage_status,                      NULL, BBFDM_BOTH},
{"PartitionNumberOfEntries", &DMREAD, DMT_UNINT,  get_storage_partition_number_of_entries, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DeviceInfo.X_*_Storage. *** */
DMOBJ tDeviceInfoStorageObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{"Storage", &DMREAD, NULL, NULL, NULL, browseStorageInst, NULL, NULL, tDeviceInfoStorageInstanceObj, tDeviceInfoStorageInstanceParams, NULL, BBFDM_BOTH},
{0}
};

DMLEAF tDeviceInfoStorageParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"StorageNumberOfEntries", &DMREAD, DMT_UNINT, get_storage_number_of_entries, NULL, BBFDM_BOTH},
{0}
};

DMOBJ tDeviceStorageObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{BBF_VENDOR_PREFIX"Storage", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tDeviceInfoStorageObj, tDeviceInfoStorageParams, NULL, BBFDM_BOTH},
{0}
};

DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.", tDeviceStorageObj, NULL},
{0}
};
