/* ufs.c - Unix File System */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2004,2005,2007,2008,2009  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/err.h>
#include <grub/file.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/types.h>

GRUB_MOD_LICENSE("GPLv3+");

#ifdef MODE_UFS2
#define GRUB_UFS_MAGIC		0x19540119
#else
#define GRUB_UFS_MAGIC		0x11954
#endif

#define GRUB_UFS_INODE		2
#define GRUB_UFS_FILETYPE_DIR	4
#define GRUB_UFS_FILETYPE_LNK	10
#define GRUB_UFS_MAX_SYMLNK_CNT	8

#define GRUB_UFS_DIRBLKS	12
#define GRUB_UFS_INDIRBLKS	3

#define GRUB_UFS_ATTR_TYPE      0160000
#define GRUB_UFS_ATTR_FILE	0100000
#define GRUB_UFS_ATTR_DIR	0040000
#define GRUB_UFS_ATTR_LNK       0120000

#define GRUB_UFS_VOLNAME_LEN	32

#ifdef MODE_BIGENDIAN
#define grub_ufs_to_cpu16 grub_be_to_cpu16
#define grub_ufs_to_cpu32 grub_be_to_cpu32
#define grub_ufs_to_cpu64 grub_be_to_cpu64
#define grub_cpu_to_ufs32_compile_time grub_cpu_to_be32_compile_time
#else
#define grub_ufs_to_cpu16 grub_le_to_cpu16
#define grub_ufs_to_cpu32 grub_le_to_cpu32
#define grub_ufs_to_cpu64 grub_le_to_cpu64
#define grub_cpu_to_ufs32_compile_time grub_cpu_to_le32_compile_time
#endif

#ifdef MODE_UFS2
typedef grub_uint64_t grub_ufs_blk_t;
static inline grub_disk_addr_t
grub_ufs_to_cpu_blk(grub_ufs_blk_t blk)
{
	return grub_ufs_to_cpu64(blk);
}
#else
typedef grub_uint32_t grub_ufs_blk_t;
static inline grub_disk_addr_t
grub_ufs_to_cpu_blk(grub_ufs_blk_t blk)
{
	return grub_ufs_to_cpu32(blk);
}
#endif

/* Calculate in which group the inode can be found.  */
#define UFS_BLKSZ(sblock) (grub_ufs_to_cpu32 (sblock->bsize))
#define UFS_LOG_BLKSZ(sblock) (data->log2_blksz)

#ifdef MODE_UFS2
#define INODE_ENDIAN(data,field,bits1,bits2) grub_ufs_to_cpu##bits2 (data->inode.field)
#else
#define INODE_ENDIAN(data,field,bits1,bits2) grub_ufs_to_cpu##bits1 (data->inode.field)
#endif

#define INODE_SIZE(data) grub_ufs_to_cpu64 (data->inode.size)
#define INODE_MODE(data) grub_ufs_to_cpu16 (data->inode.mode)
#ifdef MODE_UFS2
#define LOG_INODE_BLKSZ 3
#else
#define LOG_INODE_BLKSZ 2
#endif
#ifdef MODE_UFS2
#define UFS_INODE_PER_BLOCK 2
#else
#define UFS_INODE_PER_BLOCK 4
#endif
#define INODE_DIRBLOCKS(data,blk) INODE_ENDIAN \
                                   (data,blocks.dir_blocks[blk],32,64)
#define INODE_INDIRBLOCKS(data,blk) INODE_ENDIAN \
                                     (data,blocks.indir_blocks[blk],32,64)

/* The blocks on which the superblock can be found.  */
static int sblocklist[] = { 128, 16, 0, 512, -1 };

struct grub_ufs_sblock
{
	grub_uint8_t unused[16];
	/* The offset of the inodes in the cylinder group.  */
	grub_uint32_t inoblk_offs;

	grub_uint8_t unused2[4];

