emd.c
上传用户:lgb322
上传日期:2013-02-24
资源大小:30529k
文件大小:16k
- /*
- * linux/fs/umsdos/emd.c
- *
- * Written 1993 by Jacques Gelinas
- *
- * Extended MS-DOS directory handling functions
- */
- #include <linux/types.h>
- #include <linux/fcntl.h>
- #include <linux/kernel.h>
- #include <linux/sched.h>
- #include <linux/errno.h>
- #include <linux/string.h>
- #include <linux/msdos_fs.h>
- #include <linux/umsdos_fs.h>
- #include <linux/dcache.h>
- #include <linux/pagemap.h>
- #include <linux/delay.h>
- void put_entry (struct umsdos_dirent *p, struct umsdos_dirent *q)
- {
- p->name_len = q->name_len;
- p->flags = q->flags;
- p->nlink = cpu_to_le16(q->nlink);
- p->uid = cpu_to_le16(q->uid);
- p->gid = cpu_to_le16(q->gid);
- p->atime = cpu_to_le32(q->atime);
- p->mtime = cpu_to_le32(q->mtime);
- p->ctime = cpu_to_le32(q->ctime);
- p->rdev = cpu_to_le16(q->rdev);
- p->mode = cpu_to_le16(q->mode);
- }
- static void get_entry(struct umsdos_dirent *p, struct umsdos_dirent *q)
- {
- p->name_len = q->name_len;
- p->name[p->name_len]=' ';
- p->flags = q->flags;
- p->nlink = le16_to_cpu (q->nlink);
- /* FIXME -- 32bit UID/GID issues */
- p->uid = le16_to_cpu (q->uid);
- p->gid = le16_to_cpu (q->gid);
- p->atime = le32_to_cpu (q->atime);
- p->mtime = le32_to_cpu (q->mtime);
- p->ctime = le32_to_cpu (q->ctime);
- p->rdev = le16_to_cpu (q->rdev);
- p->mode = le16_to_cpu (q->mode);
- }
- /*
- * Lookup the EMD dentry for a directory.
- *
- * Note: the caller must hold a lock on the parent directory.
- */
- struct dentry *umsdos_get_emd_dentry(struct dentry *parent)
- {
- struct dentry *demd;
- demd = umsdos_lookup_dentry(parent, UMSDOS_EMD_FILE,
- UMSDOS_EMD_NAMELEN, 1);
- return demd;
- }
- /*
- * Check whether a directory has an EMD file.
- *
- * Note: the caller must hold a lock on the parent directory.
- */
- int umsdos_have_emd(struct dentry *dir)
- {
- struct dentry *demd = umsdos_get_emd_dentry (dir);
- int found = 0;
- if (!IS_ERR(demd)) {
- if (demd->d_inode)
- found = 1;
- dput(demd);
- }
- return found;
- }
- /*
- * Create the EMD file for a directory if it doesn't
- * already exist. Returns 0 or an error code.
- *
- * Note: the caller must hold a lock on the parent directory.
- */
- int umsdos_make_emd(struct dentry *parent)
- {
- struct dentry *demd = umsdos_get_emd_dentry(parent);
- int err = PTR_ERR(demd);
- if (IS_ERR(demd)) {
- printk("umsdos_make_emd: can't get dentry in %s, err=%dn",
- parent->d_name.name, err);
- goto out;
- }
- /* already created? */
- err = 0;
- if (demd->d_inode)
- goto out_set;
- Printk(("umsdos_make_emd: creating EMD %s/%sn",
- parent->d_name.name, demd->d_name.name));
- err = msdos_create(parent->d_inode, demd, S_IFREG | 0777);
- if (err) {
- printk (KERN_WARNING
- "umsdos_make_emd: create %s/%s failed, err=%dn",
- parent->d_name.name, demd->d_name.name, err);
- }
- out_set:
- dput(demd);
- out:
- return err;
- }
- /*
- * Read an entry from the EMD file.
- * Support variable length record.
- * Return -EIO if error, 0 if OK.
- *
- * does not change {d,i}_count
- */
- int umsdos_emd_dir_readentry (struct dentry *demd, loff_t *pos, struct umsdos_dirent *entry)
- {
- struct address_space *mapping = demd->d_inode->i_mapping;
- struct page *page;
- struct umsdos_dirent *p;
- int offs = *pos & ~PAGE_CACHE_MASK;
- int recsize;
- int ret = 0;
- page = read_cache_page(mapping, *pos>>PAGE_CACHE_SHIFT,
- (filler_t*)mapping->a_ops->readpage, NULL);
- if (IS_ERR(page))
- goto sync_fail;
- wait_on_page(page);
- if (!Page_Uptodate(page))
- goto async_fail;
- p = (struct umsdos_dirent*)(kmap(page)+offs);
- /* if this is an invalid entry (invalid name length), ignore it */
- if( p->name_len > UMSDOS_MAXNAME )
- {
- printk (KERN_WARNING "Ignoring invalid EMD entry with size %dn", entry->name_len);
- p->name_len = 0;
- ret = -ENAMETOOLONG; /* notify umssync(8) code that something is wrong */
- /* FIXME: does not work if we did 'ls -l' before 'udosctl uls' ?! */
- }
- recsize = umsdos_evalrecsize(p->name_len);
- if (offs + recsize > PAGE_CACHE_SIZE) {
- struct page *page2;
- int part = (char *)(page_address(page) + PAGE_CACHE_SIZE) - p->spare;
- page2 = read_cache_page(mapping, 1+(*pos>>PAGE_CACHE_SHIFT),
- (filler_t*)mapping->a_ops->readpage, NULL);
- if (IS_ERR(page2)) {
- kunmap(page);
- page_cache_release(page);
- page = page2;
- goto sync_fail;
- }
- wait_on_page(page2);
- if (!Page_Uptodate(page2)) {
- kunmap(page);
- page_cache_release(page2);
- goto async_fail;
- }
- memcpy(entry->spare,p->spare,part);
- memcpy(entry->spare+part,kmap(page2),
- recsize+offs-PAGE_CACHE_SIZE);
- kunmap(page2);
- page_cache_release(page2);
- } else
- memcpy(entry->spare,p->spare,((char*)p+recsize)-p->spare);
- get_entry(entry, p);
- kunmap(page);
- page_cache_release(page);
- *pos += recsize;
- return ret;
- async_fail:
- page_cache_release(page);
- page = ERR_PTR(-EIO);
- sync_fail:
- return PTR_ERR(page);
- }
- /*
- * Write an entry in the EMD file.
- * Return 0 if OK, -EIO if some error.
- *
- * Note: the caller must hold a lock on the parent directory.
- */
- int umsdos_writeentry (struct dentry *parent, struct umsdos_info *info,
- int free_entry)
- {
- struct inode *dir = parent->d_inode;
- struct umsdos_dirent *entry = &info->entry;
- struct dentry *emd_dentry;
- int ret;
- struct umsdos_dirent entry0,*p;
- struct address_space *mapping;
- struct page *page, *page2 = NULL;
- int offs;
- emd_dentry = umsdos_get_emd_dentry(parent);
- ret = PTR_ERR(emd_dentry);
- if (IS_ERR(emd_dentry))
- goto out;
- /* make sure there's an EMD file */
- ret = -EIO;
- if (!emd_dentry->d_inode) {
- printk(KERN_WARNING
- "umsdos_writeentry: no EMD file in %s/%sn",
- parent->d_parent->d_name.name, parent->d_name.name);
- goto out_dput;
- }
- if (free_entry) {
- /* #Specification: EMD file / empty entries
- * Unused entries in the EMD file are identified
- * by the name_len field equal to 0. However to
- * help future extension (or bug correction :-( ),
- * empty entries are filled with 0.
- */
- memset (&entry0, 0, sizeof (entry0));
- entry = &entry0;
- } else if (entry->name_len > 0) {
- memset (entry->name + entry->name_len, ' ',
- sizeof (entry->name) - entry->name_len);
- /* #Specification: EMD file / spare bytes
- * 10 bytes are unused in each record of the EMD. They
- * are set to 0 all the time, so it will be possible
- * to do new stuff and rely on the state of those
- * bytes in old EMD files.
- */
- memset (entry->spare, 0, sizeof (entry->spare));
- }
- /* write the entry and update the parent timestamps */
- mapping = emd_dentry->d_inode->i_mapping;
- offs = info->f_pos & ~PAGE_CACHE_MASK;
- ret = -ENOMEM;
- page = grab_cache_page(mapping, info->f_pos>>PAGE_CACHE_SHIFT);
- if (!page)
- goto out_dput;
- p = (struct umsdos_dirent *) (page_address(page) + offs);
- if (offs + info->recsize > PAGE_CACHE_SIZE) {
- ret = mapping->a_ops->prepare_write(NULL,page,offs,
- PAGE_CACHE_SIZE);
- if (ret)
- goto out_unlock;
- page2 = grab_cache_page(mapping,
- (info->f_pos>>PAGE_CACHE_SHIFT)+1);
- if (!page2)
- goto out_unlock2;
- ret = mapping->a_ops->prepare_write(NULL,page2,0,
- offs+info->recsize-PAGE_CACHE_SIZE);
- if (ret)
- goto out_unlock3;
- put_entry (p, entry);
- memcpy(p->spare,entry->spare,
- (char *)(page_address(page) + PAGE_CACHE_SIZE) - p->spare);
- memcpy(page_address(page2),
- ((char*)entry)+PAGE_CACHE_SIZE-offs,
- offs+info->recsize-PAGE_CACHE_SIZE);
- ret = mapping->a_ops->commit_write(NULL,page2,0,
- offs+info->recsize-PAGE_CACHE_SIZE);
- if (ret)
- goto out_unlock3;
- ret = mapping->a_ops->commit_write(NULL,page,offs,
- PAGE_CACHE_SIZE);
- UnlockPage(page2);
- page_cache_release(page2);
- if (ret)
- goto out_unlock;
- } else {
- ret = mapping->a_ops->prepare_write(NULL,page,offs,
- offs + info->recsize);
- if (ret)
- goto out_unlock;
- put_entry (p, entry);
- memcpy(p->spare,entry->spare,((char*)p+info->recsize)-p->spare);
- ret = mapping->a_ops->commit_write(NULL,page,offs,
- offs + info->recsize);
- if (ret)
- goto out_unlock;
- }
- UnlockPage(page);
- page_cache_release(page);
-
- dir->i_ctime = dir->i_mtime = CURRENT_TIME;
- mark_inode_dirty(dir);
- out_dput:
- dput(emd_dentry);
- out:
- Printk (("umsdos_writeentry /mn/: returning %d...n", ret));
- return ret;
- out_unlock3:
- UnlockPage(page2);
- page_cache_release(page2);
- out_unlock2:
- ClearPageUptodate(page);
- kunmap(page);
- out_unlock:
- UnlockPage(page);
- page_cache_release(page);
- printk ("UMSDOS: problem with EMD file: can't writen");
- goto out_dput;
- }
- /*
- * General search, locate a name in the EMD file or an empty slot to
- * store it. if info->entry.name_len == 0, search the first empty
- * slot (of the proper size).
- *
- * Return 0 if found, -ENOENT if not found, another error code if
- * other problem.
- *
- * So this routine is used to either find an existing entry or to
- * create a new one, while making sure it is a new one. After you
- * get -ENOENT, you make sure the entry is stuffed correctly and
- * call umsdos_writeentry().
- *
- * To delete an entry, you find it, zero out the entry (memset)
- * and call umsdos_writeentry().
- *
- * All this to say that umsdos_writeentry must be called after this
- * function since it relies on the f_pos field of info.
- *
- * Note: the caller must hold a lock on the parent directory.
- */
- /* #Specification: EMD file structure
- * The EMD file uses a fairly simple layout. It is made of records
- * (UMSDOS_REC_SIZE == 64). When a name can't be written in a single
- * record, multiple contiguous records are allocated.
- */
- static int umsdos_find (struct dentry *demd, struct umsdos_info *info)
- {
- struct umsdos_dirent *entry = &info->entry;
- int recsize = info->recsize;
- struct inode *emd_dir;
- int ret = -ENOENT;
- struct {
- off_t posok; /* Position available to store the entry */
- off_t one; /* One empty position -> maybe <- large enough */
- } empty;
- int found = 0;
- int empty_size = 0;
- struct address_space *mapping;
- filler_t *readpage;
- struct page *page = NULL;
- int index = -1;
- int offs = PAGE_CACHE_SIZE,max_offs = PAGE_CACHE_SIZE;
- char *p = NULL;
- loff_t pos = 0;
- /* make sure there's an EMD file ... */
- ret = -ENOENT;
- emd_dir = demd->d_inode;
- if (!emd_dir)
- goto out_dput;
- mapping = emd_dir->i_mapping;
- readpage = (filler_t*)mapping->a_ops->readpage;
- empty.posok = emd_dir->i_size;
- while (1) {
- struct umsdos_dirent *rentry;
- int entry_size;
- if (offs >= max_offs) {
- if (page) {
- kunmap(page);
- page_cache_release(page);
- page = NULL;
- }
- if (pos >= emd_dir->i_size) {
- info->f_pos = empty.posok;
- break;
- }
- if (++index == (emd_dir->i_size>>PAGE_CACHE_SHIFT))
- max_offs = emd_dir->i_size & ~PAGE_CACHE_MASK;
- offs -= PAGE_CACHE_SIZE;
- page = read_cache_page(mapping,index,readpage,NULL);
- if (IS_ERR(page))
- goto sync_fail;
- wait_on_page(page);
- if (!Page_Uptodate(page))
- goto async_fail;
- p = kmap(page);
- }
- rentry = (struct umsdos_dirent *)(p+offs);
- if (rentry->name_len == 0) {
- /* We are looking for an empty section at least */
- /* as large as recsize. */
- if (entry->name_len == 0) {
- info->f_pos = pos;
- ret = 0;
- break;
- }
- offs += UMSDOS_REC_SIZE;
- pos += UMSDOS_REC_SIZE;
- if (found)
- continue;
- if (!empty_size)
- empty.one = pos-UMSDOS_REC_SIZE;
- empty_size += UMSDOS_REC_SIZE;
- if (empty_size == recsize) {
- /* Here is a large enough section. */
- empty.posok = empty.one;
- found = 1;
- }
- continue;
- }
- entry_size = umsdos_evalrecsize(rentry->name_len);
- if (entry_size > PAGE_CACHE_SIZE)
- goto async_fail;
- empty_size = 0;
- if (entry->name_len != rentry->name_len)
- goto skip_it;
- if (entry_size + offs > PAGE_CACHE_SIZE) {
- /* Sucker spans the page boundary */
- int len = (p+PAGE_CACHE_SIZE)-rentry->name;
- struct page *next_page;
- char *q;
- next_page = read_cache_page(mapping,index+1,readpage,NULL);
- if (IS_ERR(next_page)) {
- page_cache_release(page);
- page = next_page;
- goto sync_fail;
- }
- wait_on_page(next_page);
- if (!Page_Uptodate(next_page)) {
- page_cache_release(page);
- page = next_page;
- goto async_fail;
- }
- q = kmap(next_page);
- if (memcmp(entry->name, rentry->name, len) ||
- memcmp(entry->name+len, q, entry->name_len-len)) {
- kunmap(next_page);
- page_cache_release(next_page);
- goto skip_it;
- }
- kunmap(next_page);
- page_cache_release(next_page);
- } else if (memcmp (entry->name, rentry->name, entry->name_len))
- goto skip_it;
- info->f_pos = pos;
- get_entry(entry, rentry);
- ret = 0;
- break;
- skip_it:
- offs+=entry_size;
- pos+=entry_size;
- }
- if (page) {
- kunmap(page);
- page_cache_release(page);
- }
- umsdos_manglename (info);
- out_dput:
- dput(demd);
- return ret;
- async_fail:
- page_cache_release(page);
- page = ERR_PTR(-EIO);
- sync_fail:
- return PTR_ERR(page);
- }
- /*
- * Add a new entry in the EMD file.
- * Return 0 if OK or a negative error code.
- * Return -EEXIST if the entry already exists.
- *
- * Complete the information missing in info.
- *
- * N.B. What if the EMD file doesn't exist?
- */
- int umsdos_newentry (struct dentry *parent, struct umsdos_info *info)
- {
- int err, ret = -EEXIST;
- struct dentry *demd = umsdos_get_emd_dentry(parent);
- ret = PTR_ERR(demd);
- if (IS_ERR(demd))
- goto out;
- err = umsdos_find (demd, info);
- if (err && err == -ENOENT) {
- ret = umsdos_writeentry (parent, info, 0);
- Printk (("umsdos_writeentry EMD ret = %dn", ret));
- }
- out:
- return ret;
- }
- /*
- * Create a new hidden link.
- * Return 0 if OK, an error code if not.
- */
- /* #Specification: hard link / hidden name
- * When a hard link is created, the original file is renamed
- * to a hidden name. The name is "..LINKNNN" where NNN is a
- * number define from the entry offset in the EMD file.
- */
- int umsdos_newhidden (struct dentry *parent, struct umsdos_info *info)
- {
- int ret;
- struct dentry *demd = umsdos_get_emd_dentry(parent);
- ret = PTR_ERR(demd);
- if (IS_ERR(demd))
- goto out;
- umsdos_parse ("..LINK", 6, info);
- info->entry.name_len = 0;
- ret = umsdos_find (demd, info);
- if (ret == -ENOENT || ret == 0) {
- info->entry.name_len = sprintf (info->entry.name,
- "..LINK%ld", info->f_pos);
- ret = 0;
- }
- out:
- return ret;
- }
- /*
- * Remove an entry from the EMD file.
- * Return 0 if OK, a negative error code otherwise.
- *
- * Complete the information missing in info.
- */
- int umsdos_delentry (struct dentry *parent, struct umsdos_info *info, int isdir)
- {
- int ret;
- struct dentry *demd = umsdos_get_emd_dentry(parent);
- ret = PTR_ERR(demd);
- if (IS_ERR(demd))
- goto out;
- ret = umsdos_find (demd, info);
- if (ret)
- goto out;
- if (info->entry.name_len == 0)
- goto out;
- if ((isdir != 0) != (S_ISDIR (info->entry.mode) != 0)) {
- if (S_ISDIR (info->entry.mode)) {
- ret = -EISDIR;
- } else {
- ret = -ENOTDIR;
- }
- goto out;
- }
- ret = umsdos_writeentry (parent, info, 1);
- out:
- return ret;
- }
- /*
- * Verify that an EMD directory is empty.
- * Return:
- * 0 if not empty,
- * 1 if empty (except for EMD file),
- * 2 if empty or no EMD file.
- */
- int umsdos_isempty (struct dentry *dentry)
- {
- struct dentry *demd;
- int ret = 2;
- loff_t pos = 0;
- demd = umsdos_get_emd_dentry(dentry);
- if (IS_ERR(demd))
- goto out;
- /* If the EMD file does not exist, it is certainly empty. :-) */
- if (!demd->d_inode)
- goto out_dput;
- ret = 1;
- while (pos < demd->d_inode->i_size) {
- struct umsdos_dirent entry;
- if (umsdos_emd_dir_readentry (demd, &pos, &entry) != 0) {
- ret = 0;
- break;
- }
- if (entry.name_len != 0) {
- ret = 0;
- break;
- }
- }
- out_dput:
- dput(demd);
- out:
- return ret;
- }
- /*
- * Locate an entry in a EMD directory.
- * Return 0 if OK, error code if not, generally -ENOENT.
- *
- * expect argument:
- * 0: anything
- * 1: file
- * 2: directory
- */
- int umsdos_findentry (struct dentry *parent, struct umsdos_info *info,
- int expect)
- {
- int ret;
- struct dentry *demd = umsdos_get_emd_dentry(parent);
- ret = PTR_ERR(demd);
- if (IS_ERR(demd))
- goto out;
- ret = umsdos_find (demd, info);
- if (ret)
- goto out;
- switch (expect) {
- case 1:
- if (S_ISDIR (info->entry.mode))
- ret = -EISDIR;
- break;
- case 2:
- if (!S_ISDIR (info->entry.mode))
- ret = -ENOTDIR;
- }
- out:
- Printk (("umsdos_findentry: returning %dn", ret));
- return ret;
- }