Пример #1
0
/*
 * init_file_inode()
 *
 * Given an HFS catalog entry initialize an inode for a file.
 */
static void init_file_inode(struct inode *inode, hfs_u8 fork)
{
    struct hfs_fork *fk;
    struct hfs_cat_entry *entry = HFS_I(inode)->entry;

    if (fork == HFS_FK_DATA) {
        inode->i_mode = S_IRWXUGO | S_IFREG;
    } else {
        inode->i_mode = S_IRUGO | S_IWUGO | S_IFREG;
    }

    if (fork == HFS_FK_DATA) {
#if 0 /* XXX: disable crlf translations for now */
        hfs_u32 type = hfs_get_nl(entry->info.file.finfo.fdType);

        HFS_I(inode)->convert =
            ((HFS_SB(inode->i_sb)->s_conv == 't') ||
             ((HFS_SB(inode->i_sb)->s_conv == 'a') &&
              ((type == htonl(0x54455854)) ||   /* "TEXT" */
               (type == htonl(0x7474726f)))));  /* "ttro" */
#else
        HFS_I(inode)->convert = 0;
#endif
        fk = &entry->u.file.data_fork;
    } else {
        fk = &entry->u.file.rsrc_fork;
        HFS_I(inode)->convert = 0;
    }
    HFS_I(inode)->fork = fk;
    inode->i_size = fk->lsize;
    inode->i_blocks = fk->psize;
    inode->i_nlink = 1;
}
Пример #2
0
/*
 * parse_old_part_table()
 *
 * Parse a old style partition map looking for the
 * start and length of the 'part'th HFS partition.
 */
static int parse_old_part_table(hfs_sysmdb sys_mdb, hfs_buffer buf,
				int part, hfs_s32 *size, hfs_s32 *start)
{
	struct old_pmap *pm = (struct old_pmap *)hfs_buffer_data(buf);
	struct old_pmap_entry *p = &pm->pdEntry[0];
	int hfs_part = 0;

	while ((p->pdStart || p->pdSize || p->pdFSID) && !(*start)) {
		/* look for an HFS partition */
		if ((hfs_get_nl(p->pdFSID) == htonl(0x54465331)/*"TFS1"*/) &&
		    ((hfs_part++) == part)) {
			/* Found it! */
			*start = hfs_get_hl(p->pdStart);
			*size = hfs_get_hl(p->pdSize);
		}
		++p;
	}
	hfs_buffer_put(buf);

	return 0;
}
Пример #3
0
/*
 * cap_readdir()
 *
 * This is the readdir() entry in the file_operations structure for
 * HFS directories in the CAP scheme.  The purpose is to enumerate the
 * entries in a directory, given the inode of the directory and a
 * (struct file *), the 'f_pos' field of which indicates the location
 * in the directory.  The (struct file *) is updated so that the next
 * call with the same 'dir' and 'filp' arguments will produce the next
 * directory entry.  The entries are returned in 'dirent', which is
 * "filled-in" by calling filldir().  This allows the same readdir()
 * function be used for different dirent formats.  We try to read in
 * as many entries as we can before filldir() refuses to take any more.
 *
 * XXX: In the future it may be a good idea to consider not generating
 * metadata files for covered directories since the data doesn't
 * correspond to the mounted directory.	 However this requires an
 * iget() for every directory which could be considered an excessive
 * amount of overhead.	Since the inode for a mount point is always
 * in-core this is another argument for a call to get an inode if it
 * is in-core or NULL if it is not.
 */
