/* * linux/fs/umsdos/emd.c * * Written 1993 by Jacques Gelinas * * Extended MS-DOS directory handling functions */ #include #include #include #include #include #include #include #include #include #include #include static void copy_entry(struct umsdos_dirent *p, struct umsdos_dirent *q) { p->name_len = q->name_len; p->name[p->name_len]='\0'; 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=%d\n", 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/%s\n", 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=%d\n", 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*)((char*)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 %d\n", entry->name_len); p->name_len = 0; ret = -ENAMETOOLONG; /* notify umssync(8) code that something is wrong */ } 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,(char*)kmap(page2), recsize+offs-PAGE_CACHE_SIZE); kunmap(page2); page_cache_release(page2); } else memcpy(entry->spare,p->spare,((char*)p+recsize)-p->spare); copy_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/%s\n", 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, '\0', 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; p->name_len = entry->name_len; p->flags = entry->flags; p->nlink = cpu_to_le16(entry->nlink); p->uid = cpu_to_le16(entry->uid); p->gid = cpu_to_le16(entry->gid); p->atime = cpu_to_le32(entry->atime); p->mtime = cpu_to_le32(entry->mtime); p->ctime = cpu_to_le32(entry->ctime); p->rdev = cpu_to_le16(entry->rdev); p->mode = cpu_to_le16(entry->mode); memcpy(p->name,entry->name, (char *)(page_address(page) + PAGE_CACHE_SIZE) - p->spare); memcpy(page_address(page2), entry->spare+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; p->name_len = entry->name_len; p->flags = entry->flags; p->nlink = cpu_to_le16(entry->nlink); p->uid = cpu_to_le16(entry->uid); p->gid = cpu_to_le16(entry->gid); p->atime = cpu_to_le32(entry->atime); p->mtime = cpu_to_le32(entry->mtime); p->ctime = cpu_to_le32(entry->ctime); p->rdev = cpu_to_le16(entry->rdev); p->mode = cpu_to_le16(entry->mode); 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 write\n"); 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 = (char*)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 = (char*)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; copy_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 = %d\n", 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 %d\n", ret)); return ret; }