	/* The start of the cylinder group.  */
	grub_uint32_t cylg_offset;
	grub_uint32_t cylg_mask;

	grub_uint32_t mtime;
	grub_uint8_t unused4[12];

	/* The size of a block in bytes.  */
	grub_int32_t bsize;
	grub_uint8_t unused5[48];

	/* The size of filesystem blocks to disk blocks.  */
	grub_uint32_t log2_blksz;
	grub_uint8_t unused6[40];
	grub_uint32_t uuidhi;
	grub_uint32_t uuidlow;
	grub_uint8_t unused7[32];

	/* Inodes stored per cylinder group.  */
	grub_uint32_t ino_per_group;

	/* The frags per cylinder group.  */
	grub_uint32_t frags_per_group;

	grub_uint8_t unused8[488];

	/* Volume name for UFS2.  */
	grub_uint8_t volume_name[GRUB_UFS_VOLNAME_LEN];
	grub_uint8_t unused9[360];

	grub_uint64_t mtime2;
	grub_uint8_t unused10[292];

	/* Magic value to check if this is really a UFS filesystem.  */
	grub_uint32_t magic;
};

#ifdef MODE_UFS2
/* UFS inode.  */
GRUB_PACKED_START
struct grub_ufs_inode
{
	grub_uint16_t mode;
	grub_uint16_t nlinks;
	grub_uint32_t uid;
	grub_uint32_t gid;
	grub_uint32_t blocksize;
	grub_uint64_t size;
	grub_int64_t nblocks;
	grub_uint64_t atime;
	grub_uint64_t mtime;
	grub_uint64_t ctime;
	grub_uint64_t create_time;
	grub_uint32_t atime_usec;
	grub_uint32_t mtime_usec;
	grub_uint32_t ctime_usec;
	grub_uint32_t create_time_sec;
	grub_uint32_t gen;
	grub_uint32_t kernel_flags;
	grub_uint32_t flags;
	grub_uint32_t extsz;
	grub_uint64_t ext[2];
	union
	{
		struct
		{
			grub_uint64_t dir_blocks[GRUB_UFS_DIRBLKS];
			grub_uint64_t indir_blocks[GRUB_UFS_INDIRBLKS];
		} blocks;
		grub_uint8_t symlink[(GRUB_UFS_DIRBLKS + GRUB_UFS_INDIRBLKS) * 8];
	};

	grub_uint8_t unused[24];
};
GRUB_PACKED_END
#else
/* UFS inode.  */
GRUB_PACKED_START
struct grub_ufs_inode
{
	grub_uint16_t mode;
	grub_uint16_t nlinks;
	grub_uint16_t uid;
	grub_uint16_t gid;
	grub_uint64_t size;
	grub_uint32_t atime;
	grub_uint32_t atime_usec;
	grub_uint32_t mtime;
	grub_uint32_t mtime_usec;
	grub_uint32_t ctime;
	grub_uint32_t ctime_usec;
	union
	{
		struct
		{
			grub_uint32_t dir_blocks[GRUB_UFS_DIRBLKS];
			grub_uint32_t indir_blocks[GRUB_UFS_INDIRBLKS];
		} blocks;
		grub_uint8_t symlink[(GRUB_UFS_DIRBLKS + GRUB_UFS_INDIRBLKS) * 4];
	};
	grub_uint32_t flags;
	grub_uint32_t nblocks;
	grub_uint32_t gen;
	grub_uint32_t unused;
	grub_uint8_t pad[12];
};
GRUB_PACKED_END
#endif

/* Directory entry.  */
GRUB_PACKED_START
struct grub_ufs_dirent
{
	grub_uint32_t ino;
	grub_uint16_t direntlen;
	union
	{
		grub_uint16_t namelen;
		struct
		{
			grub_uint8_t filetype_bsd;
			grub_uint8_t namelen_bsd;
		};
	};
};
GRUB_PACKED_END

