/*
 * linux/fs/hfs/mdb.c
 *
 * Copyright (C) 1995-1997  Paul H. Hargrove
 * This file may be distributed under the terms of the GNU General Public License.
 *
 * This file contains functions for reading/writing the MDB.
 *
 * "XXX" in a comment is a note to myself to consider changing something.
 *
 * In function preconditions the term "valid" applied to a pointer to
 * a structure means that the pointer is non-NULL and the structure it
 * points to has all fields initialized to consistent values.
 *
 * The code in this file initializes some structures which contain
 * pointers by calling memset(&foo, 0, sizeof(foo)).
 * This produces the desired behavior only due to the non-ANSI
 * assumption that the machine representation of NULL is all zeros.
 */

#include "hfs.h"

/*================ File-local data types ================*/

/* 
 * The HFS Master Directory Block (MDB).
 *
 * Also known as the Volume Information Block (VIB), this structure is
 * the HFS equivalent of a superblock.
 *
 * Reference: _Inside Macintosh: Files_ pages 2-59 through 2-62
 *
 * modified for HFS Extended
 */
struct raw_mdb {
	hfs_word_t	drSigWord;	/* Signature word indicating fs type */
	hfs_lword_t	drCrDate;	/* fs creation date/time */
	hfs_lword_t	drLsMod;	/* fs modification date/time */
	hfs_word_t	drAtrb;		/* fs attributes */
	hfs_word_t	drNmFls;	/* number of files in root directory */
	hfs_word_t	drVBMSt;	/* location (in 512-byte blocks)
					   of the volume bitmap */
	hfs_word_t	drAllocPtr;	/* location (in allocation blocks)
					   to begin next allocation search */
	hfs_word_t	drNmAlBlks;	/* number of allocation blocks */
	hfs_lword_t	drAlBlkSiz;	/* bytes in an allocation block */
	hfs_lword_t	drClpSiz;	/* clumpsize, the number of bytes to
					   allocate when extending a file */
	hfs_word_t	drAlBlSt;	/* location (in 512-byte blocks)
					   of the first allocation block */
	hfs_lword_t	drNxtCNID;	/* CNID to assign to the next
					   file or directory created */
	hfs_word_t	drFreeBks;	/* number of free allocation blocks */
	hfs_byte_t	drVN[28];	/* the volume label */
	hfs_lword_t	drVolBkUp;	/* fs backup date/time */
	hfs_word_t	drVSeqNum;	/* backup sequence number */
	hfs_lword_t	drWrCnt;	/* fs write count */
	hfs_lword_t	drXTClpSiz;	/* clumpsize for the extents B-tree */
	hfs_lword_t	drCTClpSiz;	/* clumpsize for the catalog B-tree */
	hfs_word_t	drNmRtDirs;	/* number of directories in
					   the root directory */
	hfs_lword_t	drFilCnt;	/* number of files in the fs */
	hfs_lword_t	drDirCnt;	/* number of directories in the fs */
	hfs_byte_t	drFndrInfo[32];	/* data used by the Finder */
	hfs_word_t	drEmbedSigWord;	/* embedded volume signature */
	hfs_lword_t     drEmbedExtent;  /* starting block number (xdrStABN) 
					   and number of allocation blocks 
					   (xdrNumABlks) occupied by embedded
					   volume */
	hfs_lword_t	drXTFlSize;	/* bytes in the extents B-tree */
	hfs_byte_t	drXTExtRec[12];	/* extents B-tree's first 3 extents */
	hfs_lword_t	drCTFlSize;	/* bytes in the catalog B-tree */
	hfs_byte_t	drCTExtRec[12];	/* catalog B-tree's first 3 extents */
} __attribute__((packed));

/*================ Global functions ================*/

/*
 * hfs_mdb_get()
 *
 * Build the in-core MDB for a filesystem, including
 * the B-trees and the volume bitmap.
 */
