blob: a82753c462d91f51641aa169f39c1a45a1d58afb [file] [log] [blame]
/*
* linux/fs/hfs/catalog.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 the functions related to the catalog B-tree.
*
* "XXX" in a comment is a note to myself to consider changing something.
*
* Cache code shamelessly stolen from
* linux/fs/inode.c Copyright (C) 1991, 1992 Linus Torvalds
* re-shamelessly stolen Copyright (C) 1997 Linus Torvalds
*
* 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 by calling
* memset(&foo, 0, sizeof(foo)). This produces the desired behavior
* only due to the non-ANSI assumption that the machine representation
*/
#include "hfs.h"
/*================ Variable-like macros ================*/
/* Number of hash table slots */
#define C_HASHBITS 10
#define C_HASHSIZE (1UL << C_HASHBITS)
#define C_HASHMASK (C_HASHSIZE - 1)
/* Number of entries to fit in a single page on an i386.
* Actually, now it's used to increment the free entry pool. */
#define CCACHE_INC (PAGE_SIZE/sizeof(struct hfs_cat_entry))
#define CCACHE_MAX (CCACHE_INC * 8)
/*================ File-local data types ================*/
/* The catalog record for a file */
typedef struct {
hfs_byte_t Flags; /* Flags such as read-only */
hfs_byte_t Typ; /* file version number = 0 */
hfs_finfo_t UsrWds; /* data used by the Finder */
hfs_lword_t FlNum; /* The CNID */
hfs_word_t StBlk; /* obsolete */
hfs_lword_t LgLen; /* The logical EOF of the data fork*/
hfs_lword_t PyLen; /* The physical EOF of the data fork */
hfs_word_t RStBlk; /* obsolete */
hfs_lword_t RLgLen; /* The logical EOF of the rsrc fork */
hfs_lword_t RPyLen; /* The physical EOF of the rsrc fork */
hfs_lword_t CrDat; /* The creation date */
hfs_lword_t MdDat; /* The modified date */
hfs_lword_t BkDat; /* The last backup date */
hfs_fxinfo_t FndrInfo; /* more data for the Finder */
hfs_word_t ClpSize; /* number of bytes to allocate
when extending files */
hfs_byte_t ExtRec[12]; /* first extent record
for the data fork */
hfs_byte_t RExtRec[12]; /* first extent record
for the resource fork */
hfs_lword_t Resrv; /* reserved by Apple */
} __attribute__((packed)) FIL_REC;
/* the catalog record for a directory */
typedef struct {
hfs_word_t Flags; /* flags */
hfs_word_t Val; /* Valence: number of files and
dirs in the directory */
hfs_lword_t DirID; /* The CNID */
hfs_lword_t CrDat; /* The creation date */
hfs_lword_t MdDat; /* The modification date */
hfs_lword_t BkDat; /* The last backup date */
hfs_dinfo_t UsrInfo; /* data used by the Finder */
hfs_dxinfo_t FndrInfo; /* more data used by Finder */
hfs_byte_t Resrv[16]; /* reserved by Apple */
} __attribute__((packed)) DIR_REC;
/* the catalog record for a thread */
typedef struct {
hfs_byte_t Reserv[8]; /* reserved by Apple */
hfs_lword_t ParID; /* CNID of parent directory */
struct hfs_name CName; /* The name of this entry */
} __attribute__((packed)) THD_REC;
/* A catalog tree record */
struct hfs_cat_rec {
hfs_byte_t cdrType; /* The type of entry */
hfs_byte_t cdrResrv2; /* padding */
union {
FIL_REC fil;
DIR_REC dir;
THD_REC thd;
} u;
} __attribute__((packed));
/*================ File-local variables ================*/
static LIST_HEAD(entry_in_use);
static LIST_HEAD(entry_unused);
static struct list_head hash_table[C_HASHSIZE];
static spinlock_t entry_lock = SPIN_LOCK_UNLOCKED;
static struct {
int nr_entries;
int nr_free_entries;
} entries_stat;
/*================ File-local functions ================*/
/*
* brec_to_id
*
* Get the CNID from a brec
*/
static inline hfs_u32 brec_to_id(struct hfs_brec *brec)
{
struct hfs_cat_rec *rec = brec->data;
return hfs_get_nl((rec->cdrType==HFS_CDR_FIL) ?
rec->u.fil.FlNum : rec->u.dir.DirID);
}
/*
* hashfn()
*
* hash an (struct mdb *) and a (struct hfs_cat_key *) to an integer.
*/
static inline unsigned int hashfn(const struct hfs_mdb *mdb,
const struct hfs_cat_key *key)
{
unsigned int hash;
hash = (unsigned long) mdb | (unsigned long) key->ParID[3] |
hfs_strhash(key->CName.Name, key->CName.Len);
hash = hash ^ (hash >> C_HASHBITS) ^ (hash >> C_HASHBITS*2);
return hash & C_HASHMASK;
}
/*
* hash()
*
* hash an (struct mdb *) and a (struct hfs_cat_key *)
* to a pointer to a slot in the hash table.
*/
static inline struct list_head *hash(struct hfs_mdb *mdb,
const struct hfs_cat_key *key)
{
return hash_table + hashfn(mdb, key);
}
static inline void insert_hash(struct hfs_cat_entry *entry)
{
struct list_head *head = hash(entry->mdb, &entry->key);
list_add(&entry->hash, head);
}
static inline void remove_hash(struct hfs_cat_entry *entry)
{
list_del(&entry->hash);
INIT_LIST_HEAD(&entry->hash);
}
/*
* wait_on_entry()
*
* Sleep until a locked entry is unlocked.
*/
static inline void wait_on_entry(struct hfs_cat_entry * entry)
{
while ((entry->state & HFS_LOCK)) {
hfs_sleep_on(&entry->wait);
}
}
/*
* lock_entry()
*
* Obtain an exclusive lock on an entry.
*/
static void lock_entry(struct hfs_cat_entry * entry)
{
wait_on_entry(entry);
spin_lock(&entry_lock);
entry->state |= HFS_LOCK;
spin_unlock(&entry_lock);
}
/*
* lock_entry()
*
* Relinquish an exclusive lock on an entry.
*/
static void unlock_entry(struct hfs_cat_entry * entry)
{
spin_lock(&entry_lock);
entry->state &= ~HFS_LOCK;
spin_unlock(&entry_lock);
hfs_wake_up(&entry->wait);
}
/* put entry on mdb dirty list. */
void hfs_cat_mark_dirty(struct hfs_cat_entry *entry)
{
struct hfs_mdb *mdb = entry->mdb;
spin_lock(&entry_lock);
if (!(entry->state & HFS_DIRTY)) {
entry->state |= HFS_DIRTY;
/* Only add valid (ie hashed) entries to the dirty list. */
if (!list_empty(&entry->hash)) {
list_del(&entry->list);
list_add(&entry->list, &mdb->entry_dirty);
}
}
spin_unlock(&entry_lock);
}
/* delete an entry and remove it from the hash table. */
static void delete_entry(struct hfs_cat_entry *entry)
{
if (!(entry->state & HFS_DELETED)) {
entry->state |= HFS_DELETED;
list_del(&entry->hash);
INIT_LIST_HEAD(&entry->hash);
if (entry->type == HFS_CDR_FIL) {
/* free all extents */
entry->u.file.data_fork.lsize = 0;
hfs_extent_adj(&entry->u.file.data_fork);
entry->u.file.rsrc_fork.lsize = 0;
hfs_extent_adj(&entry->u.file.rsrc_fork);
}
}
}
static inline void init_entry(struct hfs_cat_entry *entry)
{
memset(entry, 0, sizeof(*entry));
hfs_init_waitqueue(&entry->wait);
INIT_LIST_HEAD(&entry->hash);
INIT_LIST_HEAD(&entry->list);
}
/*
* hfs_cat_alloc()
*
* Try to allocate another entry.
*/
static inline struct hfs_cat_entry *hfs_cat_alloc(void)
{
struct hfs_cat_entry *entry;
if (!HFS_NEW(entry))
return NULL;
init_entry(entry);
return entry;
}
/* this gets called with the spinlock held. */
static int grow_entries(void)
{
struct hfs_cat_entry *entry;
int i;
for (i = 0; i < CCACHE_INC; i++) {
if (!(entry = hfs_cat_alloc()))
break;
list_add(&entry->list, &entry_unused);
}
entries_stat.nr_entries += i;
entries_stat.nr_free_entries += i;
return i;
}
/*
* __read_entry()
*
* Convert a (struct hfs_cat_rec) to a (struct hfs_cat_entry).
*/
static void __read_entry(struct hfs_cat_entry *entry,
const struct hfs_cat_rec *cat)
{
entry->type = cat->cdrType;
if (cat->cdrType == HFS_CDR_DIR) {
struct hfs_dir *dir = &entry->u.dir;
entry->cnid = hfs_get_nl(cat->u.dir.DirID);
dir->magic = HFS_DIR_MAGIC;
dir->flags = hfs_get_ns(cat->u.dir.Flags);
memcpy(&entry->info.dir.dinfo, &cat->u.dir.UsrInfo, 16);
memcpy(&entry->info.dir.dxinfo, &cat->u.dir.FndrInfo, 16);
entry->create_date = hfs_get_nl(cat->u.dir.CrDat);
entry->modify_date = hfs_get_nl(cat->u.dir.MdDat);
entry->backup_date = hfs_get_nl(cat->u.dir.BkDat);
dir->dirs = dir->files = 0;
hfs_init_waitqueue(&dir->read_wait);
hfs_init_waitqueue(&dir->write_wait);
} else if (cat->cdrType == HFS_CDR_FIL) {
struct hfs_file *fil = &entry->u.file;
entry->cnid = hfs_get_nl(cat->u.fil.FlNum);
fil->magic = HFS_FILE_MAGIC;
fil->data_fork.fork = HFS_FK_DATA;
fil->data_fork.entry = entry;
fil->data_fork.lsize = hfs_get_hl(cat->u.fil.LgLen);
fil->data_fork.psize = hfs_get_hl(cat->u.fil.PyLen) >>
HFS_SECTOR_SIZE_BITS;
hfs_extent_in(&fil->data_fork, cat->u.fil.ExtRec);
fil->rsrc_fork.fork = HFS_FK_RSRC;
fil->rsrc_fork.entry = entry;
fil->rsrc_fork.lsize = hfs_get_hl(cat->u.fil.RLgLen);
fil->rsrc_fork.psize = hfs_get_hl(cat->u.fil.RPyLen) >>
HFS_SECTOR_SIZE_BITS;
hfs_extent_in(&fil->rsrc_fork, cat->u.fil.RExtRec);
memcpy(&entry->info.file.finfo, &cat->u.fil.UsrWds, 16);
memcpy(&entry->info.file.fxinfo, &cat->u.fil.FndrInfo, 16);
entry->create_date = hfs_get_nl(cat->u.fil.CrDat);
entry->modify_date = hfs_get_nl(cat->u.fil.MdDat);
entry->backup_date = hfs_get_nl(cat->u.fil.BkDat);
fil->clumpablks = (hfs_get_hs(cat->u.fil.ClpSize)
/ entry->mdb->alloc_blksz)
>> HFS_SECTOR_SIZE_BITS;
fil->flags = cat->u.fil.Flags;
} else {
hfs_warn("hfs_fs: entry is neither file nor directory!\n");
}
}
/*
* count_dir_entries()
*
* Count the number of files and directories in a given directory.
*/
static inline void count_dir_entries(struct hfs_cat_entry *entry,
struct hfs_brec *brec)
{
int error = 0;
hfs_u32 cnid;
hfs_u8 type;
if (!hfs_cat_open(entry, brec)) {
while (!(error = hfs_cat_next(entry, brec, 1, &cnid, &type))) {
if (type == HFS_CDR_FIL) {
++entry->u.dir.files;
} else if (type == HFS_CDR_DIR) {
++entry->u.dir.dirs;
}
} /* -ENOENT is normal termination */
}
if (error != -ENOENT) {
entry->cnid = 0;
}
}
/*
* read_entry()
*
* Convert a (struct hfs_brec) to a (struct hfs_cat_entry).
*/
static inline void read_entry(struct hfs_cat_entry *entry,
struct hfs_brec *brec)
{
int need_count;
struct hfs_cat_rec *rec = brec->data;
__read_entry(entry, rec);
need_count = (rec->cdrType == HFS_CDR_DIR) && rec->u.dir.Val;
hfs_brec_relse(brec, NULL);
if (need_count) {
count_dir_entries(entry, brec);
}
}
/*
* __write_entry()
*
* Convert a (struct hfs_cat_entry) to a (struct hfs_cat_rec).
*/
static void __write_entry(const struct hfs_cat_entry *entry,
struct hfs_cat_rec *cat)
{
if (entry->type == HFS_CDR_DIR) {
const struct hfs_dir *dir = &entry->u.dir;
hfs_put_ns(dir->flags, cat->u.dir.Flags);
hfs_put_hs(dir->dirs + dir->files, cat->u.dir.Val);
hfs_put_nl(entry->cnid, cat->u.dir.DirID);
hfs_put_nl(entry->create_date, cat->u.dir.CrDat);
hfs_put_nl(entry->modify_date, cat->u.dir.MdDat);
hfs_put_nl(entry->backup_date, cat->u.dir.BkDat);
memcpy(&cat->u.dir.UsrInfo, &entry->info.dir.dinfo, 16);
memcpy(&cat->u.dir.FndrInfo, &entry->info.dir.dxinfo, 16);
} else if (entry->type == HFS_CDR_FIL) {
const struct hfs_file *fil = &entry->u.file;
cat->u.fil.Flags = fil->flags;
hfs_put_nl(entry->cnid, cat->u.fil.FlNum);
memcpy(&cat->u.fil.UsrWds, &entry->info.file.finfo, 16);
hfs_put_hl(fil->data_fork.lsize, cat->u.fil.LgLen);
hfs_put_hl(fil->data_fork.psize << HFS_SECTOR_SIZE_BITS,
cat->u.fil.PyLen);
hfs_put_hl(fil->rsrc_fork.lsize, cat->u.fil.RLgLen);
hfs_put_hl(fil->rsrc_fork.psize << HFS_SECTOR_SIZE_BITS,
cat->u.fil.RPyLen);
hfs_put_nl(entry->create_date, cat->u.fil.CrDat);
hfs_put_nl(entry->modify_date, cat->u.fil.MdDat);
hfs_put_nl(entry->backup_date, cat->u.fil.BkDat);
memcpy(&cat->u.fil.FndrInfo, &entry->info.file.fxinfo, 16);
hfs_put_hs((fil->clumpablks * entry->mdb->alloc_blksz)
<< HFS_SECTOR_SIZE_BITS, cat->u.fil.ClpSize);
hfs_extent_out(&fil->data_fork, cat->u.fil.ExtRec);
hfs_extent_out(&fil->rsrc_fork, cat->u.fil.RExtRec);
} else {
hfs_warn("__write_entry: invalid entry\n");
}
}
/*
* write_entry()
*
* Write a modified entry back to the catalog B-tree. this gets called
* with the entry locked.
*/
static void write_entry(struct hfs_cat_entry * entry)
{
struct hfs_brec brec;
int error;
if (!(entry->state & HFS_DELETED)) {
error = hfs_bfind(&brec, entry->mdb->cat_tree,
HFS_BKEY(&entry->key), HFS_BFIND_WRITE);
if (!error) {
if ((entry->state & HFS_KEYDIRTY)) {
/* key may have changed case due to a rename */
entry->state &= ~HFS_KEYDIRTY;
if (brec.key->KeyLen != entry->key.KeyLen) {
hfs_warn("hfs_write_entry: key length "
"changed!\n");
error = 1;
} else {
memcpy(brec.key, &entry->key,
entry->key.KeyLen);
}
} else if (entry->cnid != brec_to_id(&brec)) {
hfs_warn("hfs_write_entry: CNID "
"changed unexpectedly!\n");
error = 1;
}
if (!error) {
__write_entry(entry, brec.data);
}
hfs_brec_relse(&brec, NULL);
}
if (error) {
hfs_warn("hfs_write_entry: unable to write "
"entry %08x\n", entry->cnid);
}
}
}
/* this gets called with the spinlock held. */
static struct hfs_cat_entry *find_entry(struct hfs_mdb *mdb,
const struct hfs_cat_key *key)
{
struct list_head *tmp, *head = hash(mdb, key);
struct hfs_cat_entry * entry;
tmp = head;
for (;;) {
tmp = tmp->next;
entry = NULL;
if (tmp == head)
break;
entry = list_entry(tmp, struct hfs_cat_entry, hash);
if (entry->mdb != mdb)
continue;
if (hfs_cat_compare(&entry->key, key)) {
continue;
}
entry->count++;
break;
}
return entry;
}
/* be careful. this gets called with the spinlock held. */
static struct hfs_cat_entry *get_new_entry(struct hfs_mdb *mdb,
const struct hfs_cat_key *key,
const int read)
{
struct hfs_cat_entry *entry;
struct list_head *head = hash(mdb, key);
struct list_head *tmp;
add_new_entry:
tmp = entry_unused.next;
if ((tmp != &entry_unused) ) {
list_del(tmp);
entries_stat.nr_free_entries--;
entry = list_entry(tmp, struct hfs_cat_entry, list);
list_add(&entry->list, &entry_in_use);
list_add(&entry->hash, head);
entry->mdb = mdb;
entry->count = 1;
memcpy(&entry->key, key, sizeof(*key));
entry->state = HFS_LOCK;
spin_unlock(&entry_lock);
if (read) {
struct hfs_brec brec;
if (hfs_bfind(&brec, mdb->cat_tree,
HFS_BKEY(key), HFS_BFIND_READ_EQ)) {
/* uh oh. we failed to read the record.
* the entry doesn't actually exist. */
goto read_fail;
}
read_entry(entry, &brec);
/* error */
if (!entry->cnid) {
goto read_fail;
}
/* we don't have to acquire a spinlock here or
* below for the unlocking bits as we're the first
* user of this entry. */
entry->state &= ~HFS_LOCK;
hfs_wake_up(&entry->wait);
}
return entry;
}
/* try to allocate more entries. grow_entries() doesn't release
* the spinlock. */
if (grow_entries())
goto add_new_entry;
spin_unlock(&entry_lock);
return NULL;
read_fail:
/* short-cut hfs_cat_put by doing everything here. */
spin_lock(&entry_lock);
list_del(&entry->hash);
list_del(&entry->list);
init_entry(entry);
list_add(&entry->list, &entry_unused);
entries_stat.nr_free_entries++;
spin_unlock(&entry_lock);
return NULL;
}
/*
* get_entry()
*
* Try to return an entry for the indicated file or directory.
* If ('read' == 0) then no attempt will be made to read it from disk
* and a locked, but uninitialized, entry is returned.
*/
static struct hfs_cat_entry *get_entry(struct hfs_mdb *mdb,
const struct hfs_cat_key *key,
const int read)
{
struct hfs_cat_entry * entry;
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_get_entry: mdb=%p key=%s read=%d\n",
mdb, key->CName.Name, read);
#endif
spin_lock(&entry_lock);
entry = find_entry(mdb, key);
if (!entry) {
return get_new_entry(mdb, key, read);
}
spin_unlock(&entry_lock);
wait_on_entry(entry);
return entry;
}
/*
* new_cnid()
*
* Allocate a CNID to use for a new file or directory.
*/
static inline hfs_u32 new_cnid(struct hfs_mdb *mdb)
{
/* If the create succeeds then the mdb will get dirtied */
return htonl(mdb->next_id++);
}
/*
* update_dir()
*
* Update counts, times and dirt on a changed directory
*/
static void update_dir(struct hfs_mdb *mdb, struct hfs_cat_entry *dir,
int is_dir, int count)
{
/* update counts */
if (is_dir) {
mdb->dir_count += count;
dir->u.dir.dirs += count;
if (dir->cnid == htonl(HFS_ROOT_CNID)) {
mdb->root_dirs += count;
}
} else {
mdb->file_count += count;
dir->u.dir.files += count;
if (dir->cnid == htonl(HFS_ROOT_CNID)) {
mdb->root_files += count;
}
}
/* update times and dirt */
dir->modify_date = hfs_time();
hfs_cat_mark_dirty(dir);
}
/*
* Add a writer to dir, excluding readers.
*
* XXX: this is wrong. it allows a move to occur when a directory
* is being written to.
*/
static inline void start_write(struct hfs_cat_entry *dir)
{
if (dir->u.dir.readers || waitqueue_active(&dir->u.dir.read_wait)) {
hfs_sleep_on(&dir->u.dir.write_wait);
}
++dir->u.dir.writers;
}
/*
* Add a reader to dir, excluding writers.
*/
static inline void start_read(struct hfs_cat_entry *dir)
{
if (dir->u.dir.writers || waitqueue_active(&dir->u.dir.write_wait)) {
hfs_sleep_on(&dir->u.dir.read_wait);
}
++dir->u.dir.readers;
}
/*
* Remove a writer from dir, possibly admitting readers.
*/
static inline void end_write(struct hfs_cat_entry *dir)
{
if (!(--dir->u.dir.writers)) {
hfs_wake_up(&dir->u.dir.read_wait);
}
}
/*
* Remove a reader from dir, possibly admitting writers.
*/
static inline void end_read(struct hfs_cat_entry *dir)
{
if (!(--dir->u.dir.readers)) {
hfs_wake_up(&dir->u.dir.write_wait);
}
}
/*
* create_entry()
*
* Add a new file or directory to the catalog B-tree and
* return a (struct hfs_cat_entry) for it in '*result'.
*/
static int create_entry(struct hfs_cat_entry *parent, struct hfs_cat_key *key,
const struct hfs_cat_rec *record, int is_dir,
hfs_u32 cnid, struct hfs_cat_entry **result)
{
struct hfs_mdb *mdb = parent->mdb;
struct hfs_cat_entry *entry;
struct hfs_cat_key thd_key;
struct hfs_cat_rec thd_rec;
int error, has_thread;
if (result) {
*result = NULL;
}
/* keep readers from getting confused by changing dir size */
start_write(parent);
/* create a locked entry in the cache */
entry = get_entry(mdb, key, 0);
if (!entry) {
/* The entry exists but can't be read */
error = -EIO;
goto done;
}
if (entry->cnid) {
/* The (unlocked) entry exists in the cache */
error = -EEXIST;
goto bail2;
}
/* limit directory valence to signed 16-bit integer */
if ((parent->u.dir.dirs + parent->u.dir.files) >= HFS_MAX_VALENCE) {
error = -ENOSPC;
goto bail1;
}
has_thread = is_dir || (record->u.fil.Flags & HFS_FIL_THD);
if (has_thread) {
/* init some fields for the thread record */
memset(&thd_rec, 0, sizeof(thd_rec));
thd_rec.cdrType = is_dir ? HFS_CDR_THD : HFS_CDR_FTH;
memcpy(&thd_rec.u.thd.ParID, &key->ParID,
sizeof(hfs_u32) + sizeof(struct hfs_name));
/* insert the thread record */
hfs_cat_build_key(cnid, NULL, &thd_key);
error = hfs_binsert(mdb->cat_tree, HFS_BKEY(&thd_key),
&thd_rec, 2 + sizeof(THD_REC));
if (error) {
goto bail1;
}
}
/* insert the record */
error = hfs_binsert(mdb->cat_tree, HFS_BKEY(key), record,
is_dir ? 2 + sizeof(DIR_REC) :
2 + sizeof(FIL_REC));
if (error) {
if (has_thread && (error != -EIO)) {
/* at least TRY to remove the thread record */
(void)hfs_bdelete(mdb->cat_tree, HFS_BKEY(&thd_key));
}
goto bail1;
}
/* update the parent directory */
update_dir(mdb, parent, is_dir, 1);
/* complete the cache entry and return success */
__read_entry(entry, record);
unlock_entry(entry);
if (result) {
*result = entry;
} else {
hfs_cat_put(entry);
}
goto done;
bail1:
/* entry really didn't exist, so we don't need to really delete it.
* we do need to remove it from the hash, though. */
entry->state |= HFS_DELETED;
remove_hash(entry);
unlock_entry(entry);
bail2:
hfs_cat_put(entry);
done:
end_write(parent);
return error;
}
/*================ Global functions ================*/
/*
* hfs_cat_put()
*
* Release an entry we aren't using anymore.
*
* nothing in hfs_cat_put goes to sleep now except on the initial entry.
*/
void hfs_cat_put(struct hfs_cat_entry * entry)
{
if (entry) {
wait_on_entry(entry);
/* just in case. this should never happen. */
if (!entry->count) {
hfs_warn("hfs_cat_put: trying to free free entry: %p\n",
entry);
return;
}
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_cat_put: %p(%u) type=%d state=%lu\n",
entry, entry->count, entry->type, entry->state);
#endif
spin_lock(&entry_lock);
if (!--entry->count) {
if ((entry->state & HFS_DELETED))
goto entry_deleted;
if ((entry->type == HFS_CDR_FIL)) {
/* clear out any cached extents */
if (entry->u.file.data_fork.first.next) {
hfs_extent_free(&entry->u.file.data_fork);
}
if (entry->u.file.rsrc_fork.first.next) {
hfs_extent_free(&entry->u.file.rsrc_fork);
}
}
/* if we put a dirty entry, write it out. */
if ((entry->state & HFS_DIRTY)) {
entry->state ^= HFS_DIRTY | HFS_LOCK;
write_entry(entry);
entry->state &= ~HFS_LOCK;
}
list_del(&entry->hash);
entry_deleted: /* deleted entries have already been removed
* from the hash list. */
list_del(&entry->list);
if (entries_stat.nr_free_entries > CCACHE_MAX) {
HFS_DELETE(entry);
entries_stat.nr_entries--;
} else {
init_entry(entry);
list_add(&entry->list, &entry_unused);
entries_stat.nr_free_entries++;
}
}
spin_unlock(&entry_lock);
}
}
/*
* hfs_cat_get()
*
* Wrapper for get_entry() which always calls with ('read'==1).
* Used for access to get_entry() from outside this file.
*/
struct hfs_cat_entry *hfs_cat_get(struct hfs_mdb *mdb,
const struct hfs_cat_key *key)
{
return get_entry(mdb, key, 1);
}
/* invalidate all entries for a device */
static void invalidate_list(struct list_head *head, struct hfs_mdb *mdb,
struct list_head *dispose)
{
struct list_head *next;
next = head->next;
for (;;) {
struct list_head *tmp = next;
struct hfs_cat_entry * entry;
next = next->next;
if (tmp == head)
break;
entry = list_entry(tmp, struct hfs_cat_entry, list);
if (entry->mdb != mdb) {
continue;
}
if (!entry->count) {
list_del(&entry->hash);
INIT_LIST_HEAD(&entry->hash);
list_del(&entry->list);
list_add(&entry->list, dispose);
continue;
}
hfs_warn("hfs_fs: entry %p(%u) busy on removed device %s.\n",
entry, entry->count,
hfs_mdb_name(entry->mdb->sys_mdb));
}
}
/* delete entries from a list */
static void delete_list(struct list_head *head)
{
struct list_head *next = head->next;
struct hfs_cat_entry *entry;
for (;;) {
struct list_head * tmp = next;
next = next->next;
if (tmp == head) {
break;
}
entry = list_entry(tmp, struct hfs_cat_entry, list);
HFS_DELETE(entry);
}
}
/*
* hfs_cat_invalidate()
*
* Called by hfs_mdb_put() to remove all the entries
* in the cache that are associated with a given MDB.
*/
void hfs_cat_invalidate(struct hfs_mdb *mdb)
{
LIST_HEAD(throw_away);
spin_lock(&entry_lock);
invalidate_list(&entry_in_use, mdb, &throw_away);
invalidate_list(&mdb->entry_dirty, mdb, &throw_away);
spin_unlock(&entry_lock);
delete_list(&throw_away);
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_cat_invalidate: free=%d total=%d\n",
entries_stat.nr_free_entries,
entries_stat.nr_entries);
#endif
}
/*
* hfs_cat_commit()
*
* Called by hfs_mdb_commit() to write dirty entries to the disk buffers.
*/
void hfs_cat_commit(struct hfs_mdb *mdb)
{
struct list_head *tmp, *head = &mdb->entry_dirty;
struct hfs_cat_entry *entry;
spin_lock(&entry_lock);
while ((tmp = head->prev) != head) {
entry = list_entry(tmp, struct hfs_cat_entry, list);
if ((entry->state & HFS_LOCK)) {
spin_unlock(&entry_lock);
wait_on_entry(entry);
spin_lock(&entry_lock);
} else {
struct list_head *insert = &entry_in_use;
if (!entry->count)
insert = entry_in_use.prev;
/* add to in_use list */
list_del(&entry->list);
list_add(&entry->list, insert);
/* reset DIRTY, set LOCK */
entry->state ^= HFS_DIRTY | HFS_LOCK;
spin_unlock(&entry_lock);
write_entry(entry);
spin_lock(&entry_lock);
entry->state &= ~HFS_LOCK;
hfs_wake_up(&entry->wait);
}
}
spin_unlock(&entry_lock);
}
/*
* hfs_cat_free()
*
* Releases all the memory allocated in grow_entries().
* Must call hfs_cat_invalidate() on all MDBs before calling this.
* This only gets rid of the unused pool of entries. all the other
* entry references should have either been freed by cat_invalidate
* or moved onto the unused list.
*/
void hfs_cat_free(void)
{
delete_list(&entry_unused);
}
/*
* hfs_cat_compare()
*
* Description:
* This is the comparison function used for the catalog B-tree. In
* comparing catalog B-tree entries, the parent id is the most
* significant field (compared as unsigned ints). The name field is
* the least significant (compared in "Macintosh lexical order",
* see hfs_strcmp() in string.c)
* Input Variable(s):
* struct hfs_cat_key *key1: pointer to the first key to compare
* struct hfs_cat_key *key2: pointer to the second key to compare
* Output Variable(s):
* NONE
* Returns:
* int: negative if key1<key2, positive if key1>key2, and 0 if key1==key2
* Preconditions:
* key1 and key2 point to "valid" (struct hfs_cat_key)s.
* Postconditions:
* This function has no side-effects
*/
int hfs_cat_compare(const struct hfs_cat_key *key1,
const struct hfs_cat_key *key2)
{
unsigned int parents;
int retval;
parents = hfs_get_hl(key1->ParID) - hfs_get_hl(key2->ParID);
if (parents != 0) {
retval = (int)parents;
} else {
retval = hfs_strcmp(key1->CName.Name, key1->CName.Len,
key2->CName.Name, key2->CName.Len);
}
return retval;
}
/*
* hfs_cat_build_key()
*
* Given the ID of the parent and the name build a search key.
*/
void hfs_cat_build_key(hfs_u32 parent, const struct hfs_name *cname,
struct hfs_cat_key *key)
{
hfs_put_nl(parent, key->ParID);
if (cname) {
key->KeyLen = 6 + cname->Len;
memcpy(&key->CName, cname, sizeof(*cname));
} else {
key->KeyLen = 6;
memset(&key->CName, 0, sizeof(*cname));
}
}
/*
* hfs_cat_open()
*
* Given a directory on an HFS filesystem get its thread and
* lock the directory against insertions and deletions.
* Return 0 on success or an error code on failure.
*/
int hfs_cat_open(struct hfs_cat_entry *dir, struct hfs_brec *brec)
{
struct hfs_cat_key key;
int error;
if (dir->type != HFS_CDR_DIR) {
return -EINVAL;
}
/* Block writers */
start_read(dir);
/* Find the directory */
hfs_cat_build_key(dir->cnid, NULL, &key);
error = hfs_bfind(brec, dir->mdb->cat_tree,
HFS_BKEY(&key), HFS_BFIND_READ_EQ);
if (error) {
end_read(dir);
}
return error;
}
/*
* hfs_cat_next()
*
* Given a catalog brec structure, replace it with the count'th next brec
* in the same directory.
* Return an error code if there is a problem, 0 if OK.
* Note that an error code of -ENOENT means there are no more entries
* in this directory.
* The directory is "closed" on an error.
*/
int hfs_cat_next(struct hfs_cat_entry *dir, struct hfs_brec *brec,
hfs_u16 count, hfs_u32 *cnid, hfs_u8 *type)
{
int error;
if (!dir || !brec) {
return -EINVAL;
}
/* Get the count'th next catalog tree entry */
error = hfs_bsucc(brec, count);
if (!error) {
struct hfs_cat_key *key = (struct hfs_cat_key *)brec->key;
if (hfs_get_nl(key->ParID) != dir->cnid) {
hfs_brec_relse(brec, NULL);
error = -ENOENT;
}
}
if (!error) {
*type = ((struct hfs_cat_rec *)brec->data)->cdrType;
*cnid = brec_to_id(brec);
} else {
end_read(dir);
}
return error;
}
/*
* hfs_cat_close()
*
* Given a catalog brec structure, replace it with the count'th next brec
* in the same directory.
* Return an error code if there is a problem, 0 if OK.
* Note that an error code of -ENOENT means there are no more entries
* in this directory.
*/
void hfs_cat_close(struct hfs_cat_entry *dir, struct hfs_brec *brec)
{
if (dir && brec) {
hfs_brec_relse(brec, NULL);
end_read(dir);
}
}
/*
* hfs_cat_parent()
*
* Given a catalog entry, return the entry for its parent.
* Uses catalog key for the entry to get its parent's ID
* and then uses the parent's thread record to locate the
* parent's actual catalog entry.
*/
struct hfs_cat_entry *hfs_cat_parent(struct hfs_cat_entry *entry)
{
struct hfs_cat_entry *retval = NULL;
struct hfs_mdb *mdb = entry->mdb;
struct hfs_brec brec;
struct hfs_cat_key key;
int error;
lock_entry(entry);
if (!(entry->state & HFS_DELETED)) {
hfs_cat_build_key(hfs_get_nl(entry->key.ParID), NULL, &key);
error = hfs_bfind(&brec, mdb->cat_tree,
HFS_BKEY(&key), HFS_BFIND_READ_EQ);
if (!error) {
/* convert thread record to key */
struct hfs_cat_rec *rec = brec.data;
key.KeyLen = 6 + rec->u.thd.CName.Len;
memcpy(&key.ParID, &rec->u.thd.ParID,
sizeof(hfs_u32) + sizeof(struct hfs_name));
hfs_brec_relse(&brec, NULL);
retval = hfs_cat_get(mdb, &key);
}
}
unlock_entry(entry);
return retval;
}
/*
* hfs_cat_create()
*
* Create a new file with the indicated name in the indicated directory.
* The file will have the indicated flags, type and creator.
* If successful an (struct hfs_cat_entry) is returned in '*result'.
*/
int hfs_cat_create(struct hfs_cat_entry *parent, struct hfs_cat_key *key,
hfs_u8 flags, hfs_u32 type, hfs_u32 creator,
struct hfs_cat_entry **result)
{
struct hfs_cat_rec record;
hfs_u32 id = new_cnid(parent->mdb);
hfs_u32 mtime = hfs_time();
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_cat_create: %p/%s flags=%d res=%p\n",
parent, key->CName.Name, flags, result);
#endif
/* init some fields for the file record */
memset(&record, 0, sizeof(record));
record.cdrType = HFS_CDR_FIL;
record.u.fil.Flags = flags | HFS_FIL_USED;
hfs_put_nl(id, record.u.fil.FlNum);
hfs_put_nl(mtime, record.u.fil.CrDat);
hfs_put_nl(mtime, record.u.fil.MdDat);
hfs_put_nl(0, record.u.fil.BkDat);
hfs_put_nl(type, record.u.fil.UsrWds.fdType);
hfs_put_nl(creator, record.u.fil.UsrWds.fdCreator);
return create_entry(parent, key, &record, 0, id, result);
}
/*
* hfs_cat_mkdir()
*
* Create a new directory with the indicated name in the indicated directory.
* If successful an (struct hfs_cat_entry) is returned in '*result'.
*/
int hfs_cat_mkdir(struct hfs_cat_entry *parent, struct hfs_cat_key *key,
struct hfs_cat_entry **result)
{
struct hfs_cat_rec record;
hfs_u32 id = new_cnid(parent->mdb);
hfs_u32 mtime = hfs_time();
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_cat_mkdir: %p/%s res=%p\n", parent, key->CName.Name,
result);
#endif
/* init some fields for the directory record */
memset(&record, 0, sizeof(record));
record.cdrType = HFS_CDR_DIR;
hfs_put_nl(id, record.u.dir.DirID);
hfs_put_nl(mtime, record.u.dir.CrDat);
hfs_put_nl(mtime, record.u.dir.MdDat);
hfs_put_nl(0, record.u.dir.BkDat);
hfs_put_hs(0xff, record.u.dir.UsrInfo.frView);
return create_entry(parent, key, &record, 1, id, result);
}
/*
* hfs_cat_delete()
*
* Delete the indicated file or directory.
* The associated thread is also removed unless ('with_thread'==0).
*/
int hfs_cat_delete(struct hfs_cat_entry *parent, struct hfs_cat_entry *entry,
int with_thread)
{
struct hfs_cat_key key;
struct hfs_mdb *mdb = parent->mdb;
int is_dir, error = 0;
#if defined(DEBUG_CATALOG) || defined(DEBUG_ALL)
hfs_warn("hfs_cat_delete: %p/%p type=%d state=%lu, thread=%d\n",
parent, entry, entry->type, entry->state, with_thread);
#endif
if (parent->mdb != entry->mdb) {
return -EINVAL;
}
if (entry->type == HFS_CDR_FIL) {
with_thread = (entry->u.file.flags&HFS_FIL_THD) && with_thread;
is_dir = 0;
} else {
is_dir = 1;
}
/* keep readers from getting confused by changing dir size */
start_write(parent);
/* don't delete a busy directory */
if (entry->type == HFS_CDR_DIR) {
start_read(entry);
error = -ENOTEMPTY;
if (entry->u.dir.files || entry->u.dir.dirs)
goto hfs_delete_end;
}
/* try to delete the file or directory */
lock_entry(entry);
error = -ENOENT;
if ((entry->state & HFS_DELETED)) {
/* somebody beat us to it. */
goto hfs_delete_unlock;
}
/* delete the catalog record */
if ((error = hfs_bdelete(mdb->cat_tree, HFS_BKEY(&entry->key)))) {
goto hfs_delete_unlock;
}
/* Mark the entry deleted and remove it from the cache */
delete_entry(entry);
/* try to delete the thread entry if it exists */
if (with_thread) {
hfs_cat_build_key(entry->cnid, NULL, &key);
(void)hfs_bdelete(mdb->cat_tree, HFS_BKEY(&key));
}
update_dir(mdb, parent, is_dir, -1);
hfs_delete_unlock:
unlock_entry(entry);
hfs_delete_end:
if (entry->type == HFS_CDR_DIR) {
end_read(entry);
}
end_write(parent);
return error;
}
/*
* hfs_cat_move()
*
* Rename a file or directory, possibly to a new directory.
* If the destination exists it is removed and a
* (struct hfs_cat_entry) for it is returned in '*result'.
*/
int hfs_cat_move(struct hfs_cat_entry *old_dir, struct hfs_cat_entry *new_dir,
struct hfs_cat_entry *entry, struct hfs_cat_key *new_key,
struct hfs_cat_entry **removed)
{
struct hfs_cat_entry *dest;
struct hfs_mdb *mdb;
int error = 0;
int is_dir, has_thread;
if (removed) {
*removed = NULL;
}
/* sanity checks */
if (!old_dir || !new_dir) {
return -EINVAL;
}
mdb = old_dir->mdb;
if (mdb != new_dir->mdb) {
return -EXDEV;
}
/* precompute a few things */
if (entry->type == HFS_CDR_DIR) {
is_dir = 1;
has_thread = 1;
} else if (entry->type == HFS_CDR_FIL) {
is_dir = 0;
has_thread = entry->u.file.flags & HFS_FIL_THD;
} else {
return -EINVAL;
}
/* keep readers from getting confused by changing dir size */
start_write(new_dir);
if (old_dir != new_dir) {
start_write(old_dir);
}
/* Don't move a directory inside itself */
if (is_dir) {
struct hfs_cat_key thd_key;
struct hfs_brec brec;
hfs_u32 id = new_dir->cnid;
while (id != htonl(HFS_ROOT_CNID)) {
if (id == entry->cnid) {
error = -EINVAL;
} else {
hfs_cat_build_key(id, NULL, &thd_key);
error = hfs_bfind(&brec, mdb->cat_tree,
HFS_BKEY(&thd_key),
HFS_BFIND_READ_EQ);
}
if (error) {
goto done;
} else {
struct hfs_cat_rec *rec = brec.data;
id = hfs_get_nl(rec->u.thd.ParID);
hfs_brec_relse(&brec, NULL);
}
}
}
restart:
/* see if the destination exists, getting it if it does */
dest = hfs_cat_get(mdb, new_key);
if (!dest) {
/* destination doesn't exist, so create it */
struct hfs_cat_rec new_record;
/* create a locked entry in the cache */
dest = get_entry(mdb, new_key, 0);
if (!dest) {
error = -EIO;
goto done;
}
if (dest->cnid) {
/* The (unlocked) entry exists in the cache */
goto have_distinct;
}
/* limit directory valence to signed 16-bit integer */
if ((new_dir->u.dir.dirs + new_dir->u.dir.files) >=
HFS_MAX_VALENCE) {
error = -ENOSPC;
goto bail3;
}
/* build the new record. make sure to zero out the
record. */
memset(&new_record, 0, sizeof(new_record));
new_record.cdrType = entry->type;
__write_entry(entry, &new_record);
/* insert the new record */
error = hfs_binsert(mdb->cat_tree, HFS_BKEY(new_key),
&new_record, is_dir ? 2 + sizeof(DIR_REC) :
2 + sizeof(FIL_REC));
if (error == -EEXIST) {
delete_entry(dest);
unlock_entry(dest);
hfs_cat_put(dest);
goto restart;
} else if (error) {
goto bail3;
}
/* update the destination directory */
update_dir(mdb, new_dir, is_dir, 1);
} else if (entry != dest) {
have_distinct:
/* The destination exists and is not same as source */
lock_entry(dest);
if ((dest->state & HFS_DELETED)) {
unlock_entry(dest);
hfs_cat_put(dest);
goto restart;
}
if (dest->type != entry->type) {
/* can't move a file on top
of a dir nor vice versa. */
error = is_dir ? -ENOTDIR : -EISDIR;
} else if (is_dir && (dest->u.dir.dirs || dest->u.dir.files)) {
/* directory to replace is not empty */
error = -ENOTEMPTY;
}
if (error) {
goto bail2;
}
} else {
/* The destination exists but is same as source */
--entry->count;
dest = NULL;
}
/* lock the entry */
lock_entry(entry);
if ((entry->state & HFS_DELETED)) {
error = -ENOENT;
goto bail1;
}
if (dest) {
/* remove the old entry */
error = hfs_bdelete(mdb->cat_tree, HFS_BKEY(&entry->key));
if (error) {
/* We couldn't remove the entry for the
original file, so nothing has changed. */
goto bail1;
}
update_dir(mdb, old_dir, is_dir, -1);
}
/* update the thread of the dir/file we're moving */
if (has_thread) {
struct hfs_cat_key thd_key;
struct hfs_brec brec;
hfs_cat_build_key(entry->cnid, NULL, &thd_key);
error = hfs_bfind(&brec, mdb->cat_tree,
HFS_BKEY(&thd_key), HFS_BFIND_WRITE);
if (error == -ENOENT) {
if (is_dir) {
/* directory w/o a thread! */
error = -EIO;
} else {
/* We were lied to! */
entry->u.file.flags &= ~HFS_FIL_THD;
hfs_cat_mark_dirty(entry);
}
}
if (!error) {
struct hfs_cat_rec *rec = brec.data;
memcpy(&rec->u.thd.ParID, &new_key->ParID,
sizeof(hfs_u32) + sizeof(struct hfs_name));
hfs_brec_relse(&brec, NULL);
} else if (error == -ENOENT) {
error = 0;
} else if (!dest) {
/* Nothing was changed */
unlock_entry(entry);
goto done;
} else {
/* Something went seriously wrong.
The dir/file has been deleted. */
/* XXX try some recovery? */
delete_entry(entry);
goto bail1;
}
}
/* TRY to remove the thread for the pre-existing entry */
if (dest && dest->cnid &&
(is_dir || (dest->u.file.flags & HFS_FIL_THD))) {
struct hfs_cat_key thd_key;
hfs_cat_build_key(dest->cnid, NULL, &thd_key);
(void)hfs_bdelete(mdb->cat_tree, HFS_BKEY(&thd_key));
}
/* update directories */
new_dir->modify_date = hfs_time();
hfs_cat_mark_dirty(new_dir);
/* update key */
remove_hash(entry);
memcpy(&entry->key, new_key, sizeof(*new_key));
/* KEYDIRTY as case might differ */
entry->state |= HFS_KEYDIRTY;
insert_hash(entry);
hfs_cat_mark_dirty(entry);
unlock_entry(entry);
/* delete any pre-existing or place-holder entry */
if (dest) {
delete_entry(dest);
unlock_entry(dest);
if (removed && dest->cnid) {
*removed = dest;
} else {
hfs_cat_put(dest);
}
}
goto done;
bail1:
unlock_entry(entry);
bail2:
if (dest) {
if (!dest->cnid) {
/* TRY to remove the new entry */
(void)hfs_bdelete(mdb->cat_tree, HFS_BKEY(new_key));
update_dir(mdb, new_dir, is_dir, -1);
bail3:
delete_entry(dest);
}
unlock_entry(dest);
hfs_cat_put(dest);
}
done:
if (new_dir != old_dir) {
end_write(old_dir);
}
end_write(new_dir);
return error;
}
/*
* Initialize the hash tables
*/
void hfs_cat_init(void)
{
int i;
struct list_head *head = hash_table;
i = C_HASHSIZE;
do {
INIT_LIST_HEAD(head);
head++;
i--;
} while (i);
}