dir.c 11.2 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6
/*
 *  linux/fs/minix/dir.c
 *
 *  Copyright (C) 1991, 1992 Linus Torvalds
 *
 *  minix directory handling functions
7 8
 *
 *  Updated to filesystem version 3 by Daniel Aragones
Linus Torvalds's avatar
Linus Torvalds committed
9 10 11
 */

#include "minix.h"
12
#include <linux/buffer_head.h>
Linus Torvalds's avatar
Linus Torvalds committed
13
#include <linux/highmem.h>
14
#include <linux/swap.h>
Linus Torvalds's avatar
Linus Torvalds committed
15 16

typedef struct minix_dir_entry minix_dirent;
17
typedef struct minix3_dir_entry minix3_dirent;
Linus Torvalds's avatar
Linus Torvalds committed
18 19 20

static int minix_readdir(struct file *, void *, filldir_t);

21
const struct file_operations minix_dir_operations = {
Al Viro's avatar
Al Viro committed
22
	.llseek		= generic_file_llseek,
Linus Torvalds's avatar
Linus Torvalds committed
23 24
	.read		= generic_read_dir,
	.readdir	= minix_readdir,
25
	.fsync		= generic_file_fsync,
Linus Torvalds's avatar
Linus Torvalds committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
};

static inline void dir_put_page(struct page *page)
{
	kunmap(page);
	page_cache_release(page);
}

/*
 * Return the offset into page `page_nr' of the last valid
 * byte in that page, plus one.
 */
static unsigned
minix_last_byte(struct inode *inode, unsigned long page_nr)
{
	unsigned last_byte = PAGE_CACHE_SIZE;

	if (page_nr == (inode->i_size >> PAGE_CACHE_SHIFT))
		last_byte = inode->i_size & (PAGE_CACHE_SIZE - 1);
	return last_byte;
}

static inline unsigned long dir_pages(struct inode *inode)
{
	return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
}

53
static int dir_commit_chunk(struct page *page, loff_t pos, unsigned len)
Linus Torvalds's avatar
Linus Torvalds committed
54
{
55 56
	struct address_space *mapping = page->mapping;
	struct inode *dir = mapping->host;
Linus Torvalds's avatar
Linus Torvalds committed
57
	int err = 0;
58 59 60 61 62 63
	block_write_end(NULL, mapping, pos, len, len, page, NULL);

	if (pos+len > dir->i_size) {
		i_size_write(dir, pos+len);
		mark_inode_dirty(dir);
	}
Linus Torvalds's avatar
Linus Torvalds committed
64 65 66 67 68 69 70 71 72 73
	if (IS_DIRSYNC(dir))
		err = write_one_page(page, 1);
	else
		unlock_page(page);
	return err;
}

static struct page * dir_get_page(struct inode *dir, unsigned long n)
{
	struct address_space *mapping = dir->i_mapping;
74
	struct page *page = read_mapping_page(mapping, n, NULL);
75
	if (!IS_ERR(page))
Linus Torvalds's avatar
Linus Torvalds committed
76 77 78 79 80 81 82 83 84 85 86 87
		kmap(page);
	return page;
}

static inline void *minix_next_entry(void *de, struct minix_sb_info *sbi)
{
	return (void*)((char*)de + sbi->s_dirsize);
}

static int minix_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
	unsigned long pos = filp->f_pos;
Al Viro's avatar
Al Viro committed
88
	struct inode *inode = file_inode(filp);
Linus Torvalds's avatar
Linus Torvalds committed
89 90 91 92 93 94
	struct super_block *sb = inode->i_sb;
	unsigned offset = pos & ~PAGE_CACHE_MASK;
	unsigned long n = pos >> PAGE_CACHE_SHIFT;
	unsigned long npages = dir_pages(inode);
	struct minix_sb_info *sbi = minix_sb(sb);
	unsigned chunk_size = sbi->s_dirsize;
95 96
	char *name;
	__u32 inumber;