struct hfs_mdb *hfs_mdb_get(hfs_sysmdb sys_mdb, int readonly,
			    hfs_s32 part_start)
{
	struct hfs_mdb *mdb;
	hfs_buffer buf;
	struct raw_mdb *raw;
	unsigned int bs, block;
	int lcv, limit;
	hfs_buffer *bmbuf;

	if (!HFS_NEW(mdb)) {
		hfs_warn("hfs_fs: out of memory\n");
		return NULL;
	}

	memset(mdb, 0, sizeof(*mdb));
	mdb->magic = HFS_MDB_MAGIC;
	mdb->sys_mdb = sys_mdb;
	INIT_LIST_HEAD(&mdb->entry_dirty);
	hfs_init_waitqueue(&mdb->rename_wait);
	hfs_init_waitqueue(&mdb->bitmap_wait);

	/* See if this is an HFS filesystem */
	buf = hfs_buffer_get(sys_mdb, part_start + HFS_MDB_BLK, 1);
	if (!hfs_buffer_ok(buf)) {
		hfs_warn("hfs_fs: Unable to read superblock\n");
		HFS_DELETE(mdb);
		goto bail2;
	}

	raw = (struct raw_mdb *)hfs_buffer_data(buf);
	if (hfs_get_ns(raw->drSigWord) != htons(HFS_SUPER_MAGIC)) {
		hfs_buffer_put(buf);
		HFS_DELETE(mdb);
		goto bail2;
	}
	mdb->buf = buf;
	
	bs = hfs_get_hl(raw->drAlBlkSiz);
	if (!bs || (bs & (HFS_SECTOR_SIZE-1))) {
		hfs_warn("hfs_fs: bad allocation block size %d != 512\n", bs);
		hfs_buffer_put(buf);
		HFS_DELETE(mdb);
		goto bail2;
	}
	mdb->alloc_blksz = bs >> HFS_SECTOR_SIZE_BITS;

	/* These parameters are read from the MDB, and never written */
	mdb->create_date = hfs_get_hl(raw->drCrDate);
	mdb->fs_ablocks  = hfs_get_hs(raw->drNmAlBlks);
	mdb->fs_start    = hfs_get_hs(raw->drAlBlSt) + part_start;
	mdb->backup_date = hfs_get_hl(raw->drVolBkUp);
	mdb->clumpablks  = (hfs_get_hl(raw->drClpSiz) / mdb->alloc_blksz)
						 >> HFS_SECTOR_SIZE_BITS;
	memcpy(mdb->vname, raw->drVN, sizeof(raw->drVN));

	/* These parameters are read from and written to the MDB */
	mdb->modify_date  = hfs_get_nl(raw->drLsMod);
	mdb->attrib       = hfs_get_ns(raw->drAtrb);
	mdb->free_ablocks = hfs_get_hs(raw->drFreeBks);
	mdb->next_id      = hfs_get_hl(raw->drNxtCNID);
	mdb->write_count  = hfs_get_hl(raw->drWrCnt);
	mdb->root_files   = hfs_get_hs(raw->drNmFls);
	mdb->root_dirs    = hfs_get_hs(raw->drNmRtDirs);
	mdb->file_count   = hfs_get_hl(raw->drFilCnt);
	mdb->dir_count    = hfs_get_hl(raw->drDirCnt);

	/* TRY to get the alternate (backup) MDB. */
	lcv = mdb->fs_start + mdb->fs_ablocks * mdb->alloc_blksz;
	limit = lcv + mdb->alloc_blksz;
	for (; lcv < limit; ++lcv) {
		buf = hfs_buffer_get(sys_mdb, lcv, 1);
		if (hfs_buffer_ok(buf)) {
			struct raw_mdb *tmp =
				(struct raw_mdb *)hfs_buffer_data(buf);
			
			if (hfs_get_ns(tmp->drSigWord) ==
			    htons(HFS_SUPER_MAGIC)) {
				mdb->alt_buf = buf;
				break;
			}
		}
		hfs_buffer_put(buf);
	}
	
	if (mdb->alt_buf == NULL) {
		hfs_warn("hfs_fs: unable to locate alternate MDB\n");
		hfs_warn("hfs_fs: continuing without an alternate MDB\n");
	}
	
	/* read in the bitmap */
	block = hfs_get_hs(raw->drVBMSt) + part_start;
	bmbuf = mdb->bitmap;
	lcv = (mdb->fs_ablocks + 4095) / 4096;
	for ( ; lcv; --lcv, ++bmbuf, ++block) {
		if (!hfs_buffer_ok(*bmbuf =
				   hfs_buffer_get(sys_mdb, block, 1))) {
			hfs_warn("hfs_fs: unable to read volume bitmap\n");
			goto bail1;
		}
	}

	if (!(mdb->ext_tree = hfs_btree_init(mdb, htonl(HFS_EXT_CNID),
					     raw->drXTExtRec,
					     hfs_get_hl(raw->drXTFlSize),
					     hfs_get_hl(raw->drXTClpSiz))) ||
	    !(mdb->cat_tree = hfs_btree_init(mdb, htonl(HFS_CAT_CNID),
					     raw->drCTExtRec,
					     hfs_get_hl(raw->drCTFlSize),
					     hfs_get_hl(raw->drCTClpSiz)))) {
		hfs_warn("hfs_fs: unable to initialize data structures\n");
		goto bail1;
	}

	if (!(mdb->attrib & htons(HFS_SB_ATTRIB_CLEAN))) {
		hfs_warn("hfs_fs: WARNING: mounting unclean filesystem.\n");
	} else if (!readonly) {
		/* Mark the volume uncleanly unmounted in case we crash */
		hfs_put_ns(mdb->attrib & htons(~HFS_SB_ATTRIB_CLEAN),
			   raw->drAtrb);
		hfs_buffer_dirty(mdb->buf);
		hfs_buffer_sync(mdb->buf);
	}

	return mdb;

bail1:
	hfs_mdb_put(mdb, readonly);
bail2:
	return NULL;
}