/* Information about a "mounted" ufs filesystem.  */
struct grub_ufs_data
{
	struct grub_ufs_sblock sblock;
	grub_disk_t disk;
	struct grub_ufs_inode inode;
	int ino;
	int linknest;
	int log2_blksz;
};

/* Forward declaration.  */
static grub_err_t grub_ufs_find_file(struct grub_ufs_data* data,
	const char* path);

static grub_disk_addr_t
grub_ufs_get_file_block(struct grub_ufs_data* data, grub_disk_addr_t blk)
{
	unsigned long indirsz;
	int log2_blksz, log_indirsz;

	/* Direct.  */
	if (blk < GRUB_UFS_DIRBLKS)
		return INODE_DIRBLOCKS(data, blk);

	log2_blksz = grub_ufs_to_cpu32(data->sblock.log2_blksz);

	blk -= GRUB_UFS_DIRBLKS;

	log_indirsz = data->log2_blksz - LOG_INODE_BLKSZ;
	indirsz = 1 << log_indirsz;

	/* Single indirect block.  */
	if (blk < indirsz)
	{
		grub_ufs_blk_t indir;
		grub_disk_read(data->disk,
			((grub_disk_addr_t)INODE_INDIRBLOCKS(data, 0))
			<< log2_blksz,
			blk * sizeof(indir), sizeof(indir), &indir);
		return indir;
	}
	blk -= indirsz;

	/* Double indirect block.  */
	if (blk < (grub_disk_addr_t)indirsz * (grub_disk_addr_t)indirsz)
	{
		grub_ufs_blk_t indir;

		grub_disk_read(data->disk,
			((grub_disk_addr_t)INODE_INDIRBLOCKS(data, 1))
			<< log2_blksz,
			(blk >> log_indirsz) * sizeof(indir),
			sizeof(indir), &indir);
		grub_disk_read(data->disk,
			grub_ufs_to_cpu_blk(indir) << log2_blksz,
			(blk & ((1 << log_indirsz) - 1)) * sizeof(indir),
			sizeof(indir), &indir);

		return indir;
	}

	blk -= (grub_disk_addr_t)indirsz * (grub_disk_addr_t)indirsz;

	/* Triple indirect block.  */
	if (!(blk >> (3 * log_indirsz)))
	{
		grub_ufs_blk_t indir;

		grub_disk_read(data->disk,
			((grub_disk_addr_t)INODE_INDIRBLOCKS(data, 2))
			<< log2_blksz,
			(blk >> (2 * log_indirsz)) * sizeof(indir),
			sizeof(indir), &indir);
		grub_disk_read(data->disk,
			grub_ufs_to_cpu_blk(indir) << log2_blksz,
			((blk >> log_indirsz)
				& ((1 << log_indirsz) - 1)) * sizeof(indir),
			sizeof(indir), &indir);

		grub_disk_read(data->disk,
			grub_ufs_to_cpu_blk(indir) << log2_blksz,
			(blk & ((1 << log_indirsz) - 1)) * sizeof(indir),
			sizeof(indir), &indir);

		return indir;
	}

	grub_error(GRUB_ERR_BAD_FS,
		"ufs does not support quadruple indirect blocks");
	return 0;
}

/* Read LEN bytes from the file described by DATA starting with byte
   POS.  Return the amount of read bytes in READ.  */