static int cap_readdir(struct file * filp,
		       void * dirent, filldir_t filldir)
{
	ino_t type;
	int skip_dirs;
	struct hfs_brec brec;
        struct hfs_cat_entry *entry;
	struct inode *dir = filp->f_dentry->d_inode;

	entry = HFS_I(dir)->entry;
	type = HFS_ITYPE(dir->i_ino);
	skip_dirs = (type == HFS_CAP_RDIR);

	if (filp->f_pos == 0) {
		/* Entry 0 is for "." */
		if (filldir(dirent, DOT->Name, DOT_LEN, 0, dir->i_ino, DT_DIR)) {
			return 0;
		}
		filp->f_pos = 1;
	}

	if (filp->f_pos == 1) {
		/* Entry 1 is for ".." */
		hfs_u32 cnid;

		if (type == HFS_CAP_NDIR) {
			cnid = hfs_get_nl(entry->key.ParID);
		} else {
			cnid = entry->cnid;
		}

		if (filldir(dirent, DOT_DOT->Name,
			    DOT_DOT_LEN, 1, ntohl(cnid), DT_DIR)) {
			return 0;
		}
		filp->f_pos = 2;
	}

	if (filp->f_pos < (dir->i_size - 3)) {
		hfs_u32 cnid;
		hfs_u8 type;

	    	if (hfs_cat_open(entry, &brec) ||
	    	    hfs_cat_next(entry, &brec, filp->f_pos - 2, &cnid, &type)) {
			return 0;
		}
		while (filp->f_pos < (dir->i_size - 3)) {
			if (hfs_cat_next(entry, &brec, 1, &cnid, &type)) {
				return 0;
			}
			if (!skip_dirs || (type != HFS_CDR_DIR)) {
				ino_t ino;
				unsigned int len;
				unsigned char tmp_name[HFS_NAMEMAX];

				ino = ntohl(cnid) | HFS_I(dir)->file_type;
				len = hfs_namein(dir, tmp_name,
				    &((struct hfs_cat_key *)brec.key)->CName);
				if (filldir(dirent, tmp_name, len,
					    filp->f_pos, ino, DT_UNKNOWN)) {
					hfs_cat_close(entry, &brec);
					return 0;
				}
			}
			++filp->f_pos;
		}
		hfs_cat_close(entry, &brec);
	}

	if (filp->f_pos == (dir->i_size - 3)) {
		if ((entry->cnid == htonl(HFS_ROOT_CNID)) &&
		    (type == HFS_CAP_NDIR)) {
			/* In root dir last-2 entry is for ".rootinfo" */
			if (filldir(dirent, DOT_ROOTINFO->Name,
				    DOT_ROOTINFO_LEN, filp->f_pos,
				    ntohl(entry->cnid) | HFS_CAP_FNDR,
				    DT_UNKNOWN)) {
				return 0;
			}
		}
		++filp->f_pos;
	}

	if (filp->f_pos == (dir->i_size - 2)) {
		if (type == HFS_CAP_NDIR) {
			/* In normal dirs last-1 entry is for ".finderinfo" */
			if (filldir(dirent, DOT_FINDERINFO->Name,
				    DOT_FINDERINFO_LEN, filp->f_pos,
				    ntohl(entry->cnid) | HFS_CAP_FDIR,
				    DT_UNKNOWN)) {
				return 0;
			}
		}
		++filp->f_pos;
	}

	if (filp->f_pos == (dir->i_size - 1)) {
		if (type == HFS_CAP_NDIR) {
			/* In normal dirs last entry is for ".resource" */
			if (filldir(dirent, DOT_RESOURCE->Name,
				    DOT_RESOURCE_LEN, filp->f_pos,
				    ntohl(entry->cnid) | HFS_CAP_RDIR,
				    DT_UNKNOWN)) {
				return 0;
			}
		}
		++filp->f_pos;
	}

	return 0;
}
Пример #4
0
/*
 * parse_options()
 * 
 * adapted from linux/fs/msdos/inode.c written 1992,93 by Werner Almesberger
 * This function is called by hfs_read_super() to parse the mount options.
 */