Linus Torvalds's avatar
Linus Torvalds committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110

	pos = (pos + chunk_size-1) & ~(chunk_size-1);
	if (pos >= inode->i_size)
		goto done;

	for ( ; n < npages; n++, offset = 0) {
		char *p, *kaddr, *limit;
		struct page *page = dir_get_page(inode, n);

		if (IS_ERR(page))
			continue;
		kaddr = (char *)page_address(page);
		p = kaddr+offset;
		limit = kaddr + minix_last_byte(inode, n) - chunk_size;
111 112 113 114 115 116 117 118 119 120 121
		for ( ; p <= limit; p = minix_next_entry(p, sbi)) {
			if (sbi->s_version == MINIX_V3) {
				minix3_dirent *de3 = (minix3_dirent *)p;
				name = de3->name;
				inumber = de3->inode;
	 		} else {
				minix_dirent *de = (minix_dirent *)p;
				name = de->name;
				inumber = de->inode;
			}
			if (inumber) {
Linus Torvalds's avatar
Linus Torvalds committed
122 123
				int over;

124
				unsigned l = strnlen(name, sbi->s_namelen);
Linus Torvalds's avatar
Linus Torvalds committed
125
				offset = p - kaddr;
126 127 128
				over = filldir(dirent, name, l,
					(n << PAGE_CACHE_SHIFT) | offset,
					inumber, DT_UNKNOWN);
Linus Torvalds's avatar
Linus Torvalds committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
				if (over) {
					dir_put_page(page);
					goto done;
				}
			}
		}
		dir_put_page(page);
	}

done:
	filp->f_pos = (n << PAGE_CACHE_SHIFT) | offset;
	return 0;
}

static inline int namecompare(int len, int maxlen,
	const char * name, const char * buffer)
{
	if (len < maxlen && buffer[len])
		return 0;
	return !memcmp(name, buffer, len);
}

/*
 *	minix_find_entry()
 *
 * finds an entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as a parameter - res_dir). It does NOT read the inode of the
 * entry - you'll have to do that yourself if you want to.
 */
minix_dirent *minix_find_entry(struct dentry *dentry, struct page **res_page)
{
	const char * name = dentry->d_name.name;
	int namelen = dentry->d_name.len;
	struct inode * dir = dentry->d_parent->d_inode;
	struct super_block * sb = dir->i_sb;
	struct minix_sb_info * sbi = minix_sb(sb);
	unsigned long n;
	unsigned long npages = dir_pages(dir);
	struct page *page = NULL;
169
	char *p;
Linus Torvalds's avatar
Linus Torvalds committed
170

171 172
	char *namx;
	__u32 inumber;
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175
	*res_page = NULL;

	for (n = 0; n < npages; n++) {
176 177
		char *kaddr, *limit;

Linus Torvalds's avatar
Linus Torvalds committed
178 179 180 181 182
		page = dir_get_page(dir, n);
		if (IS_ERR(page))
			continue;

		kaddr = (char*)page_address(page);
183 184 185 186 187 188 189 190 191 192 193 194
		limit = kaddr + minix_last_byte(dir, n) - sbi->s_dirsize;
		for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
			if (sbi->s_version == MINIX_V3) {
				minix3_dirent *de3 = (minix3_dirent *)p;
				namx = de3->name;
				inumber = de3->inode;
 			} else {
				minix_dirent *de = (minix_dirent *)p;
				namx = de->name;
				inumber = de->inode;
			}
			if (!inumber)
Linus Torvalds's avatar
Linus Torvalds committed
195
				continue;
196
			if (namecompare(namelen, sbi->s_namelen, name, namx))
Linus Torvalds's avatar
Linus Torvalds committed
197 198 199 200 201 202 203 204
				goto found;
		}
		dir_put_page(page);
	}
	return NULL;

found:
	*res_page = page;
205
	return (minix_dirent *)p;
Linus Torvalds's avatar
Linus Torvalds committed
206 207 208 209 210 211 212 213 214 215 216 217
}

int minix_add_link(struct dentry *dentry, struct inode *inode)
{
	struct inode *dir = dentry->d_parent->d_inode;
	const char * name = dentry->d_name.name;
	int namelen = dentry->d_name.len;
	struct super_block * sb = dir->i_sb;
	struct minix_sb_info * sbi = minix_sb(sb);
	struct page *page = NULL;
	unsigned long npages = dir_pages(dir);
	unsigned long n;
218 219 220
	char *kaddr, *p;
	minix_dirent *de;
	minix3_dirent *de3;
221
	loff_t pos;
Linus Torvalds's avatar
Linus Torvalds committed
222
	int err;
223 224
	char *namx = NULL;
	__u32 inumber;
Linus Torvalds's avatar
Linus Torvalds committed
225 226 227 228 229 230 231

	/*
	 * We take care of directory expansion in the same loop
	 * This code plays outside i_size, so it locks the page
	 * to protect that region.
	 */
	for (n = 0; n <= npages; n++) {
232
		char *limit, *dir_end;
Linus Torvalds's avatar
Linus Torvalds committed
233 234 235 236 237 238 239 240

		page = dir_get_page(dir, n);
		err = PTR_ERR(page);
		if (IS_ERR(page))
			goto out;
		lock_page(page);
		kaddr = (char*)page_address(page);
		dir_end = kaddr + minix_last_byte(dir, n);
241 242 243 244 245 246 247 248 249 250 251 252
		limit = kaddr + PAGE_CACHE_SIZE - sbi->s_dirsize;
		for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
			de = (minix_dirent *)p;
			de3 = (minix3_dirent *)p;
			if (sbi->s_version == MINIX_V3) {
				namx = de3->name;
				inumber = de3->inode;
		 	} else {
  				namx = de->name;
				inumber = de->inode;
			}
			if (p == dir_end) {
Linus Torvalds's avatar
Linus Torvalds committed
253
				/* We hit i_size */
254 255 256 257
				if (sbi->s_version == MINIX_V3)
					de3->inode = 0;
		 		else
					de->inode = 0;
Linus Torvalds's avatar
Linus Torvalds committed
258 259
				goto got_it;
			}
260
			if (!inumber)
Linus Torvalds's avatar
Linus Torvalds committed
261 262
				goto got_it;
			err = -EEXIST;
263
			if (namecompare(namelen, sbi->s_namelen, name, namx))
Linus Torvalds's avatar
Linus Torvalds committed
264 265 266 267 268 269 270 271 272
				goto out_unlock;
		}
		unlock_page(page);
		dir_put_page(page);
	}
	BUG();
	return -EINVAL;

