| /* |
| * linux/fs/isofs/dir.c |
| * |
| * (C) 1992 Eric Youngdale Modified for ISO9660 filesystem. |
| * |
| * (C) 1991 Linus Torvalds - minix filesystem |
| * |
| * isofs directory handling functions |
| */ |
| |
| #include <linux/errno.h> |
| |
| #include <asm/segment.h> |
| |
| #include <linux/fs.h> |
| #include <linux/iso_fs.h> |
| #include <linux/kernel.h> |
| #include <linux/stat.h> |
| #include <linux/string.h> |
| #include <linux/mm.h> |
| |
| static int isofs_readdir(struct inode *, struct file *, struct dirent *, int); |
| |
| static struct file_operations isofs_dir_operations = { |
| NULL, /* lseek - default */ |
| NULL, /* read */ |
| NULL, /* write - bad */ |
| isofs_readdir, /* readdir */ |
| NULL, /* select - default */ |
| NULL, /* ioctl - default */ |
| NULL, /* no special open code */ |
| NULL, /* no special release code */ |
| NULL /* fsync */ |
| }; |
| |
| /* |
| * directories can handle most operations... |
| */ |
| struct inode_operations isofs_dir_inode_operations = { |
| &isofs_dir_operations, /* default directory file-ops */ |
| NULL, /* create */ |
| isofs_lookup, /* lookup */ |
| NULL, /* link */ |
| NULL, /* unlink */ |
| NULL, /* symlink */ |
| NULL, /* mkdir */ |
| NULL, /* rmdir */ |
| NULL, /* mknod */ |
| NULL, /* rename */ |
| NULL, /* readlink */ |
| NULL, /* follow_link */ |
| isofs_bmap, /* bmap */ |
| NULL, /* truncate */ |
| NULL /* permission */ |
| }; |
| |
| /* This is used to speed up lookup. Without this we would need to |
| make a linear search of the directory to find the file that the |
| directory read just returned. This is a single element cache. */ |
| |
| struct lookup_cache cache = {0,}; |
| |
| static int isofs_readdir(struct inode * inode, struct file * filp, |
| struct dirent * dirent, int count) |
| { |
| unsigned int block,offset,i, j; |
| char c = 0; |
| int inode_number; |
| struct buffer_head * bh; |
| void * cpnt = NULL; |
| unsigned int old_offset; |
| int dlen, rrflag; |
| char * dpnt; |
| struct iso_directory_record * de; |
| |
| if (!inode || !S_ISDIR(inode->i_mode)) |
| return -EBADF; |
| |
| offset = filp->f_pos & (ISOFS_BUFFER_SIZE(inode) - 1); |
| block = isofs_bmap(inode,(filp->f_pos)>>ISOFS_BUFFER_BITS(inode)); |
| if (!block || !(bh = bread(inode->i_dev,block,ISOFS_BUFFER_SIZE(inode)))) |
| return 0; |
| |
| while (filp->f_pos < inode->i_size) { |
| #ifdef DEBUG |
| printk("Block, offset: %x %x %x\n",block, offset, filp->f_pos); |
| #endif |
| de = (struct iso_directory_record *) (offset + bh->b_data); |
| inode_number = (block << ISOFS_BUFFER_BITS(inode))+(offset & (ISOFS_BUFFER_SIZE(inode) - 1)); |
| |
| /* If the length byte is zero, we should move on to the next CDROM sector. |
| If we are at the end of the directory, we kick out of the while loop. */ |
| |
| if (*((char*) de) == 0) { |
| brelse(bh); |
| offset = 0; |
| filp->f_pos =(filp->f_pos & ~(ISOFS_BLOCK_SIZE - 1))+ISOFS_BLOCK_SIZE; |
| block = isofs_bmap(inode,(filp->f_pos)>>ISOFS_BUFFER_BITS(inode)); |
| if (!block || !(bh = bread(inode->i_dev,block,ISOFS_BUFFER_SIZE(inode)))) |
| return 0; |
| continue; |
| } |
| |
| /* Make sure that the entire directory record is in the current bh block. |
| If not, we malloc a buffer, and put the two halves together, so that |
| we can cleanly read the block */ |
| |
| old_offset = offset; |
| offset += *((unsigned char*) de); |
| filp->f_pos += *((unsigned char*) de); |
| |
| if (offset >= ISOFS_BUFFER_SIZE(inode)) { |
| cpnt = kmalloc(1 << ISOFS_BLOCK_BITS, GFP_KERNEL); |
| memcpy(cpnt, bh->b_data, ISOFS_BUFFER_SIZE(inode)); |
| de = (struct iso_directory_record *) (old_offset + cpnt); |
| brelse(bh); |
| offset = filp->f_pos & (ISOFS_BUFFER_SIZE(inode) - 1); |
| block = isofs_bmap(inode,(filp->f_pos)>> ISOFS_BUFFER_BITS(inode)); |
| if (!block || !(bh = bread(inode->i_dev,block,ISOFS_BUFFER_SIZE(inode)))) |
| return 0; |
| memcpy(cpnt+ISOFS_BUFFER_SIZE(inode), bh->b_data, ISOFS_BUFFER_SIZE(inode)); |
| } |
| |
| /* Handle the case of the '.' directory */ |
| |
| rrflag = 0; |
| i = 1; |
| if (de->name_len[0] == 1 && de->name[0] == 0) { |
| put_fs_byte('.',dirent->d_name); |
| inode_number = inode->i_ino; |
| dpnt = "."; |
| } |
| |
| /* Handle the case of the '..' directory */ |
| |
| else if (de->name_len[0] == 1 && de->name[0] == 1) { |
| put_fs_byte('.',dirent->d_name); |
| put_fs_byte('.',dirent->d_name+1); |
| i = 2; |
| dpnt = ".."; |
| if((inode->i_sb->u.isofs_sb.s_firstdatazone << ISOFS_BUFFER_BITS(inode)) != inode->i_ino) |
| inode_number = inode->u.isofs_i.i_backlink; |
| else |
| inode_number = inode->i_ino; |
| |
| /* This should never happen, but who knows. Try to be forgiving */ |
| if(inode_number == -1) { |
| inode_number = |
| isofs_lookup_grandparent(inode, |
| find_rock_ridge_relocation(de, inode)); |
| if(inode_number == -1){ /* Should never happen */ |
| printk("Backlink not properly set.\n"); |
| goto out; |
| }; |
| } |
| } |
| |
| /* Handle everything else. Do name translation if there |
| is no Rock Ridge NM field. */ |
| |
| else { |
| dlen = de->name_len[0]; |
| dpnt = de->name; |
| i = dlen; |
| rrflag = get_rock_ridge_filename(de, &dpnt, &dlen, inode); |
| if (rrflag) { |
| if (rrflag == -1) { /* This is a rock ridge reloc dir */ |
| if (cpnt) { |
| kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS); |
| cpnt = NULL; |
| }; |
| continue; |
| }; |
| i = dlen; |
| } |
| else |
| if(inode->i_sb->u.isofs_sb.s_mapping == 'n') |
| for (i = 0; i < dlen && i < NAME_MAX; i++) { |
| if (!(c = dpnt[i])) break; |
| if (c >= 'A' && c <= 'Z') c |= 0x20; /* lower case */ |
| if (c == ';' && i == dlen-2 && de->name[i+1] == '1') |
| break; /* Drop trailing ';1' */ |
| if (c == ';') c = '.'; /* Convert remaining ';' to '.' */ |
| dpnt[i] = c; |
| }; |
| |
| for(j=0; j<i; j++) |
| put_fs_byte(dpnt[j],j+dirent->d_name); /* And save it */ |
| }; |
| #if 0 |
| printk("Nchar: %d\n",i); |
| #endif |
| |
| if (i) { |
| while (cache.lock); |
| cache.lock = 1; |
| cache.ino = inode_number; |
| cache.dir = inode->i_ino; |
| cache.dev = inode->i_dev; |
| strncpy(cache.filename, dpnt, i); |
| cache.dlen = dlen; |
| cache.lock = 0; |
| }; |
| |
| if (rrflag) kfree(dpnt); |
| if (cpnt) { |
| kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS); |
| cpnt = NULL; |
| }; |
| |
| if (i) { |
| put_fs_long(inode_number, &dirent->d_ino); |
| put_fs_byte(0,i+dirent->d_name); |
| put_fs_word(i,&dirent->d_reclen); |
| brelse(bh); |
| return i; |
| } |
| } |
| /* We go here for any condition we cannot handle. We also drop through |
| to here at the end of the directory. */ |
| out: |
| if (cpnt) |
| kfree_s(cpnt, 1 << ISOFS_BLOCK_BITS); |
| brelse(bh); |
| return 0; |
| } |
| |
| |
| |