static int parse_options(char *options, struct hfs_sb_info *hsb, int *part)
{
	char *this_char, *value;
	char names, fork;

	/* initialize the sb with defaults */
	memset(hsb, 0, sizeof(*hsb));
	hsb->magic = HFS_SB_MAGIC;
	hsb->s_uid   = current->uid;
	hsb->s_gid   = current->gid;
	hsb->s_umask = current->fs->umask;
	hsb->s_type    = 0x3f3f3f3f;	/* == '????' */
	hsb->s_creator = 0x3f3f3f3f;	/* == '????' */
	hsb->s_lowercase = 0;
	hsb->s_quiet     = 0;
	hsb->s_afpd      = 0;
        /* default version. 0 just selects the defaults */
	hsb->s_version   = 0; 
	hsb->s_conv = 'b';
	names = '?';
	fork = '?';
	*part = 0;

	if (!options) {
		goto done;
	}
	for (this_char = strtok(options,","); this_char;
	     this_char = strtok(NULL,",")) {
		if ((value = strchr(this_char,'=')) != NULL) {
			*value++ = 0;
		}
	/* Numeric-valued options */
		if (!strcmp(this_char, "version")) {
			if (!value || !*value) {
				return 0;
			}
			hsb->s_version = simple_strtoul(value,&value,0);
			if (*value) {
				return 0;
			}
		} else if (!strcmp(this_char,"uid")) {
			if (!value || !*value) {
				return 0;
			}
			hsb->s_uid = simple_strtoul(value,&value,0);
			if (*value) {
				return 0;
			}
		} else if (!strcmp(this_char,"gid")) {
			if (!value || !*value) {
				return 0;
			}
			hsb->s_gid = simple_strtoul(value,&value,0);
			if (*value) {
				return 0;
			}
		} else if (!strcmp(this_char,"umask")) {
			if (!value || !*value) {
				return 0;
			}
			hsb->s_umask = simple_strtoul(value,&value,8);
			if (*value) {
				return 0;
			}
		} else if (!strcmp(this_char,"part")) {
			if (!value || !*value) {
				return 0;
			}
			*part = simple_strtoul(value,&value,0);
			if (*value) {
				return 0;
			}
	/* String-valued options */
		} else if (!strcmp(this_char,"type") && value) {
			if (strlen(value) != 4) {
				return 0;
			}
			hsb->s_type = hfs_get_nl(value);
		} else if (!strcmp(this_char,"creator") && value) {
			if (strlen(value) != 4) {
				return 0;
			}
			hsb->s_creator = hfs_get_nl(value);
	/* Boolean-valued options */
		} else if (!strcmp(this_char,"quiet")) {
			if (value) {
				return 0;
			}
			hsb->s_quiet = 1;
		} else if (!strcmp(this_char,"afpd")) {
			if (value) {
				return 0;
			}
			hsb->s_afpd = 1;
	/* Multiple choice options */
		} else if (!strcmp(this_char,"names") && value) {
			if ((*value && !value[1] && strchr("ntal78c",*value)) ||
			    !strcmp(value,"netatalk") ||
			    !strcmp(value,"trivial") ||
			    !strcmp(value,"alpha") ||
			    !strcmp(value,"latin") ||
			    !strcmp(value,"7bit") ||
			    !strcmp(value,"8bit") ||
			    !strcmp(value,"cap")) {
				names = *value;
			} else {
				return 0;
			}
		} else if (!strcmp(this_char,"fork") && value) {
			if ((*value && !value[1] && strchr("nsdc",*value)) ||
			    !strcmp(value,"netatalk") ||
			    !strcmp(value,"single") ||
			    !strcmp(value,"double") ||
			    !strcmp(value,"cap")) {
				fork = *value;
			} else {
				return 0;
			}
		} else if (!strcmp(this_char,"case") && value) {
			if ((*value && !value[1] && strchr("la",*value)) ||
			    !strcmp(value,"lower") ||
			    !strcmp(value,"asis")) {
				hsb->s_lowercase = (*value == 'l');
			} else {
				return 0;
			}
		} else if (!strcmp(this_char,"conv") && value) {
			if ((*value && !value[1] && strchr("bta",*value)) ||
			    !strcmp(value,"binary") ||
			    !strcmp(value,"text") ||
			    !strcmp(value,"auto")) {
				hsb->s_conv = *value;
			} else {
				return 0;
			}
		} else {
			return 0;
		}
	}

done:
	/* Parse the "fork" and "names" options */
	if (fork == '?') {
		fork = hsb->s_afpd ? 'n' : 'c';
	}
	switch (fork) {
	default:
	case 'c':
		hsb->s_ifill = hfs_cap_ifill;
		hsb->s_reserved1 = hfs_cap_reserved1;
		hsb->s_reserved2 = hfs_cap_reserved2;
		break;

	case 's':
		hfs_warn("hfs_fs: AppleSingle not yet implemented.\n");
		return 0;
		/* break; */
	
	case 'd':
		hsb->s_ifill = hfs_dbl_ifill;
		hsb->s_reserved1 = hfs_dbl_reserved1;
		hsb->s_reserved2 = hfs_dbl_reserved2;
		break;

	case 'n':
		hsb->s_ifill = hfs_nat_ifill;
		hsb->s_reserved1 = hfs_nat_reserved1;
		hsb->s_reserved2 = hfs_nat_reserved2;
		break;
	}

	if (names == '?') {
		names = fork;
	}
	switch (names) {
	default:
	case 'n':
		hsb->s_nameout = hfs_colon2mac;
		hsb->s_namein = hfs_mac2nat;
		break;

	case 'c':
		hsb->s_nameout = hfs_colon2mac;
		hsb->s_namein = hfs_mac2cap;
		break;

	case 't':
		hsb->s_nameout = hfs_triv2mac;
		hsb->s_namein = hfs_mac2triv;
		break;

	case '7':
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2seven;
		break;

	case '8':
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2eight;
		break;

	case 'l':
		hsb->s_nameout = hfs_latin2mac;
		hsb->s_namein = hfs_mac2latin;
		break;

 	case 'a':	/* 's' and 'd' are unadvertised aliases for 'alpha', */
 	case 's':	/* since 'alpha' is the default if fork=s or fork=d. */
 	case 'd':	/* (It is also helpful for poor typists!)           */
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2alpha;
		break;
	}

	return 1;
}
Пример #5
0
/*
 * parse_options()
 * 
 * adapted from linux/fs/msdos/inode.c written 1992,93 by Werner Almesberger
 * This function is called by hfs_read_super() to parse the mount options.
 */