got_it:
273
	pos = page_offset(page) + p - (char *)page_address(page);
274
	err = minix_prepare_chunk(page, pos, sbi->s_dirsize);
Linus Torvalds's avatar
Linus Torvalds committed
275 276
	if (err)
		goto out_unlock;
277 278 279 280 281 282 283 284
	memcpy (namx, name, namelen);
	if (sbi->s_version == MINIX_V3) {
		memset (namx + namelen, 0, sbi->s_dirsize - namelen - 4);
		de3->inode = inode->i_ino;
	} else {
		memset (namx + namelen, 0, sbi->s_dirsize - namelen - 2);
		de->inode = inode->i_ino;
	}
285
	err = dir_commit_chunk(page, pos, sbi->s_dirsize);
Linus Torvalds's avatar
Linus Torvalds committed
286 287 288 289 290 291 292 293 294 295 296 297 298
	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
	mark_inode_dirty(dir);
out_put:
	dir_put_page(page);
out:
	return err;
out_unlock:
	unlock_page(page);
	goto out_put;
}

int minix_delete_entry(struct minix_dir_entry *de, struct page *page)
{
299
	struct inode *inode = page->mapping->host;
Linus Torvalds's avatar
Linus Torvalds committed
300
	char *kaddr = page_address(page);
301
	loff_t pos = page_offset(page) + (char*)de - kaddr;
302 303
	struct minix_sb_info *sbi = minix_sb(inode->i_sb);
	unsigned len = sbi->s_dirsize;
Linus Torvalds's avatar
Linus Torvalds committed
304 305 306
	int err;

	lock_page(page);
307
	err = minix_prepare_chunk(page, pos, len);
Linus Torvalds's avatar
Linus Torvalds committed
308
	if (err == 0) {
309 310 311 312
		if (sbi->s_version == MINIX_V3)
			((minix3_dirent *) de)->inode = 0;
		else
			de->inode = 0;
313
		err = dir_commit_chunk(page, pos, len);
Linus Torvalds's avatar
Linus Torvalds committed
314 315 316 317 318 319 320 321 322 323 324
	} else {
		unlock_page(page);
	}
	dir_put_page(page);
	inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
	mark_inode_dirty(inode);
	return err;
}

int minix_make_empty(struct inode *inode, struct inode *dir)
{
325
	struct page *page = grab_cache_page(inode->i_mapping, 0);
326
	struct minix_sb_info *sbi = minix_sb(inode->i_sb);
Linus Torvalds's avatar
Linus Torvalds committed
327 328 329 330 331
	char *kaddr;
	int err;

	if (!page)
		return -ENOMEM;
332
	err = minix_prepare_chunk(page, 0, 2 * sbi->s_dirsize);
Linus Torvalds's avatar
Linus Torvalds committed
333 334 335 336 337
	if (err) {
		unlock_page(page);
		goto fail;
	}

338
	kaddr = kmap_atomic(page);
Linus Torvalds's avatar
Linus Torvalds committed
339 340
	memset(kaddr, 0, PAGE_CACHE_SIZE);

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	if (sbi->s_version == MINIX_V3) {
		minix3_dirent *de3 = (minix3_dirent *)kaddr;

		de3->inode = inode->i_ino;
		strcpy(de3->name, ".");
		de3 = minix_next_entry(de3, sbi);
		de3->inode = dir->i_ino;
		strcpy(de3->name, "..");
	} else {
		minix_dirent *de = (minix_dirent *)kaddr;

		de->inode = inode->i_ino;
		strcpy(de->name, ".");
		de = minix_next_entry(de, sbi);
		de->inode = dir->i_ino;
		strcpy(de->name, "..");
	}
358
	kunmap_atomic(kaddr);
Linus Torvalds's avatar
Linus Torvalds committed
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373

	err = dir_commit_chunk(page, 0, 2 * sbi->s_dirsize);
fail:
	page_cache_release(page);
	return err;
}