static grub_ssize_t
grub_ufs_read_file(struct grub_ufs_data* data,
	grub_disk_read_hook_t read_hook, void* read_hook_data,
	grub_off_t pos, grub_size_t len, char* buf)
{
	struct grub_ufs_sblock* sblock = &data->sblock;
	grub_off_t i;
	grub_off_t blockcnt;

	/* Adjust len so it we can't read past the end of the file.  */
	if (len + pos > INODE_SIZE(data))
		len = INODE_SIZE(data) - pos;

	blockcnt = (len + pos + UFS_BLKSZ(sblock) - 1) >> UFS_LOG_BLKSZ(sblock);

	for (i = pos >> UFS_LOG_BLKSZ(sblock); i < blockcnt; i++)
	{
		grub_disk_addr_t blknr;
		grub_off_t blockoff;
		grub_off_t blockend = UFS_BLKSZ(sblock);

		int skipfirst = 0;

		blockoff = pos & (UFS_BLKSZ(sblock) - 1);

		blknr = grub_ufs_get_file_block(data, i);
		if (grub_errno)
			return -1;

		/* Last block.  */
		if (i == blockcnt - 1)
		{
			blockend = (len + pos) & (UFS_BLKSZ(sblock) - 1);

			if (!blockend)
				blockend = UFS_BLKSZ(sblock);
		}

		/* First block.  */
		if (i == (pos >> UFS_LOG_BLKSZ(sblock)))
		{
			skipfirst = blockoff;
			blockend -= skipfirst;
		}

		/* XXX: If the block number is 0 this block is not stored on
	   disk but is zero filled instead.  */
		if (blknr)
		{
			data->disk->read_hook = read_hook;
			data->disk->read_hook_data = read_hook_data;
			grub_disk_read(data->disk,
				blknr << grub_ufs_to_cpu32(data->sblock.log2_blksz),
				skipfirst, blockend, buf);
			data->disk->read_hook = 0;
			if (grub_errno)
				return -1;
		}
		else
			grub_memset(buf, 0, blockend);

		buf += UFS_BLKSZ(sblock) - skipfirst;
	}

	return len;
}

/* Read inode INO from the mounted filesystem described by DATA.  This
   inode is used by default now.  */
static grub_err_t
grub_ufs_read_inode(struct grub_ufs_data* data, int ino, char* inode)
{
	struct grub_ufs_sblock* sblock = &data->sblock;

	/* Determine the group the inode is in.  */
	int group = ino / grub_ufs_to_cpu32(sblock->ino_per_group);

	/* Determine the inode within the group.  */
	int grpino = ino % grub_ufs_to_cpu32(sblock->ino_per_group);

	/* The first block of the group.  */
	int grpblk = group * (grub_ufs_to_cpu32(sblock->frags_per_group));

#ifndef MODE_UFS2
	grpblk += grub_ufs_to_cpu32(sblock->cylg_offset)
		* (group & (~grub_ufs_to_cpu32(sblock->cylg_mask)));
#endif

	if (!inode)
	{
		inode = (char*)&data->inode;
		data->ino = ino;
	}

	grub_disk_read(data->disk,
		((grub_ufs_to_cpu32(sblock->inoblk_offs) + grpblk)
			<< grub_ufs_to_cpu32(data->sblock.log2_blksz))
		+ grpino / UFS_INODE_PER_BLOCK,
		(grpino % UFS_INODE_PER_BLOCK)
		* sizeof(struct grub_ufs_inode),
		sizeof(struct grub_ufs_inode),
		inode);

	return grub_errno;
}

/* Lookup the symlink the current inode points to.  INO is the inode
   number of the directory the symlink is relative to.  */
static grub_err_t
grub_ufs_lookup_symlink(struct grub_ufs_data* data, int ino)
{
	char* symlink;
	grub_size_t sz = INODE_SIZE(data);

	if (++data->linknest > GRUB_UFS_MAX_SYMLNK_CNT)
		return grub_error(GRUB_ERR_SYMLINK_LOOP, N_("too deep nesting of symlinks"));

	symlink = grub_malloc(sz + 1);
	if (!symlink)
		return grub_errno;
	/* Normally we should just check that data->inode.nblocks == 0.
	   However old Linux doesn't maintain nblocks correctly and so it's always
	   0. If size is bigger than inline space then the symlink is surely not
	   inline.  */
	   /* Check against zero is paylindromic, no need to swap.  */
	if (data->inode.nblocks == 0
		&& INODE_SIZE(data) <= sizeof(data->inode.symlink))
		grub_strcpy(symlink, (char*)data->inode.symlink);
	else
	{
		if (grub_ufs_read_file(data, 0, 0, 0, sz, symlink) < 0)
		{
			grub_free(symlink);
			return grub_errno;
		}
	}
	symlink[sz] = '\0';

	/* The symlink is an absolute path, go back to the root inode.  */
	if (symlink[0] == '/')
		ino = GRUB_UFS_INODE;

	/* Now load in the old inode.  */
	if (grub_ufs_read_inode(data, ino, 0))
	{
		grub_free(symlink);
		return grub_errno;
	}

	grub_ufs_find_file(data, symlink);

	grub_free(symlink);

	return grub_errno;
}

