| /* |
| * linux/fs/msdos/misc.c |
| * |
| * Written 1992,1993 by Werner Almesberger |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/msdos_fs.h> |
| #include <linux/sched.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/stat.h> |
| |
| /* Well-known binary file extensions */ |
| |
| static char bin_extensions[] = |
| "EXECOMBINAPPSYSDRVOVLOVROBJLIBDLLPIF" /* program code */ |
| "ARCZIPLHALZHZOOTARZ ARJ" /* common archivers */ |
| "TZ TAZTZPTPZ" /* abbreviations of tar.Z and tar.zip */ |
| "GIFBMPTIFGL JPGPCX" /* graphics */ |
| "TFMVF GF PK PXLDVI"; /* TeX */ |
| |
| |
| /* |
| * fs_panic reports a severe file system problem and sets the file system |
| * read-only. The file system can be made writable again by remounting it. |
| */ |
| |
| void fs_panic(struct super_block *s,char *msg) |
| { |
| int not_ro; |
| |
| not_ro = !(s->s_flags & MS_RDONLY); |
| if (not_ro) s->s_flags |= MS_RDONLY; |
| printk("Filesystem panic (dev 0x%04X, mounted on 0x%04X:%ld)\n %s\n", |
| s->s_dev,s->s_covered->i_dev,s->s_covered->i_ino,msg); |
| if (not_ro) |
| printk(" File system has been set read-only\n"); |
| } |
| |
| |
| /* |
| * is_binary selects optional text conversion based on the conversion mode and |
| * the extension part of the file name. |
| */ |
| |
| int is_binary(char conversion,char *extension) |
| { |
| char *walk; |
| |
| switch (conversion) { |
| case 'b': |
| return 1; |
| case 't': |
| return 0; |
| case 'a': |
| for (walk = bin_extensions; *walk; walk += 3) |
| if (!strncmp(extension,walk,3)) return 1; |
| return 0; |
| default: |
| printk("Invalid conversion mode - defaulting to " |
| "binary.\n"); |
| return 1; |
| } |
| } |
| |
| |
| /* File creation lock. This is system-wide to avoid deadlocks in rename. */ |
| /* (rename might deadlock before detecting cross-FS moves.) */ |
| |
| static struct wait_queue *creation_wait = NULL; |
| static creation_lock = 0; |
| |
| |
| void lock_creation(void) |
| { |
| while (creation_lock) sleep_on(&creation_wait); |
| creation_lock = 1; |
| } |
| |
| |
| void unlock_creation(void) |
| { |
| creation_lock = 0; |
| wake_up(&creation_wait); |
| } |
| |
| |
| void lock_fat(struct super_block *sb) |
| { |
| while (MSDOS_SB(sb)->fat_lock) sleep_on(&MSDOS_SB(sb)->fat_wait); |
| MSDOS_SB(sb)->fat_lock = 1; |
| } |
| |
| |
| void unlock_fat(struct super_block *sb) |
| { |
| MSDOS_SB(sb)->fat_lock = 0; |
| wake_up(&MSDOS_SB(sb)->fat_wait); |
| } |
| |
| |
| /* |
| * msdos_add_cluster tries to allocate a new cluster and adds it to the file |
| * represented by inode. The cluster is zero-initialized. |
| */ |
| |
| int msdos_add_cluster(struct inode *inode) |
| { |
| int count,nr,limit,last,current,sector; |
| void *data; |
| struct buffer_head *bh; |
| |
| if (inode->i_ino == MSDOS_ROOT_INO) return -ENOSPC; |
| if (!MSDOS_SB(inode->i_sb)->free_clusters) return -ENOSPC; |
| lock_fat(inode->i_sb); |
| limit = MSDOS_SB(inode->i_sb)->clusters; |
| nr = limit; /* to keep GCC happy */ |
| for (count = 0; count < limit; count++) { |
| nr = ((count+MSDOS_SB(inode->i_sb)->prev_free) % limit)+2; |
| if (fat_access(inode->i_sb,nr,-1) == 0) break; |
| } |
| #ifdef DEBUG |
| printk("free cluster: %d\n",nr); |
| #endif |
| MSDOS_SB(inode->i_sb)->prev_free = (count+MSDOS_SB(inode->i_sb)-> |
| prev_free+1) % limit; |
| if (count >= limit) { |
| MSDOS_SB(inode->i_sb)->free_clusters = 0; |
| unlock_fat(inode->i_sb); |
| return -ENOSPC; |
| } |
| fat_access(inode->i_sb,nr,MSDOS_SB(inode->i_sb)->fat_bits == 12 ? |
| 0xff8 : 0xfff8); |
| if (MSDOS_SB(inode->i_sb)->free_clusters != -1) |
| MSDOS_SB(inode->i_sb)->free_clusters--; |
| unlock_fat(inode->i_sb); |
| #ifdef DEBUG |
| printk("set to %x\n",fat_access(inode->i_sb,nr,-1)); |
| #endif |
| last = 0; |
| if ((current = MSDOS_I(inode)->i_start) != 0) { |
| cache_lookup(inode,INT_MAX,&last,¤t); |
| while (current && current != -1) |
| if (!(current = fat_access(inode->i_sb, |
| last = current,-1))) { |
| fs_panic(inode->i_sb,"File without EOF"); |
| return -ENOSPC; |
| } |
| } |
| #ifdef DEBUG |
| printk("last = %d\n",last); |
| #endif |
| if (last) fat_access(inode->i_sb,last,nr); |
| else { |
| MSDOS_I(inode)->i_start = nr; |
| inode->i_dirt = 1; |
| } |
| #ifdef DEBUG |
| if (last) printk("next set to %d\n",fat_access(inode->i_sb,last,-1)); |
| #endif |
| for (current = 0; current < MSDOS_SB(inode->i_sb)->cluster_size; |
| current++) { |
| sector = MSDOS_SB(inode->i_sb)->data_start+(nr-2)* |
| MSDOS_SB(inode->i_sb)->cluster_size+current; |
| #ifdef DEBUG |
| printk("zeroing sector %d\n",sector); |
| #endif |
| if (current < MSDOS_SB(inode->i_sb)->cluster_size-1 && |
| !(sector & 1)) { |
| if (!(bh = getblk(inode->i_dev,sector >> 1, |
| BLOCK_SIZE))) |
| printk("getblk failed\n"); |
| else { |
| memset(bh->b_data,0,BLOCK_SIZE); |
| bh->b_uptodate = 1; |
| } |
| current++; |
| } |
| else { |
| if (!(bh = msdos_sread(inode->i_dev,sector, |
| &data))) |
| printk("msdos_sread failed\n"); |
| else memset(data,0,SECTOR_SIZE); |
| } |
| if (bh) { |
| bh->b_dirt = 1; |
| brelse(bh); |
| } |
| } |
| inode->i_blocks += MSDOS_SB(inode->i_sb)->cluster_size; |
| if (S_ISDIR(inode->i_mode)) { |
| if (inode->i_size & (SECTOR_SIZE-1)) { |
| fs_panic(inode->i_sb,"Odd directory size"); |
| inode->i_size = (inode->i_size+SECTOR_SIZE) & |
| ~(SECTOR_SIZE-1); |
| } |
| inode->i_size += SECTOR_SIZE*MSDOS_SB(inode->i_sb)-> |
| cluster_size; |
| #ifdef DEBUG |
| printk("size is %d now (%x)\n",inode->i_size,inode); |
| #endif |
| inode->i_dirt = 1; |
| } |
| return 0; |
| } |
| |
| |
| /* Linear day numbers of the respective 1sts in non-leap years. */ |
| |
| static int day_n[] = { 0,31,59,90,120,151,181,212,243,273,304,334,0,0,0,0 }; |
| /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */ |
| |
| |
| extern struct timezone sys_tz; |
| |
| |
| /* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */ |
| |
| int date_dos2unix(unsigned short time,unsigned short date) |
| { |
| int month,year,secs; |
| |
| month = ((date >> 5) & 15)-1; |
| year = date >> 9; |
| secs = (time & 31)*2+60*((time >> 5) & 63)+(time >> 11)*3600+86400* |
| ((date & 31)-1+day_n[month]+(year/4)+year*365-((year & 3) == 0 && |
| month < 2 ? 1 : 0)+3653); |
| /* days since 1.1.70 plus 80's leap day */ |
| secs += sys_tz.tz_minuteswest*60; |
| return secs; |
| } |
| |
| |
| /* Convert linear UNIX date to a MS-DOS time/date pair. */ |
| |
| void date_unix2dos(int unix_date,unsigned short *time, |
| unsigned short *date) |
| { |
| int day,year,nl_day,month; |
| |
| unix_date -= sys_tz.tz_minuteswest*60; |
| *time = (unix_date % 60)/2+(((unix_date/60) % 60) << 5)+ |
| (((unix_date/3600) % 24) << 11); |
| day = unix_date/86400-3652; |
| year = day/365; |
| if ((year+3)/4+365*year > day) year--; |
| day -= (year+3)/4+365*year; |
| if (day == 59 && !(year & 3)) { |
| nl_day = day; |
| month = 2; |
| } |
| else { |
| nl_day = (year & 3) || day <= 59 ? day : day-1; |
| for (month = 0; month < 12; month++) |
| if (day_n[month] > nl_day) break; |
| } |
| *date = nl_day-day_n[month-1]+1+(month << 5)+(year << 9); |
| } |
| |
| |
| /* Returns the inode number of the directory entry at offset pos. If bh is |
| non-NULL, it is brelse'd before. Pos is incremented. The buffer header is |
| returned in bh. */ |
| |
| int msdos_get_entry(struct inode *dir, off_t *pos,struct buffer_head **bh, |
| struct msdos_dir_entry **de) |
| { |
| int sector,offset; |
| void *data; |
| |
| while (1) { |
| offset = *pos; |
| if ((sector = msdos_smap(dir,offset >> SECTOR_BITS)) == -1) |
| return -1; |
| if (!sector) |
| return -1; /* beyond EOF */ |
| *pos += sizeof(struct msdos_dir_entry); |
| if (*bh) |
| brelse(*bh); |
| if (!(*bh = msdos_sread(dir->i_dev,sector,&data))) { |
| printk("Directory sread (sector %d) failed\n",sector); |
| continue; |
| } |
| *de = (struct msdos_dir_entry *) (data+(offset & |
| (SECTOR_SIZE-1))); |
| return (sector << MSDOS_DPS_BITS)+((offset & (SECTOR_SIZE-1)) >> |
| MSDOS_DIR_BITS); |
| } |
| } |
| |
| |
| /* |
| * Now an ugly part: this set of directory scan routines works on clusters |
| * rather than on inodes and sectors. They are necessary to locate the '..' |
| * directory "inode". raw_scan_sector operates in four modes: |
| * |
| * name number ino action |
| * -------- -------- -------- ------------------------------------------------- |
| * non-NULL - X Find an entry with that name |
| * NULL non-NULL non-NULL Find an entry whose data starts at *number |
| * NULL non-NULL NULL Count subdirectories in *number. (*) |
| * NULL NULL non-NULL Find an empty entry |
| * |
| * (*) The return code should be ignored. It DOES NOT indicate success or |
| * failure. *number has to be initialized to zero. |
| * |
| * - = not used, X = a value is returned unless NULL |
| * |
| * If res_bh is non-NULL, the buffer is not deallocated but returned to the |
| * caller on success. res_de is set accordingly. |
| * |
| * If cont is non-zero, raw_found continues with the entry after the one |
| * res_bh/res_de point to. |
| */ |
| |
| |
| #define RSS_NAME /* search for name */ \ |
| done = !strncmp(data[entry].name,name,MSDOS_NAME) && \ |
| !(data[entry].attr & ATTR_VOLUME); |
| |
| #define RSS_START /* search for start cluster */ \ |
| done = !IS_FREE(data[entry].name) && CF_LE_W(data[entry].start) == *number; |
| |
| #define RSS_FREE /* search for free entry */ \ |
| { \ |
| done = IS_FREE(data[entry].name); \ |
| if (done) { \ |
| inode = iget(sb,sector*MSDOS_DPS+entry); \ |
| if (inode) { \ |
| /* Directory slots of busy deleted files aren't available yet. */ \ |
| done = !MSDOS_I(inode)->i_busy; \ |
| iput(inode); \ |
| } \ |
| } \ |
| } |
| |
| #define RSS_COUNT /* count subdirectories */ \ |
| { \ |
| done = 0; \ |
| if (!IS_FREE(data[entry].name) && (data[entry].attr & ATTR_DIR)) \ |
| (*number)++; \ |
| } |
| |
| static int raw_scan_sector(struct super_block *sb,int sector,char *name, |
| int *number,int *ino,struct buffer_head **res_bh, |
| struct msdos_dir_entry **res_de) |
| { |
| struct buffer_head *bh; |
| struct msdos_dir_entry *data; |
| struct inode *inode; |
| int entry,start,done; |
| |
| if (!(bh = msdos_sread(sb->s_dev,sector,(void **) &data))) return -EIO; |
| for (entry = 0; entry < MSDOS_DPS; entry++) { |
| if (name) RSS_NAME |
| else { |
| if (!ino) RSS_COUNT |
| else { |
| if (number) RSS_START |
| else RSS_FREE |
| } |
| } |
| if (done) { |
| if (ino) *ino = sector*MSDOS_DPS+entry; |
| start = CF_LE_W(data[entry].start); |
| if (!res_bh) brelse(bh); |
| else { |
| *res_bh = bh; |
| *res_de = &data[entry]; |
| } |
| return start; |
| } |
| } |
| brelse(bh); |
| return -ENOENT; |
| } |
| |
| |
| /* |
| * raw_scan_root performs raw_scan_sector on the root directory until the |
| * requested entry is found or the end of the directory is reached. |
| */ |
| |
| static int raw_scan_root(struct super_block *sb,char *name,int *number,int *ino, |
| struct buffer_head **res_bh,struct msdos_dir_entry **res_de) |
| { |
| int count,cluster; |
| |
| for (count = 0; count < MSDOS_SB(sb)->dir_entries/MSDOS_DPS; count++) { |
| if ((cluster = raw_scan_sector(sb,MSDOS_SB(sb)->dir_start+count, |
| name,number,ino,res_bh,res_de)) >= 0) return cluster; |
| } |
| return -ENOENT; |
| } |
| |
| |
| /* |
| * raw_scan_nonroot performs raw_scan_sector on a non-root directory until the |
| * requested entry is found or the end of the directory is reached. |
| */ |
| |
| static int raw_scan_nonroot(struct super_block *sb,int start,char *name, |
| int *number,int *ino,struct buffer_head **res_bh,struct msdos_dir_entry |
| **res_de) |
| { |
| int count,cluster; |
| |
| #ifdef DEBUG |
| printk("raw_scan_nonroot: start=%d\n",start); |
| #endif |
| do { |
| for (count = 0; count < MSDOS_SB(sb)->cluster_size; count++) { |
| if ((cluster = raw_scan_sector(sb,(start-2)* |
| MSDOS_SB(sb)->cluster_size+MSDOS_SB(sb)->data_start+ |
| count,name,number,ino,res_bh,res_de)) >= 0) |
| return cluster; |
| } |
| if (!(start = fat_access(sb,start,-1))) { |
| fs_panic(sb,"FAT error"); |
| break; |
| } |
| #ifdef DEBUG |
| printk("next start: %d\n",start); |
| #endif |
| } |
| while (start != -1); |
| return -ENOENT; |
| } |
| |
| |
| /* |
| * raw_scan performs raw_scan_sector on any sector. |
| * |
| * NOTE: raw_scan must not be used on a directory that is is the process of |
| * being created. |
| */ |
| |
| static int raw_scan(struct super_block *sb,int start,char *name,int *number, |
| int *ino,struct buffer_head **res_bh,struct msdos_dir_entry **res_de) |
| { |
| if (start) |
| return raw_scan_nonroot(sb,start,name,number,ino,res_bh,res_de); |
| else return raw_scan_root(sb,name,number,ino,res_bh,res_de); |
| } |
| |
| |
| /* |
| * msdos_parent_ino returns the inode number of the parent directory of dir. |
| * File creation has to be deferred while msdos_parent_ino is running to |
| * prevent renames. |
| */ |
| |
| int msdos_parent_ino(struct inode *dir,int locked) |
| { |
| static int zero = 0; |
| int error,current,prev,nr; |
| |
| if (!S_ISDIR(dir->i_mode)) panic("Non-directory fed to m_p_i"); |
| if (dir->i_ino == MSDOS_ROOT_INO) return dir->i_ino; |
| if (!locked) lock_creation(); /* prevent renames */ |
| if ((current = raw_scan(dir->i_sb,MSDOS_I(dir)->i_start,MSDOS_DOTDOT, |
| &zero,NULL,NULL,NULL)) < 0) { |
| if (!locked) unlock_creation(); |
| return current; |
| } |
| if (!current) nr = MSDOS_ROOT_INO; |
| else { |
| if ((prev = raw_scan(dir->i_sb,current,MSDOS_DOTDOT,&zero,NULL, |
| NULL,NULL)) < 0) { |
| if (!locked) unlock_creation(); |
| return prev; |
| } |
| if ((error = raw_scan(dir->i_sb,prev,NULL,¤t,&nr,NULL, |
| NULL)) < 0) { |
| if (!locked) unlock_creation(); |
| return error; |
| } |
| } |
| if (!locked) unlock_creation(); |
| return nr; |
| } |
| |
| |
| /* |
| * msdos_subdirs counts the number of sub-directories of dir. It can be run |
| * on directories being created. |
| */ |
| |
| int msdos_subdirs(struct inode *dir) |
| { |
| int count; |
| |
| count = 0; |
| if (dir->i_ino == MSDOS_ROOT_INO) |
| (void) raw_scan_root(dir->i_sb,NULL,&count,NULL,NULL,NULL); |
| else { |
| if (!MSDOS_I(dir)->i_start) return 0; /* in mkdir */ |
| else (void) raw_scan_nonroot(dir->i_sb,MSDOS_I(dir)->i_start, |
| NULL,&count,NULL,NULL,NULL); |
| } |
| return count; |
| } |
| |
| |
| /* |
| * Scans a directory for a given file (name points to its formatted name) or |
| * for an empty directory slot (name is NULL). Returns an error code or zero. |
| */ |
| |
| int msdos_scan(struct inode *dir,char *name,struct buffer_head **res_bh, |
| struct msdos_dir_entry **res_de,int *ino) |
| { |
| int res; |
| |
| if (name) |
| res = raw_scan(dir->i_sb,MSDOS_I(dir)->i_start,name,NULL,ino, |
| res_bh,res_de); |
| else res = raw_scan(dir->i_sb,MSDOS_I(dir)->i_start,NULL,NULL,ino, |
| res_bh,res_de); |
| return res < 0 ? res : 0; |
| } |