static int parse_options(char *options, struct hfs_sb_info *hsb, int *part)
{
	char *p;
	char names, fork;
	substring_t args[MAX_OPT_ARGS];
	int option;

	/* initialize the sb with defaults */
	memset(hsb, 0, sizeof(*hsb));
	hsb->magic = HFS_SB_MAGIC;
	hsb->s_uid   = current->uid;
	hsb->s_gid   = current->gid;
	hsb->s_umask = current->fs->umask;
	hsb->s_type    = 0x3f3f3f3f;	/* == '????' */
	hsb->s_creator = 0x3f3f3f3f;	/* == '????' */
	hsb->s_lowercase = 0;
	hsb->s_quiet     = 0;
	hsb->s_afpd      = 0;
        /* default version. 0 just selects the defaults */
	hsb->s_version   = 0; 
	hsb->s_conv = 'b';
	names = '?';
	fork = '?';
	*part = 0;

	if (!options) {
		goto done;
	}
	while ((p = strsep(&options,",")) != NULL) {
		int token;
		if (!*p)
			continue;

		token = match_token(p, tokens, args);
		switch (token) {
		/* Numeric-valued options */
		case Opt_version:
			if (match_int(&args[0], &option))
				return 0;
			hsb->s_version = option;
			break;
		case Opt_uid:
			if (match_int(&args[0], &option))
				return 0;
			hsb->s_uid = option;
			break;
		case Opt_gid:
			if (match_int(&args[0], &option))
				return 0;
			hsb->s_gid = option;
			break;
		case Opt_umask:
			if (match_octal(&args[0], &option))
				return 0;
			hsb->s_umask = option;
			break;
		case Opt_part:
			if (match_int(&args[0], &option))
				return 0;
			*part = option;
			break;
		/* String-valued options */
		case Opt_type:
			if (strlen(args[0].from) != 4) {
				return 0;
			}
			hsb->s_type = hfs_get_nl(args[0].from);
			break;
		case Opt_creator:
			if (strlen(args[0].from) != 4) {
				return 0;
			}
			hsb->s_creator = hfs_get_nl(args[0].from);
			break;
		/* Boolean-valued options */
		case Opt_quiet:
			hsb->s_quiet = 1;
			break;
		case Opt_afpd:
			hsb->s_afpd = 1;
			break;
		/* Multiple choice options */
		case Opt_names_netatalk:
			names = 'n';
			break;
		case Opt_names_trivial:
			names = 't';
			break;
		case Opt_names_alpha:
			names = 'a';
			break;
		case Opt_names_latin:
			names = 'l';
			break;
		case Opt_names_7bit:
			names = '7';
			break;
		case Opt_names_8bit:
			names = '8';
			break;
		case Opt_names_cap:
			names = 'c';
			break;
		case Opt_fork_netatalk:
			fork = 'n';
			break;
		case Opt_fork_single:
			fork = 's';
			break;
		case Opt_fork_double:
			fork = 'd';
			break;
		case Opt_fork_cap:
			fork = 'c';
			break;
		case Opt_case_lower:
			hsb->s_lowercase = 1;
			break;
		case Opt_case_asis:
			hsb->s_lowercase = 0;
			break;
		case Opt_conv_binary:
			hsb->s_conv = 'b';
			break;
		case Opt_conv_text:
			hsb->s_conv = 't';
			break;
		case Opt_conv_auto:
			hsb->s_conv = 'a';
			break;
		default:
			return 0;
		}
	}

done:
	/* Parse the "fork" and "names" options */
	if (fork == '?') {
		fork = hsb->s_afpd ? 'n' : 'c';
	}
	switch (fork) {
	default:
	case 'c':
		hsb->s_ifill = hfs_cap_ifill;
		hsb->s_reserved1 = hfs_cap_reserved1;
		hsb->s_reserved2 = hfs_cap_reserved2;
		break;

	case 's':
		hfs_warn("hfs_fs: AppleSingle not yet implemented.\n");
		return 0;
		/* break; */
	
	case 'd':
		hsb->s_ifill = hfs_dbl_ifill;
		hsb->s_reserved1 = hfs_dbl_reserved1;
		hsb->s_reserved2 = hfs_dbl_reserved2;
		break;

	case 'n':
		hsb->s_ifill = hfs_nat_ifill;
		hsb->s_reserved1 = hfs_nat_reserved1;
		hsb->s_reserved2 = hfs_nat_reserved2;
		break;
	}

	if (names == '?') {
		names = fork;
	}
	switch (names) {
	default:
	case 'n':
		hsb->s_nameout = hfs_colon2mac;
		hsb->s_namein = hfs_mac2nat;
		break;

	case 'c':
		hsb->s_nameout = hfs_colon2mac;
		hsb->s_namein = hfs_mac2cap;
		break;

	case 't':
		hsb->s_nameout = hfs_triv2mac;
		hsb->s_namein = hfs_mac2triv;
		break;

	case '7':
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2seven;
		break;

	case '8':
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2eight;
		break;

	case 'l':
		hsb->s_nameout = hfs_latin2mac;
		hsb->s_namein = hfs_mac2latin;
		break;

 	case 'a':	/* 's' and 'd' are unadvertised aliases for 'alpha', */
 	case 's':	/* since 'alpha' is the default if fork=s or fork=d. */
 	case 'd':	/* (It is also helpful for poor typists!)           */
		hsb->s_nameout = hfs_prcnt2mac;
		hsb->s_namein = hfs_mac2alpha;
		break;
	}

	return 1;
}
Пример #6
0
/*
 * nat_readdir()
 *
 * This is the readdir() entry in the file_operations structure for
 * HFS directories in the netatalk scheme.  The purpose is to
 * enumerate the entries in a directory, given the inode of the
 * directory and a struct file which indicates the location in the
 * directory.  The struct file is updated so that the next call with
 * the same dir and filp will produce the next directory entry.	 The
 * entries are returned in dirent, which is "filled-in" by calling
 * filldir().  This allows the same readdir() function be used for
 * different dirent formats.  We try to read in as many entries as we
 * can before filldir() refuses to take any more.
 *
 * Note that the Netatalk format doesn't have the problem with
 * metadata for covered directories that exists in the other formats,
 * since the metadata is contained within the directory.
 */