/* Find the file with the pathname PATH on the filesystem described by
   DATA.  */
static grub_err_t
grub_ufs_find_file(struct grub_ufs_data* data, const char* path)
{
	const char* name;
	const char* next = path;
	unsigned int pos = 0;
	int dirino;
	char* filename;

	/* We reject filenames longer than the one we're looking
	   for without reading, so this allocation is enough.  */
	filename = grub_malloc(grub_strlen(path) + 2);
	if (!filename)
		return grub_errno;

	while (1)
	{
		struct grub_ufs_dirent dirent;

		name = next;
		/* Skip the first slash.  */
		while (*name == '/')
			name++;
		if (*name == 0)
		{
			grub_free(filename);
			return GRUB_ERR_NONE;
		}

		if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE)
			!= GRUB_UFS_ATTR_DIR)
		{
			grub_error(GRUB_ERR_BAD_FILE_TYPE,
				N_("not a directory"));
			goto fail;
		}

		/* Extract the actual part from the pathname.  */
		for (next = name; *next && *next != '/'; next++);
		for (pos = 0; ; pos += grub_ufs_to_cpu16(dirent.direntlen))
		{
			int namelen;

			if (pos >= INODE_SIZE(data))
			{
				grub_error(GRUB_ERR_FILE_NOT_FOUND,
					N_("file `%s' not found"),
					path);
				goto fail;
			}

			if (grub_ufs_read_file(data, 0, 0, pos, sizeof(dirent),
				(char*)&dirent) < 0)
				goto fail;

#ifdef MODE_UFS2
			namelen = dirent.namelen_bsd;
#else
			namelen = grub_ufs_to_cpu16(dirent.namelen);
#endif
			if (namelen < next - name)
				continue;

			if (grub_ufs_read_file(data, 0, 0, pos + sizeof(dirent),
				next - name + (namelen != next - name),
				filename) < 0)
				goto fail;

			if (grub_strncmp(name, filename, next - name) == 0
				&& (namelen == next - name || filename[next - name] == '\0'))
			{
				dirino = data->ino;
				grub_ufs_read_inode(data, grub_ufs_to_cpu32(dirent.ino), 0);

				if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE)
					== GRUB_UFS_ATTR_LNK)
				{
					grub_ufs_lookup_symlink(data, dirino);
					if (grub_errno)
						goto fail;
				}

				break;
			}
		}
	}
fail:
	grub_free(filename);
	return grub_errno;
}

