/* * linux/fs/umsdos/emd.c * * Written 1993 by Jacques Gelinas * * Extended MS-DOS directory handling functions */ #ifdef MODULE #include #endif #include #include #include #include #include #include #include #include #include #define PRINTK(x) #define Printk(x) printk x int umsdos_readdir_kmem( struct inode *inode, struct file *filp, struct dirent *dirent, int count) { int ret; int old_fs = get_fs(); set_fs (KERNEL_DS); ret = msdos_readdir(inode,filp,dirent,count); set_fs (old_fs); return ret; } /* Read a file into kernel space memory */ int umsdos_file_read_kmem( struct inode *inode, struct file *filp, char *buf, int count) { int ret; int old_fs = get_fs(); set_fs (KERNEL_DS); ret = msdos_file_read(inode,filp,buf,count); set_fs (old_fs); return ret; } /* Write to a file from kernel space */ int umsdos_file_write_kmem( struct inode *inode, struct file *filp, char *buf, int count) { int ret; int old_fs = get_fs(); set_fs (KERNEL_DS); ret = msdos_file_write(inode,filp,buf,count); set_fs (old_fs); return ret; } /* Write a block of bytes into one EMD file. The block of data is NOT in user space. Return 0 if ok, a negative error code if not. */ int umsdos_emd_dir_write ( struct inode *emd_dir, struct file *filp, char *buf, /* buffer in kernel memory, not in user space */ int count) { int written; filp->f_flags = 0; written = umsdos_file_write_kmem (emd_dir,filp,buf,count); return written != count ? -EIO : 0; } /* Read a block of bytes from one EMD file. The block of data is NOT in user space. Return 0 if ok, -EIO if any error. */ int umsdos_emd_dir_read ( struct inode *emd_dir, struct file *filp, char *buf, /* buffer in kernel memory, not in user space */ int count) { int ret = 0; int sizeread; filp->f_flags = 0; sizeread = umsdos_file_read_kmem (emd_dir,filp,buf,count); if (sizeread != count){ printk ("UMSDOS: problem with EMD file. Can't read\n"); ret = -EIO; } return ret; } /* Locate the EMD file in a directory and optionally, creates it. Return NULL if error. If ok, dir->u.umsdos_i.emd_inode */ struct inode *umsdos_emd_dir_lookup(struct inode *dir, int creat) { struct inode *ret = NULL; if (dir->u.umsdos_i.i_emd_dir != 0){ ret = iget (dir->i_sb,dir->u.umsdos_i.i_emd_dir); PRINTK (("deja trouve %d %x [%d] " ,dir->u.umsdos_i.i_emd_dir,ret,ret->i_count)); }else{ umsdos_real_lookup (dir,UMSDOS_EMD_FILE,UMSDOS_EMD_NAMELEN,&ret); PRINTK (("emd_dir_lookup ")); if (ret != NULL){ PRINTK (("Find --linux ")); dir->u.umsdos_i.i_emd_dir = ret->i_ino; }else if (creat){ int code; PRINTK (("avant create ")); dir->i_count++; code = msdos_create (dir,UMSDOS_EMD_FILE,UMSDOS_EMD_NAMELEN ,S_IFREG|0777,&ret); PRINTK (("Creat EMD code %d ret %x ",code,ret)); if (ret != NULL){ dir->u.umsdos_i.i_emd_dir = ret->i_ino; }else{ printk ("UMSDOS: Can't create EMD file\n"); } } } if (ret != NULL){ /* Disable UMSDOS_notify_change() for EMD file */ ret->u.umsdos_i.i_emd_owner = 0xffffffff; } return ret; } /* Read an entry from the EMD file. Support variable length record. Return -EIO if error, 0 if ok. */ int umsdos_emd_dir_readentry ( struct inode *emd_dir, struct file *filp, struct umsdos_dirent *entry) { int ret = umsdos_emd_dir_read(emd_dir,filp,(char*)entry,UMSDOS_REC_SIZE); if (ret == 0){ /* Variable size record. Maybe, we have to read some more */ int recsize = umsdos_evalrecsize (entry->name_len); if (recsize > UMSDOS_REC_SIZE){ ret = umsdos_emd_dir_read(emd_dir,filp ,((char*)entry)+UMSDOS_REC_SIZE,recsize - UMSDOS_REC_SIZE); } } return ret; } /* Write an entry in the EMD file. Return 0 if ok, -EIO if some error. */ int umsdos_writeentry ( struct inode *dir, struct inode *emd_dir, struct umsdos_info *info, int free_entry) /* This entry is deleted, so Write all 0's */ { int ret = 0; struct file filp; struct umsdos_dirent *entry = &info->entry; struct umsdos_dirent entry0; if (free_entry){ /* #Specification: EMD file / empty entries Unused entry in the EMD file are identify 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 file around. */ memset (entry->spare,0,sizeof(entry->spare)); } filp.f_pos = info->f_pos; filp.f_reada = 0; ret = umsdos_emd_dir_write(emd_dir,&filp,(char*)entry,info->recsize); if (ret != 0){ printk ("UMSDOS: problem with EMD file. Can't write\n"); }else{ dir->i_ctime = dir->i_mtime = CURRENT_TIME; dir->i_dirt = 1; } return ret; } #define CHUNK_SIZE (8*UMSDOS_REC_SIZE) struct find_buffer{ char buffer[CHUNK_SIZE]; int pos; /* read offset in buffer */ int size; /* Current size of buffer */ struct file filp; }; /* Fill the read buffer and take care of the byte remaining inside. Unread bytes are simply move to the beginning. Return -ENOENT if EOF, 0 if ok, a negative error code if any problem. */ static int umsdos_fillbuf ( struct inode *inode, struct find_buffer *buf) { int ret = -ENOENT; int mustmove = buf->size - buf->pos; int mustread; int remain; if (mustmove > 0){ memcpy (buf->buffer,buf->buffer+buf->pos,mustmove); } buf->pos = 0; mustread = CHUNK_SIZE - mustmove; remain = inode->i_size - buf->filp.f_pos; if (remain < mustread) mustread = remain; if (mustread > 0){ ret = umsdos_emd_dir_read (inode,&buf->filp,buf->buffer+mustmove ,mustread); if (ret == 0) buf->size = mustmove + mustread; }else if (mustmove){ buf->size = mustmove; ret = 0; } return ret; } /* 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). Caller must do iput on *pt_emd_dir. 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 call after this function since it rely on the f_pos field of info. */ static int umsdos_find ( struct inode *dir, struct umsdos_info *info, /* Hold name and name_len */ /* Will hold the entry found */ struct inode **pt_emd_dir) /* Will hold the emd_dir inode */ /* or NULL if not found */ { /* #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 is a single record, multiple contiguous record are allocated. */ int ret = -ENOENT; struct inode *emd_dir = umsdos_emd_dir_lookup(dir,1); if (emd_dir != NULL){ struct umsdos_dirent *entry = &info->entry; int recsize = info->recsize; struct { off_t posok; /* Position available to store the entry */ int found; /* A valid empty position has been found */ off_t one; /* One empty position -> maybe <- large enough */ int onesize; /* size of empty region starting at one */ }empty; /* Read several entries at a time to speed up the search */ struct find_buffer buf; buf.pos = 0; buf.size = 0; buf.filp.f_pos = 0; buf.filp.f_reada = 1; empty.found = 0; empty.posok = emd_dir->i_size; empty.onesize = 0; while (1){ struct umsdos_dirent *rentry = (struct umsdos_dirent*) (buf.buffer + buf.pos); int file_pos = buf.filp.f_pos - buf.size + buf.pos; if (buf.pos == buf.size){ ret = umsdos_fillbuf (emd_dir,&buf); if (ret < 0){ /* Not found, so note where it can be added */ info->f_pos = empty.posok; break; } }else if (rentry->name_len == 0){ /* We are looking for an empty section at least */ /* recsize large */ if (entry->name_len == 0){ info->f_pos = file_pos; ret = 0; break; }else if (!empty.found){ if (empty.onesize == 0){ /* This is the first empty record of a section */ empty.one = file_pos; } /* grow the empty section */ empty.onesize += UMSDOS_REC_SIZE; if (empty.onesize == recsize){ /* here is a large enough section */ empty.posok = empty.one; empty.found = 1; } } buf.pos += UMSDOS_REC_SIZE; }else{ int entry_size = umsdos_evalrecsize(rentry->name_len); if (buf.pos+entry_size > buf.size){ ret = umsdos_fillbuf (emd_dir,&buf); if (ret < 0){ /* Not found, so note where it can be added */ info->f_pos = empty.posok; break; } }else{ empty.onesize = 0; /* Reset the free slot search */ if (entry->name_len == rentry->name_len && memcmp(entry->name,rentry->name,rentry->name_len) ==0){ info->f_pos = file_pos; *entry = *rentry; ret = 0; break; }else{ buf.pos += entry_size; } } } } umsdos_manglename(info); } *pt_emd_dir = emd_dir; return ret; } /* Add a new entry in the emd file Return 0 if ok or a negative error code. Return -EEXIST if the entry already exist. Complete the information missing in info. */ int umsdos_newentry ( struct inode *dir, struct umsdos_info *info) { struct inode *emd_dir; int ret = umsdos_find (dir,info,&emd_dir); if (ret == 0){ ret = -EEXIST; }else if (ret == -ENOENT){ ret = umsdos_writeentry(dir,emd_dir,info,0); PRINTK (("umsdos_newentry EDM ret = %d\n",ret)); } iput (emd_dir); return ret; } /* Create a new hidden link. Return 0 if ok, an error code if not. */ int umsdos_newhidden ( struct inode *dir, struct umsdos_info *info) { struct inode *emd_dir; int ret; umsdos_parse ("..LINK",6,info); info->entry.name_len = 0; ret = umsdos_find (dir,info,&emd_dir); iput (emd_dir); if (ret == -ENOENT || ret == 0){ /* #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. */ info->entry.name_len = sprintf (info->entry.name,"..LINK%ld" ,info->f_pos); ret = 0; } 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 inode *dir, struct umsdos_info *info, int isdir) { struct inode *emd_dir; int ret = umsdos_find (dir,info,&emd_dir); if (ret == 0){ if (info->entry.name_len != 0){ if ((isdir != 0) != (S_ISDIR(info->entry.mode) != 0)){ if (S_ISDIR(info->entry.mode)){ ret = -EISDIR; }else{ ret = -ENOTDIR; } }else{ ret = umsdos_writeentry(dir,emd_dir,info,1); } } } iput(emd_dir); return ret; } /* Verify is a EMD directory is empty. Return 0 if not empty 1 if empty 2 if empty, no EMD file. */ int umsdos_isempty (struct inode *dir) { int ret = 2; struct inode *emd_dir = umsdos_emd_dir_lookup(dir,0); /* If the EMD file does not exist, it is certainly empty :-) */ if (emd_dir != NULL){ struct file filp; /* Find an empty slot */ filp.f_pos = 0; filp.f_reada = 1; filp.f_flags = O_RDONLY; ret = 1; while (filp.f_pos < emd_dir->i_size){ struct umsdos_dirent entry; if (umsdos_emd_dir_readentry(emd_dir,&filp,&entry)!=0){ ret = 0; break; }else if (entry.name_len != 0){ ret = 0; break; } } iput (emd_dir); } return ret; } /* Locate an entry in a EMD directory. Return 0 if ok, errcod if not, generally -ENOENT. */ int umsdos_findentry ( struct inode *dir, struct umsdos_info *info, int expect) /* 0: anything */ /* 1: file */ /* 2: directory */ { struct inode *emd_dir; int ret = umsdos_find (dir,info,&emd_dir); if (ret == 0){ if (expect != 0){ if (S_ISDIR(info->entry.mode)){ if (expect != 2) ret = -EISDIR; }else if (expect == 2){ ret = -ENOTDIR; } } } iput (emd_dir); return ret; }