/*
 * routine to check that the specified directory is empty (for rmdir)
 */
int minix_empty_dir(struct inode * inode)
{
	struct page *page = NULL;
	unsigned long i, npages = dir_pages(inode);
	struct minix_sb_info *sbi = minix_sb(inode->i_sb);
374 375
	char *name;
	__u32 inumber;
Linus Torvalds's avatar
Linus Torvalds committed
376 377

	for (i = 0; i < npages; i++) {
378
		char *p, *kaddr, *limit;
Linus Torvalds's avatar
Linus Torvalds committed
379

380
		page = dir_get_page(inode, i);
Linus Torvalds's avatar
Linus Torvalds committed
381 382 383 384
		if (IS_ERR(page))
			continue;

		kaddr = (char *)page_address(page);
385 386 387 388 389 390 391 392 393 394 395
		limit = kaddr + minix_last_byte(inode, i) - sbi->s_dirsize;
		for (p = kaddr; p <= limit; p = minix_next_entry(p, sbi)) {
			if (sbi->s_version == MINIX_V3) {
				minix3_dirent *de3 = (minix3_dirent *)p;
				name = de3->name;
				inumber = de3->inode;
			} else {
				minix_dirent *de = (minix_dirent *)p;
				name = de->name;
				inumber = de->inode;
			}
Linus Torvalds's avatar
Linus Torvalds committed
396

397
			if (inumber != 0) {
Linus Torvalds's avatar
Linus Torvalds committed
398
				/* check for . and .. */
399
				if (name[0] != '.')
Linus Torvalds's avatar
Linus Torvalds committed
400
					goto not_empty;
401 402
				if (!name[1]) {
					if (inumber != inode->i_ino)
Linus Torvalds's avatar
Linus Torvalds committed
403
						goto not_empty;
404
				} else if (name[1] != '.')
Linus Torvalds's avatar
Linus Torvalds committed
405
					goto not_empty;
406
				else if (name[2])
Linus Torvalds's avatar
Linus Torvalds committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
					goto not_empty;
			}
		}
		dir_put_page(page);
	}
	return 1;

not_empty:
	dir_put_page(page);
	return 0;
}

/* Releases the page */
void minix_set_link(struct minix_dir_entry *de, struct page *page,
	struct inode *inode)
{
423
	struct inode *dir = page->mapping->host;
Linus Torvalds's avatar
Linus Torvalds committed
424
	struct minix_sb_info *sbi = minix_sb(dir->i_sb);
425 426
	loff_t pos = page_offset(page) +
			(char *)de-(char*)page_address(page);
Linus Torvalds's avatar
Linus Torvalds committed
427 428 429
	int err;

	lock_page(page);
430

431
	err = minix_prepare_chunk(page, pos, sbi->s_dirsize);
Linus Torvalds's avatar
Linus Torvalds committed
432
	if (err == 0) {
433 434 435 436
		if (sbi->s_version == MINIX_V3)
			((minix3_dirent *) de)->inode = inode->i_ino;
		else
			de->inode = inode->i_ino;
437
		err = dir_commit_chunk(page, pos, sbi->s_dirsize);
Linus Torvalds's avatar
Linus Torvalds committed
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
	} else {
		unlock_page(page);
	}
	dir_put_page(page);
	dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
	mark_inode_dirty(dir);
}

struct minix_dir_entry * minix_dotdot (struct inode *dir, struct page **p)
{
	struct page *page = dir_get_page(dir, 0);
	struct minix_sb_info *sbi = minix_sb(dir->i_sb);
	struct minix_dir_entry *de = NULL;

	if (!IS_ERR(page)) {
		de = minix_next_entry(page_address(page), sbi);
		*p = page;
	}
	return de;
}

ino_t minix_inode_by_name(struct dentry *dentry)
{
	struct page *page;
	struct minix_dir_entry *de = minix_find_entry(dentry, &page);
	ino_t res = 0;

	if (de) {
466 467 468 469 470 471 472 473
		struct address_space *mapping = page->mapping;
		struct inode *inode = mapping->host;
		struct minix_sb_info *sbi = minix_sb(inode->i_sb);

		if (sbi->s_version == MINIX_V3)
			res = ((minix3_dirent *) de)->inode;
		else
			res = de->inode;
Linus Torvalds's avatar
Linus Torvalds committed
474 475 476 477
		dir_put_page(page);
	}
	return res;
}