/* Mount the filesystem on the disk DISK.  */
static struct grub_ufs_data*
grub_ufs_mount(grub_disk_t disk)
{
	struct grub_ufs_data* data;
	int* sblklist = sblocklist;

	data = grub_malloc(sizeof(struct grub_ufs_data));
	if (!data)
		return 0;

	/* Find a UFS sblock.  */
	while (*sblklist != -1)
	{
		grub_disk_read(disk, *sblklist, 0, sizeof(struct grub_ufs_sblock),
			&data->sblock);
		if (grub_errno)
			goto fail;

		/* No need to byteswap bsize in this check. It works the same on both
	   endiannesses.  */
		if (data->sblock.magic == grub_cpu_to_ufs32_compile_time(GRUB_UFS_MAGIC)
			&& data->sblock.bsize != 0
			&& ((data->sblock.bsize & (data->sblock.bsize - 1)) == 0)
			&& data->sblock.ino_per_group != 0)
		{
			for (data->log2_blksz = 0;
				(1U << data->log2_blksz) < grub_ufs_to_cpu32(data->sblock.bsize);
				data->log2_blksz++);

			data->disk = disk;
			data->linknest = 0;
			return data;
		}
		sblklist++;
	}

fail:

	if (grub_errno == GRUB_ERR_NONE || grub_errno == GRUB_ERR_OUT_OF_RANGE)
	{
#ifdef MODE_UFS2
		grub_error(GRUB_ERR_BAD_FS, "not an ufs2 filesystem");
#else
		grub_error(GRUB_ERR_BAD_FS, "not an ufs1 filesystem");
#endif
	}

	grub_free(data);

	return 0;
}

static grub_err_t
grub_ufs_dir(grub_disk_t disk, const char* path,
	grub_fs_dir_hook_t hook, void* hook_data)
{
	struct grub_ufs_data* data;
	unsigned int pos = 0;

	data = grub_ufs_mount(disk);
	if (!data)
		return grub_errno;

	grub_ufs_read_inode(data, GRUB_UFS_INODE, 0);
	if (grub_errno)
		return grub_errno;

	if (!path || path[0] != '/')
	{
		grub_error(GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), path);
		return grub_errno;
	}

	grub_ufs_find_file(data, path);
	if (grub_errno)
		goto fail;

	if ((INODE_MODE(data) & GRUB_UFS_ATTR_TYPE) != GRUB_UFS_ATTR_DIR)
	{
		grub_error(GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
		goto fail;
	}

	while (pos < INODE_SIZE(data))
	{
		struct grub_ufs_dirent dirent;
		int namelen;

		if (grub_ufs_read_file(data, 0, 0, pos, sizeof(dirent),
			(char*)&dirent) < 0)
			break;

		if (dirent.direntlen == 0)
			break;

#ifdef MODE_UFS2
		namelen = dirent.namelen_bsd;
#else
		namelen = grub_ufs_to_cpu16(dirent.namelen);
#endif

		char* filename = grub_malloc(namelen + 1);
		if (!filename)
			goto fail;
		struct grub_dirhook_info info;
		struct grub_ufs_inode inode;

		grub_memset(&info, 0, sizeof(info));

		if (grub_ufs_read_file(data, 0, 0, pos + sizeof(dirent),
			namelen, filename) < 0)
		{
			grub_free(filename);
			break;
		}

		filename[namelen] = '\0';
		grub_ufs_read_inode(data, grub_ufs_to_cpu32(dirent.ino),
			(char*)&inode);

		info.dir = ((grub_ufs_to_cpu16(inode.mode) & GRUB_UFS_ATTR_TYPE)
			== GRUB_UFS_ATTR_DIR);
		info.symlink = ((grub_ufs_to_cpu16(inode.mode) & GRUB_UFS_ATTR_TYPE)
			== GRUB_UFS_ATTR_LNK);
#ifdef MODE_UFS2
		info.mtime = grub_ufs_to_cpu64(inode.mtime);
#else
		info.mtime = grub_ufs_to_cpu32(inode.mtime);
#endif
		info.mtimeset = 1;

		if (hook(filename, &info, hook_data))
		{
			grub_free(filename);
			break;
		}

		grub_free(filename);

		pos += grub_ufs_to_cpu16(dirent.direntlen);
	}

fail:
	grub_free(data);

	return grub_errno;
}

