/***************************************************************************** * dir.c * File and inode operations for directories. * * Apple II DOS 3.3 Filesystem Driver for Linux 2.4.x * Copyright (c) 2001 Vince Weaver * Copyright (c) 2001 Matt Jensen. * This program is free software distributed under the terms of the GPL. *****************************************************************************/ /*= Kernel Includes =========================================================*/ #include /*= DOS 3.3 Includes =======================================================*/ #include "dos33.h" /*= Forward Declarations ===================================================*/ /* For dos33_dir_inode_operations. */ struct dentry *dos33_lookup(struct inode *,struct dentry *); int dos33_create(struct inode *,struct dentry *,int); int dos33_unlink(struct inode *,struct dentry *); int dos33_rename(struct inode *,struct dentry *,struct inode *,struct dentry *); /* For dos33_dir_operations. */ int dos33_readdir(struct file *,void *,filldir_t); /*= VFS Interface Structures ================================================*/ struct inode_operations dos33_dir_inode_operations = { lookup: dos33_lookup, create: dos33_create, unlink: dos33_unlink, rename: dos33_rename }; struct file_operations dos33_dir_operations = { read: generic_read_dir, readdir: dos33_readdir /* fsync: file_fsync */ }; /***************************************************************************** * dos33_new_dir_block() * Allocate and initialize a new directory block. *****************************************************************************/ int dos33_expand_dir(struct super_block *sb,u16 block) { int result = 0; // u16 new_block = 0; // struct buffer_head *dir_bh = NULL; // struct dos33_dir_block *dir = NULL; DOS33_DEBUG("expand_dir\n"); #if 0 /* Try to allocate a new block. */ new_block = prodos_alloc_block(sb); if (!new_block) { result = -ENOSPC; goto out_cleanup; } /* Read the block. */ dir_bh = prodos_bread(sb,new_block); if (!dir_bh) { result = -EIO; goto out_cleanup; } dir = (void*)dir_bh->b_data; /* Initialize the block. */ memset(dir,0,PRODOS_BLOCK_SIZE); dir->header.prev = cpu_to_le16(block); dir->header.next = 0; mark_buffer_dirty(dir_bh); result = new_block; out_cleanup: if (dir_bh) prodos_brelse(dir_bh); #endif return result; } /***************************************************************************** * dos33_find_entry() * Search directory @inode for an entry. If @name is non-NULL, the directory * is searched for an entry with matching name; if @name is NULL, the * directory is searched (and possibly expanded) for an unused entry. Zero is * returned on failure; the found entry's inode number is returned on success. *****************************************************************************/ unsigned long dos33_find_entry(struct inode *inode,struct qstr *name) { unsigned long result = 0; u32 catalog_block = (DOS33_INO_TRACK(inode->i_ino)<<4)+ DOS33_INO_SECTOR(inode->i_ino); u32 odd=0,entry_num=0; struct buffer_head *bh = NULL; struct dos33_catalog_sector *catalog = NULL; DOS33_DEBUG("find entry: %i %x\n",(int)inode->i_ino,(int)catalog_block); /* Scan the directory for a matching entry. */ while (catalog_block) { /* Load next directory block when necessary. */ if (!bh) { odd=catalog_block%2; /* Load the block. */ DOS33_DEBUG("==Loading block %x\n",catalog_block); bh = dos33_bread(inode->i_sb,catalog_block); if (!bh) { DOS33_DEBUG("dos33_bread() failed for block %d\n",catalog_block); goto out_cleanup; } /* deal with 256byte sector size */ if (odd) { catalog = (struct dos33_catalog_sector*)(bh->b_data+256); } else { catalog = (struct dos33_catalog_sector*)(bh->b_data); } } /* Searching for an empty entry. FIX ME */ if (!name) { DOS33_DEBUG("== no name dir.c FIXME\n"); // result = DOS33_MAKE_INO(0,(cur_block>>4)&0xffff,cur_block&0xf); goto out_cleanup; } else { int qlen=name->len; int mismatch=0; int i; struct dos33_file_t *file_ent=&catalog->files[entry_num]; /* Do the names match? */ for(i=0;iname[i] != ((file_ent->file_name[i])&0x7f)) mismatch++; } /* If they match, return the inode */ if (!mismatch) { DOS33_DEBUG("==MATCH %s\n",name->name); result = DOS33_MAKE_INO(0,entry_num, (catalog_block>>4)&0x7f, (catalog_block)&0xf); goto out_cleanup; } } /* Advance to next entry. */ /* For now only handle first 7 files */ if (++entry_num >= 7) { DOS33_DEBUG("ENDDDDDDDDDDDING\n"); entry_num=0; catalog_block = ((catalog->next.track)<<4)+catalog->next.sector; DOS33_DEBUG("== next block %x\n",catalog_block); /* Release previous block. */ dos33_brelse(bh); bh = NULL; } } out_cleanup: if (bh) { dos33_brelse(bh); bh = NULL; } return result; } /***************************************************************************** * prodos_unlink_all() * Unlink (delete) all forks of the ProDOS file represented by an @inode. *****************************************************************************/ #if 0 int dos33_unlink_all(struct inode *inode,struct dos33_dir_entry *ent) { int result = 0; const u16 dblk = PRODOS_INO_DBLK(inode->i_ino); const u8 dent = PRODOS_INO_DENT(inode->i_ino); u8 dfrk = PRODOS_INO_DFORK(inode->i_ino); struct buffer_head *ext_bh = NULL; /* We may have to delete TWO forks. */ if (PRODOS_ISEXTENDED(*ent)) { struct prodos_ext_key_block *ext_block = NULL; /* Load the extended directory block. */ ext_bh = prodos_bread(inode->i_sb,PRODOS_I(inode)->i_key); if (!ext_bh) { result = -EIO; goto out_cleanup; } ext_block = (void*)ext_bh->b_data; /* Free the resource fork. */ prodos_free_tree_blocks(inode->i_sb,ext_block->res.stype << 4, le16_to_cpu(ext_block->res.key_block), le16_to_cpu(ext_block->res.blocks_used)); /* Free the data fork. */ prodos_free_tree_blocks(inode->i_sb,ext_block->data.stype << 4, le16_to_cpu(ext_block->data.key_block), le16_to_cpu(ext_block->data.blocks_used)); /* Release the extended directory block. */ prodos_brelse(ext_bh); ext_bh = NULL; /* Free the extended directory block. */ prodos_free_block(inode->i_sb,PRODOS_I(inode)->i_key); } /* ...or we may only have to delete one fork. */ else { prodos_free_tree_blocks(inode->i_sb,PRODOS_I(inode)->i_stype, le16_to_cpu(ent->key_block), le16_to_cpu(ent->blocks_used)); } /* Update this inode. */ inode->i_nlink--; mark_inode_dirty(inode); /* Unlink the resource fork inode. */ if (PRODOS_I(inode)->i_stype == PRODOS_STYPE_EXTENDED) { inode = iget(inode->i_sb,PRODOS_MAKE_INO(dblk,dent,PRODOS_DFORK_RES)); if (inode) { down(&inode->i_sem); inode->i_nlink--; mark_inode_dirty(inode); up(&inode->i_sem); iput(inode); } } /* We may have been called on either a data fork inode OR a meta file inode. Unlink the one upon which we were NOT called (the one we were called on has, obviously, already been unlinked.) */ dfrk = dfrk == PRODOS_DFORK_META ? PRODOS_DFORK_DATA : PRODOS_DFORK_META; inode = iget(inode->i_sb,PRODOS_MAKE_INO(dblk,dent,dfrk)); if (inode) { down(&inode->i_sem); inode->i_nlink--; mark_inode_dirty(inode); up(&inode->i_sem); iput(inode); } /* Mark the directory entry as deleted. */ ent->name[0] &= 0x0f; result = 1; out_cleanup: if (ext_bh) prodos_brelse(ext_bh); return result; } #endif /***************************************************************************** * prodos_unlink_res() * Unlink (delete) the resource fork of the ProDOS file represented by an * @inode. This is the most involved of the unlink operations. The resource * fork is deleted and its blocks freed, then the file is collapsed into a * a regular (non-extended) file by copying the data fork metainformation from * the extended directory block into the base directory entry itself. *****************************************************************************/ #if 0 int dos33_unlink_res(struct inode *inode,struct prodos_dir_entry *ent) { int result = 0; struct buffer_head *key_bh = NULL; struct prodos_ext_key_block *key_block = NULL; /* Need the key block so we can copy data fork info into the dir entry. */ key_bh = prodos_bread(inode->i_sb,PRODOS_I(inode)->i_key); if (!key_bh) { result = -EIO; goto out_cleanup; } key_block = (void*)key_bh->b_data; /* Update the directory entry. Note that we don't have to worry about byte order since we're copying from one on-disk entry to another. */ ent->name[0] &= 0x0f; ent->name[0] |= key_block->data.stype << 4; ent->key_block = key_block->data.key_block; ent->blocks_used = key_block->data.blocks_used; memcpy(&ent->eof,&key_block->data.eof,3); /* TODO: Update the data and meta inodes here (stype, key block, size, blocks used) */ /* Free affected disk blocks. */ prodos_free_tree_blocks(inode->i_sb,key_block->res.stype << 4, le16_to_cpu(key_block->res.key_block), le16_to_cpu(key_block->res.blocks_used)); prodos_free_block(inode->i_sb,PRODOS_I(inode)->i_key); /* The inode is now dirty; also return 1 to cause the directory buffer to be marked dirty. */ mark_inode_dirty(inode); result = 1; out_cleanup: if (key_bh) prodos_brelse(key_bh); return result; } #endif /*= Interface Functions =====================================================*/ /***************************************************************************** * dos33_lookup() * Search a directory inode for an entry. *****************************************************************************/ struct dentry *dos33_lookup(struct inode *inode,struct dentry *dentry) { SHORTCUT struct dos33_sb_info * const psb = DOS33_SB(inode->i_sb); struct dentry *result = NULL; unsigned long ino = 0; DOS33_DEBUG("Lookup\n"); /* Grab directory lock. */ down(&psb->s_dir_lock); /* Attempt to find matching entry and get its inode number. */ ino = dos33_find_entry(inode,&dentry->d_name); if (!ino) { result = ERR_PTR(-ENOENT); goto out_cleanup; } /* Get the inode and set up a dentry. */ inode = iget(inode->i_sb,ino); if (!inode) { DOS33_DEBUG("iget() failed for ino 0x%08x",(int)ino); result = ERR_PTR(-EACCES); goto out_cleanup; } dentry->d_op = &dos33_dentry_operations; d_add(dentry,inode); out_cleanup: up(&psb->s_dir_lock); return result; } /***************************************************************************** * prodos_create() * Create a new entry in directory inode @dir. Note that the VFS has already * checked that the filename is unique within the directory. *****************************************************************************/ int dos33_create(struct inode *dir,struct dentry *dentry,int mode) { // SHORTCUT struct prodos_sb_info * psb = PRODOS_SB(dir->i_sb); int result = 0; // unsigned long ino = 0; // struct buffer_head *dir_bh = NULL; DOS33_DEBUG("create\n"); #if 0 PRODOS_ERROR_0(dir->i_sb,"called"); /* Grab directory lock. */ down(&psb->s_dir_lock); /* Get an unused "inode" in the directory. */ ino = prodos_find_entry(dir,NULL); if (!ino) { result = -ENOSPC; goto out_cleanup; } /* Read the directory block. */ /* dir_bh = prodos_bread(dir->i_sb,PRODOS_INO_DBLK(ino)); if (!dir_bh) { result = -EIO; goto out_cleanup; }*/ result = -EPERM; out_cleanup: if (dir_bh) prodos_brelse(dir_bh); up(&psb->s_dir_lock); #endif return result; } /***************************************************************************** * prodos_unlink() *****************************************************************************/ /* NOTE: The VFS holds the inode->i_zombie semaphore during this call. */ /* The big kernel lock is also held for exactly the duration of this call. */ int dos33_unlink(struct inode *inode,struct dentry *dentry) { // SHORTCUT struct prodos_sb_info * const psb = PRODOS_SB(inode->i_sb); // SHORTCUT struct inode * const victim = dentry->d_inode; int result = 0; // struct buffer_head *dir_bh = NULL; // struct prodos_dir_entry *ent = NULL; DOS33_DEBUG("unlink\n"); #if 0 /* Grab directory lock. */ down(&psb->s_dir_lock); /* Load the directory block. */ dir_bh = prodos_bread(inode->i_sb,PRODOS_INO_DBLK(victim->i_ino)); if (!dir_bh) { result = -EIO; goto out_cleanup; } ent = &((struct prodos_dir_block*)dir_bh->b_data)->entries[PRODOS_INO_DENT(victim->i_ino)]; /* Dispatch appropriate unlink function. */ switch (PRODOS_INO_DFORK(victim->i_ino)) { case PRODOS_DFORK_DATA: result = prodos_unlink_all(victim,ent); break; case PRODOS_DFORK_META: result = prodos_unlink_all(victim,ent); break; case PRODOS_DFORK_RES: if (!PRODOS_ISEXTENDED(*ent)) { result = -ENOENT; goto out_cleanup; } result = prodos_unlink_res(victim,ent); break; } /* The prodos_unlink_*() helper returned 1 if it dirtied the dir block. */ if (result == 1) { mark_buffer_dirty(dir_bh); result = 0; } out_cleanup: if (dir_bh) prodos_brelse(dir_bh); up(&psb->s_dir_lock); #endif return result; } /***************************************************************************** * prodos_rename() *****************************************************************************/ int dos33_rename(struct inode *oi,struct dentry *od,struct inode *ni,struct dentry *nd) { int result = 0; DOS33_DEBUG("rename\n"); #if 0 /* Our inode number usage prevents all but in-directory renames. */ if (oi != ni) { result = -EPERM; goto out_cleanup; } result = -EPERM; out_cleanup: #endif return result; } /***************************************************************************** * dos33_readdir() * Standard file operation; reads a directory and returns its entries via * the filldir() function supplied by the caller. *****************************************************************************/ int dos33_readdir(struct file *filp,void *dirent,filldir_t filldir) { SHORTCUT struct super_block * const sb = filp->f_dentry->d_sb; SHORTCUT struct inode * const inode = filp->f_dentry->d_inode; SHORTCUT struct inode * const parent = filp->f_dentry->d_parent->d_inode; int result = 0; struct buffer_head *bh = NULL; struct dos33_catalog_sector *catalog = NULL; u32 dent=0; u32 catalog_track = DOS33_INO_TRACK(filp->f_pos); u32 catalog_sector = DOS33_INO_SECTOR(filp->f_pos); u32 catalog_block; u32 odd; struct dos33_file_t *ent = NULL; char name[DOS33_MAX_NAME_LEN + 3] = ""; int nlen; unsigned int ino = 0; DOS33_DEBUG("readdir\n"); /* Handle the special '.' and '..' entries. */ switch ( (long int)filp->f_pos) { case 0: /* Add the '.' entry. */ if (filldir(dirent,".",1,0,inode->i_ino,DT_DIR) < 0) goto out_cleanup; /* fpos is simply incremented at this point. */ filp->f_pos++; /* Fall through. */ case 1: /* Add the '..' entry. */ if (filldir(dirent,"..",2,1,parent->i_ino,DT_DIR) < 0) goto out_cleanup; /* fpos takes on special format after second entry. */ filp->f_pos = DOS33_MAKE_INO(0,0, DOS33_INO_TRACK(inode->i_ino), DOS33_INO_SECTOR(inode->i_ino)); } /* Grab directory lock. */ down(&DOS33_SB(sb)->s_dir_lock); /* After the two special entries, filp->f_pos is the "inode number" of the next file (or fork) in the directory. See the PRODOS_INO_*() and PRODOS_MAKE_INO() macros for the format of this value. When all entries have been returned filp->f_pos is set to 3 to signal end-of-directory. */ while (filp->f_pos != 3) { catalog_block=((catalog_track<<4)+catalog_sector); odd=catalog_block%2; DOS33_DEBUG("filp: %x dblk: %x %i %i\n",(int)filp->f_pos,(int)catalog_block,(int)catalog_track,(int)catalog_sector); /* Load the block if necessary. */ if (!bh) { bh = dos33_bread(sb,catalog_block); if (!bh) { result = -EIO; goto out_cleanup; } /* handle 256 byte sectors */ if (odd) { catalog = (struct dos33_catalog_sector*)(bh->b_data+256); } else { catalog = (struct dos33_catalog_sector*)bh->b_data; } } ent = &catalog->files[dent]; /* Copy and modify the name as necessary. */ if (ent->file_name[0]!=0) { memcpy(name,&(ent->file_name),DOS33_MAX_NAME_LEN+1); nlen=dos33_convert_filename(name); /* Build inode number. */ ino = DOS33_MAKE_INO(0,dent,catalog_track, catalog_sector); /* Finally, attempt to return the entry via filldir(). */ if (filldir(dirent,&name[0],nlen,filp->f_pos,ino,DT_UNKNOWN) < 0) goto out_cleanup; } dent++; if (dent>6) { dent=0; dos33_brelse(bh); bh=NULL; catalog_track=(catalog->next.track); catalog_sector=(catalog->next.sector); if ((!catalog_track) && (!catalog_sector)) { filp->f_pos=3; } } } /* Release remaining resources and return. */ out_cleanup: if (bh) { dos33_brelse(bh); bh = NULL; } up(&DOS33_SB(sb)->s_dir_lock); return result; }