/*
 * hfs_mdb_commit()
 *
 * Description:
 *   This updates the MDB on disk (look also at hfs_write_super()).
 *   It does not check, if the superblock has been modified, or
 *   if the filesystem has been mounted read-only. It is mainly
 *   called by hfs_write_super() and hfs_btree_extend().
 * Input Variable(s):
 *   struct hfs_mdb *mdb: Pointer to the hfs MDB
 *   int backup;
 * Output Variable(s):
 *   NONE
 * Returns:
 *   void
 * Preconditions:
 *   'mdb' points to a "valid" (struct hfs_mdb).
 * Postconditions:
 *   The HFS MDB and on disk will be updated, by copying the possibly
 *   modified fields from the in memory MDB (in native byte order) to
 *   the disk block buffer.
 *   If 'backup' is non-zero then the alternate MDB is also written
 *   and the function doesn't return until it is actually on disk.
 */
void hfs_mdb_commit(struct hfs_mdb *mdb, int backup)
{
	struct raw_mdb *raw = (struct raw_mdb *)hfs_buffer_data(mdb->buf);

	/* Commit catalog entries to buffers */
	hfs_cat_commit(mdb);

	/* Commit B-tree data to buffers */
	hfs_btree_commit(mdb->cat_tree, raw->drCTExtRec, raw->drCTFlSize);
	hfs_btree_commit(mdb->ext_tree, raw->drXTExtRec, raw->drXTFlSize);

	/* Update write_count and modify_date */
	++mdb->write_count;
	mdb->modify_date = hfs_time();

	/* These parameters may have been modified, so write them back */
	hfs_put_nl(mdb->modify_date,   raw->drLsMod);
	hfs_put_hs(mdb->free_ablocks,  raw->drFreeBks);
	hfs_put_hl(mdb->next_id,       raw->drNxtCNID);
	hfs_put_hl(mdb->write_count,   raw->drWrCnt);
	hfs_put_hs(mdb->root_files,    raw->drNmFls);
	hfs_put_hs(mdb->root_dirs,     raw->drNmRtDirs);
	hfs_put_hl(mdb->file_count,    raw->drFilCnt);
	hfs_put_hl(mdb->dir_count,     raw->drDirCnt);

	/* write MDB to disk */
	hfs_buffer_dirty(mdb->buf);

       	/* write the backup MDB, not returning until it is written. 
         * we only do this when either the catalog or extents overflow
         * files grow. */
        if (backup && hfs_buffer_ok(mdb->alt_buf)) {
		struct raw_mdb *tmp = (struct raw_mdb *)
			hfs_buffer_data(mdb->alt_buf);
		
		if ((hfs_get_hl(tmp->drCTFlSize) < 
		     hfs_get_hl(raw->drCTFlSize)) ||
		    (hfs_get_hl(tmp->drXTFlSize) <
		     hfs_get_hl(raw->drXTFlSize))) {
			memcpy(hfs_buffer_data(mdb->alt_buf), 
			       hfs_buffer_data(mdb->buf), HFS_SECTOR_SIZE); 
			hfs_buffer_dirty(mdb->alt_buf);
			hfs_buffer_sync(mdb->alt_buf);
		}
        }
}

/*
 * hfs_mdb_put()
 *
 * Release the resources associated with the in-core MDB.  */
void hfs_mdb_put(struct hfs_mdb *mdb, int readonly) {
	int lcv;

	/* invalidate cached catalog entries */
	hfs_cat_invalidate(mdb);

	/* free the B-trees */
	hfs_btree_free(mdb->ext_tree);
	hfs_btree_free(mdb->cat_tree);

	/* free the volume bitmap */
	for (lcv = 0; lcv < HFS_BM_MAXBLOCKS; ++lcv) {
		hfs_buffer_put(mdb->bitmap[lcv]);
	}

	/* update volume attributes */
	if (!readonly) {
		struct raw_mdb *raw = 
				(struct raw_mdb *)hfs_buffer_data(mdb->buf);
		hfs_put_ns(mdb->attrib, raw->drAtrb);
		hfs_buffer_dirty(mdb->buf);
	}

	/* free the buffers holding the primary and alternate MDBs */
	hfs_buffer_put(mdb->buf);
	hfs_buffer_put(mdb->alt_buf);

	/* free the MDB */
	HFS_DELETE(mdb);
}