/* Open a file named NAME and initialize FILE.  */
static grub_err_t
grub_ufs_open(struct grub_file* file, const char* name)
{
	struct grub_ufs_data* data;
	data = grub_ufs_mount(file->disk);
	if (!data)
		return grub_errno;

	grub_ufs_read_inode(data, 2, 0);
	if (grub_errno)
	{
		grub_free(data);
		return grub_errno;
	}

	if (!name || name[0] != '/')
	{
		grub_error(GRUB_ERR_BAD_FILENAME, N_("invalid file name `%s'"), name);
		return grub_errno;
	}

	grub_ufs_find_file(data, name);
	if (grub_errno)
	{
		grub_free(data);
		return grub_errno;
	}

	file->data = data;
	file->size = INODE_SIZE(data);

	return GRUB_ERR_NONE;
}

static grub_ssize_t
grub_ufs_read(grub_file_t file, char* buf, grub_size_t len)
{
	struct grub_ufs_data* data =
		(struct grub_ufs_data*)file->data;

	return grub_ufs_read_file(data, file->read_hook, file->read_hook_data,
		file->offset, len, buf);
}

static grub_err_t
grub_ufs_close(grub_file_t file)
{
	grub_free(file->data);

	return GRUB_ERR_NONE;
}

static grub_err_t
grub_ufs_label(grub_disk_t disk, char** label)
{
	struct grub_ufs_data* data = 0;

	*label = 0;

	data = grub_ufs_mount(disk);
	if (data)
		*label = grub_strdup((char*)data->sblock.volume_name);

	grub_free(data);

	return grub_errno;
}

static grub_err_t
grub_ufs_uuid(grub_disk_t disk, char** uuid)
{
	struct grub_ufs_data* data;

	data = grub_ufs_mount(disk);
	if (data && (data->sblock.uuidhi != 0 || data->sblock.uuidlow != 0))
		*uuid = grub_xasprintf("%08x%08x",
			(unsigned)grub_ufs_to_cpu32(data->sblock.uuidhi),
			(unsigned)grub_ufs_to_cpu32(data->sblock.uuidlow));
	else
		*uuid = NULL;

	grub_free(data);

	return grub_errno;
}

/* Get mtime.  */
static grub_err_t
grub_ufs_mtime(grub_disk_t disk, grub_int64_t* tm)
{
	struct grub_ufs_data* data = 0;

	data = grub_ufs_mount(disk);
	if (!data)
		*tm = 0;
	else
	{
		*tm = grub_ufs_to_cpu32(data->sblock.mtime);
#ifdef MODE_UFS2
		if (*tm < (grub_int64_t)grub_ufs_to_cpu64(data->sblock.mtime2))
			*tm = grub_ufs_to_cpu64(data->sblock.mtime2);
#endif
	}

	grub_free(data);

	return grub_errno;
}



static struct grub_fs grub_ufs_fs =
{
#ifdef MODE_UFS2
	.name = "ufs2",
#else
#ifdef MODE_BIGENDIAN
	.name = "ufs1_be",
#else
	.name = "ufs1",
#endif
#endif
	.fs_dir = grub_ufs_dir,
	.fs_open = grub_ufs_open,
	.fs_read = grub_ufs_read,
	.fs_close = grub_ufs_close,
	.fs_label = grub_ufs_label,
	.fs_uuid = grub_ufs_uuid,
	.fs_mtime = grub_ufs_mtime,
	.next = 0
};

#ifdef MODE_UFS2
GRUB_MOD_INIT(ufs2)
#else
#ifdef MODE_BIGENDIAN
GRUB_MOD_INIT(ufs1_be)
#else
GRUB_MOD_INIT(ufs1)
#endif
#endif
{
	grub_fs_register(&grub_ufs_fs);
}

#ifdef MODE_UFS2
GRUB_MOD_FINI(ufs2)
#else
#ifdef MODE_BIGENDIAN
GRUB_MOD_FINI(ufs1_be)
#else
GRUB_MOD_FINI(ufs1)
#endif
#endif
{
	grub_fs_unregister(&grub_ufs_fs);
}