static int nat_readdir(struct inode * dir, struct file * filp,
		       void * dirent, filldir_t filldir)
{
	ino_t type;
	int skip_dirs;
	struct hfs_brec brec;
        struct hfs_cat_entry *entry;

	if (!dir || !dir->i_sb || !S_ISDIR(dir->i_mode)) {
		return -EBADF;
	}

        entry = HFS_I(dir)->entry;
	type = HFS_ITYPE(dir->i_ino);
	skip_dirs = (type == HFS_NAT_HDIR);

	if (filp->f_pos == 0) {
		/* Entry 0 is for "." */
		if (filldir(dirent, DOT->Name, DOT_LEN, 0, dir->i_ino)) {
			return 0;
		}
		filp->f_pos = 1;
	}

	if (filp->f_pos == 1) {
		/* Entry 1 is for ".." */
		hfs_u32 cnid;

		if (type == HFS_NAT_NDIR) {
			cnid = hfs_get_nl(entry->key.ParID);
		} else {
			cnid = entry->cnid;
		}

		if (filldir(dirent, DOT_DOT->Name,
			    DOT_DOT_LEN, 1, ntohl(cnid))) {
			return 0;
		}
		filp->f_pos = 2;
	}

	if (filp->f_pos < (dir->i_size - 1)) {
		hfs_u32 cnid;
		hfs_u8 type;

	    	if (hfs_cat_open(entry, &brec) ||
		    hfs_cat_next(entry, &brec, filp->f_pos - 2, &cnid, &type)) {
			return 0;
		}
		while (filp->f_pos < (dir->i_size - 1)) {
			if (hfs_cat_next(entry, &brec, 1, &cnid, &type)) {
				return 0;
			}
			if (!skip_dirs || (type != HFS_CDR_DIR)) {
				ino_t ino;
				unsigned int len;
				unsigned char tmp_name[HFS_NAMEMAX];

				ino = ntohl(cnid) | HFS_I(dir)->file_type;
				len = hfs_namein(dir, tmp_name,
				    &((struct hfs_cat_key *)brec.key)->CName);
				if (filldir(dirent, tmp_name, len,
					    filp->f_pos, ino)) {
					hfs_cat_close(entry, &brec);
					return 0;
				}
			}
			++filp->f_pos;
		}
		hfs_cat_close(entry, &brec);
	}

	if (filp->f_pos == (dir->i_size - 1)) {
		if (type == HFS_NAT_NDIR) {
			/* In normal dirs entry 2 is for ".AppleDouble" */
			if (filldir(dirent, DOT_APPLEDOUBLE->Name,
				    DOT_APPLEDOUBLE_LEN, filp->f_pos,
				    ntohl(entry->cnid) | HFS_NAT_HDIR)) {
				return 0;
			}
		} else if (type == HFS_NAT_HDIR) {
			/* In .AppleDouble entry 2 is for ".Parent" */
			if (filldir(dirent, DOT_PARENT->Name,
				    DOT_PARENT_LEN, filp->f_pos,
				    ntohl(entry->cnid) | HFS_NAT_HDR)) {
				return 0;
			}
		}
		++filp->f_pos;
	}

	return 0;
}
Пример #7
0
/*
 * find_ext()
 *
 * Given a pointer to a (struct hfs_file) and an allocation block
 * number in the file, find the extent record containing that block.
 * Returns a pointer to the extent record on success or NULL on failure.
 * The 'cache' field of 'fil' also points to the extent so it has a
 * reference count of at least 2.
 *
 * Callers must check that fil != NULL
 */
