/*
 * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
 *
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>

#include "superblocks.h"

/* Common gfs/gfs2 constants: */
#define GFS_LOCKNAME_LEN        64

/* gfs1 constants: */
#define GFS_FORMAT_FS           1309
#define GFS_FORMAT_MULTI        1401

struct gfs2_meta_header {
	uint32_t mh_magic;
	uint32_t mh_type;
	uint64_t __pad0;          /* Was generation number in gfs1 */
	uint32_t mh_format;
	uint32_t __pad1;          /* Was incarnation number in gfs1 */
};

struct gfs2_inum {
	uint64_t no_formal_ino;
	uint64_t no_addr;
};

struct gfs2_sb {
	struct gfs2_meta_header sb_header;

	uint32_t sb_fs_format;
	uint32_t sb_multihost_format;
	uint32_t  __pad0;  /* Was superblock flags in gfs1 */

	uint32_t sb_bsize;
	uint32_t sb_bsize_shift;
	uint32_t __pad1;   /* Was journal segment size in gfs1 */

	struct gfs2_inum sb_master_dir; /* Was jindex dinode in gfs1 */
	struct gfs2_inum __pad2; /* Was rindex dinode in gfs1 */
	struct gfs2_inum sb_root_dir;

	char sb_lockproto[GFS_LOCKNAME_LEN];
	char sb_locktable[GFS_LOCKNAME_LEN];

	struct gfs2_inum __pad3; /* Was quota inode in gfs1 */
	struct gfs2_inum __pad4; /* Was license inode in gfs1 */
	uint8_t sb_uuid[16]; /* The UUID maybe 0 for backwards compat */
} __attribute__((packed));



static int probe_gfs(blkid_probe pr, const struct blkid_idmag *mag)
{
	const struct gfs2_sb *sbd;

	sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
	if (!sbd)
		return errno ? -errno : 1;

	if (be32_to_cpu(sbd->sb_fs_format) == GFS_FORMAT_FS &&
	    be32_to_cpu(sbd->sb_multihost_format) == GFS_FORMAT_MULTI)
	{
		if (*sbd->sb_locktable)
			blkid_probe_set_label(pr,
				(unsigned char *) sbd->sb_locktable,
				sizeof(sbd->sb_locktable));

		blkid_probe_set_uuid(pr, sbd->sb_uuid);
		return 0;
	}

	return 1;
}

static inline int gfs2_format_is_valid(uint32_t format)
{
	return (format >= 1800 && format < 1900);
}
static inline int gfs2_multiformat_is_valid(uint32_t multi)
{
	return (multi >= 1900 && multi < 2000);
}

static int probe_gfs2(blkid_probe pr, const struct blkid_idmag *mag)
{
	const struct gfs2_sb *sbd;

	sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
	if (!sbd)
		return errno ? -errno : 1;

	if (gfs2_format_is_valid(be32_to_cpu(sbd->sb_fs_format)) &&
	    gfs2_multiformat_is_valid(be32_to_cpu(sbd->sb_multihost_format)))
	{
		if (*sbd->sb_locktable)
			blkid_probe_set_label(pr,
				(unsigned char *) sbd->sb_locktable,
				sizeof(sbd->sb_locktable));
		blkid_probe_set_uuid(pr, sbd->sb_uuid);
		blkid_probe_set_version(pr, "1");
		blkid_probe_set_fsblocksize(pr, be32_to_cpu(sbd->sb_bsize));
		blkid_probe_set_block_size(pr, be32_to_cpu(sbd->sb_bsize));
		return 0;
	}
	return 1;
}

const struct blkid_idinfo gfs_idinfo =
{
	.name		= "gfs",
	.usage		= BLKID_USAGE_FILESYSTEM,
	.probefunc	= probe_gfs,
	.minsz		= 32 * 1024 * 1024,	/* minimal size of GFS journal */
	.magics		=
	{
		{ .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
		{ NULL }
	}
};

const struct blkid_idinfo gfs2_idinfo =
{
	.name		= "gfs2",
	.usage		= BLKID_USAGE_FILESYSTEM,
	.probefunc	= probe_gfs2,
	.minsz		= 32 * 1024 * 1024,	/* minimal size of GFS journal */
	.magics		=
	{
		{ .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
		{ NULL }
	}
};