static struct hfs_extent * find_ext(struct hfs_fork *fork, int alloc_block)
{
    struct hfs_cat_entry *entry = fork->entry;
    struct hfs_btree *tr= entry->mdb->ext_tree;
    struct hfs_ext_key target, *key;
    struct hfs_brec brec;
    struct hfs_extent *ext, *ptr;
    int tmp;

    if (alloc_block < 0) {
        ext = &fork->first;
        goto found;
    }

    ext = fork->cache;
    if (!ext || (alloc_block < ext->start)) {
        ext = &fork->first;
    }
    while (ext->next && (alloc_block > ext->end)) {
        ext = ext->next;
    }
    if ((alloc_block <= ext->end) && (alloc_block >= ext->start)) {
        goto found;
    }

    /* time to read more extents */
    if (!HFS_NEW(ext)) {
        goto bail3;
    }

    build_key(&target, fork, alloc_block);

    tmp = hfs_bfind(&brec, tr, HFS_BKEY(&target), HFS_BFIND_READ_LE);
    if (tmp < 0) {
        goto bail2;
    }

    key = (struct hfs_ext_key *)brec.key;
    if ((hfs_get_nl(key->FNum) != hfs_get_nl(target.FNum)) ||
            (key->FkType != fork->fork)) {
        goto bail1;
    }

    read_extent(ext, brec.data, hfs_get_hs(key->FABN));
    hfs_brec_relse(&brec, NULL);

    if ((alloc_block > ext->end) && (alloc_block < ext->start)) {
        /* something strange happened */
        goto bail2;
    }

    ptr = fork->cache;
    if (!ptr || (alloc_block < ptr->start)) {
        ptr = &fork->first;
    }
    while (ptr->next && (alloc_block > ptr->end)) {
        ptr = ptr->next;
    }
    if (ext->start == ptr->start) {
        /* somebody beat us to it. */
        HFS_DELETE(ext);
        ext = ptr;
    } else if (ext->start < ptr->start) {
        /* insert just before ptr */
        ptr->prev->next = ext;
        ext->prev = ptr->prev;
        ext->next = ptr;
        ptr->prev = ext;
    } else {
        /* insert at end */
        ptr->next = ext;
        ext->prev = ptr;
    }
found:
    ++ext->count; /* for return value */
    set_cache(fork, ext);
    return ext;

bail1:
    hfs_brec_relse(&brec, NULL);
bail2:
    HFS_DELETE(ext);
bail3:
    return NULL;
}