inode.c 9.44 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10
/*
 *  linux/fs/adfs/inode.c
 *
 *  Copyright (C) 1997-1999 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/buffer_head.h>
11
#include <linux/writeback.h>
Linus Torvalds's avatar
Linus Torvalds committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include "adfs.h"

/*
 * Lookup/Create a block at offset 'block' into 'inode'.  We currently do
 * not support creation of new blocks, so we return -EIO for this case.
 */
static int
adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh,
	       int create)
{
	if (!create) {
		if (block >= inode->i_blocks)
			goto abort_toobig;

		block = __adfs_block_map(inode->i_sb, inode->i_ino, block);
		if (block)
			map_bh(bh, inode->i_sb, block);
		return 0;
	}
	/* don't support allocation of blocks yet */
	return -EIO;

abort_toobig:
	return 0;
}

static int adfs_writepage(struct page *page, struct writeback_control *wbc)
{
	return block_write_full_page(page, adfs_get_block, wbc);
}

static int adfs_readpage(struct file *file, struct page *page)
{
	return block_read_full_page(page, adfs_get_block);
}

Marco Stornelli's avatar
Marco Stornelli committed
48 49 50 51 52
static void adfs_write_failed(struct address_space *mapping, loff_t to)
{
	struct inode *inode = mapping->host;

	if (to > inode->i_size)
53
		truncate_pagecache(inode, inode->i_size);
Marco Stornelli's avatar
Marco Stornelli committed
54 55
}

56 57 58
static int adfs_write_begin(struct file *file, struct address_space *mapping,
			loff_t pos, unsigned len, unsigned flags,
			struct page **pagep, void **fsdata)
Linus Torvalds's avatar
Linus Torvalds committed
59
{
60 61
	int ret;

62
	*pagep = NULL;
63
	ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
64 65
				adfs_get_block,
				&ADFS_I(mapping->host)->mmu_private);
Marco Stornelli's avatar
Marco Stornelli committed
66 67
	if (unlikely(ret))
		adfs_write_failed(mapping, pos + len);
68 69

	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72 73 74 75 76
}

static sector_t _adfs_bmap(struct address_space *mapping, sector_t block)
{
	return generic_block_bmap(mapping, block, adfs_get_block);
}

77
static const struct address_space_operations adfs_aops = {
Linus Torvalds's avatar
Linus Torvalds committed
78 79
	.readpage	= adfs_readpage,
	.writepage	= adfs_writepage,
80 81
	.write_begin	= adfs_write_begin,
	.write_end	= generic_write_end,
Linus Torvalds's avatar
Linus Torvalds committed
82 83 84 85 86 87 88 89 90
	.bmap		= _adfs_bmap
};

/*
 * Convert ADFS attributes and filetype to Linux permission.
 */
static umode_t
adfs_atts2mode(struct super_block *sb, struct inode *inode)
{
91
	unsigned int attr = ADFS_I(inode)->attr;
Linus Torvalds's avatar
Linus Torvalds committed
92 93 94 95 96 97 98 99
	umode_t mode, rmask;
	struct adfs_sb_info *asb = ADFS_SB(sb);

	if (attr & ADFS_NDA_DIRECTORY) {
		mode = S_IRUGO & asb->s_owner_mask;
		return S_IFDIR | S_IXUGO | mode;
	}

100
	switch (ADFS_I(inode)->filetype) {
Linus Torvalds's avatar
Linus Torvalds committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 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
	case 0xfc0:	/* LinkFS */
		return S_IFLNK|S_IRWXUGO;

	case 0xfe6:	/* UnixExec */
		rmask = S_IRUGO | S_IXUGO;
		break;

	default:
		rmask = S_IRUGO;
	}

	mode = S_IFREG;

	if (attr & ADFS_NDA_OWNER_READ)
		mode |= rmask & asb->s_owner_mask;

	if (attr & ADFS_NDA_OWNER_WRITE)
		mode |= S_IWUGO & asb->s_owner_mask;

	if (attr & ADFS_NDA_PUBLIC_READ)
		mode |= rmask & asb->s_other_mask;

	if (attr & ADFS_NDA_PUBLIC_WRITE)
		mode |= S_IWUGO & asb->s_other_mask;
	return mode;
}

/*
 * Convert Linux permission to ADFS attribute.  We try to do the reverse
 * of atts2mode, but there is not a 1:1 translation.
 */
static int
adfs_mode2atts(struct super_block *sb, struct inode *inode)
{
	umode_t mode;
	int attr;
	struct adfs_sb_info *asb = ADFS_SB(sb);

	/* FIXME: should we be able to alter a link? */
	if (S_ISLNK(inode->i_mode))
		return ADFS_I(inode)->attr;

	if (S_ISDIR(inode->i_mode))
		attr = ADFS_NDA_DIRECTORY;
	else
		attr = 0;

	mode = inode->i_mode & asb->s_owner_mask;
	if (mode & S_IRUGO)
		attr |= ADFS_NDA_OWNER_READ;
	if (mode & S_IWUGO)
		attr |= ADFS_NDA_OWNER_WRITE;

	mode = inode->i_mode & asb->s_other_mask;
	mode &= ~asb->s_owner_mask;
	if (mode & S_IRUGO)
		attr |= ADFS_NDA_PUBLIC_READ;
	if (mode & S_IWUGO)
		attr |= ADFS_NDA_PUBLIC_WRITE;

	return attr;
}

/*
 * Convert an ADFS time to Unix time.  ADFS has a 40-bit centi-second time
166 167
 * referenced to 1 Jan 1900 (til 2248) so we need to discard 2208988800 seconds
 * of time to convert from RISC OS epoch to Unix epoch.
Linus Torvalds's avatar
Linus Torvalds committed
168 169 170 171 172
 */
static void
adfs_adfs2unix_time(struct timespec *tv, struct inode *inode)
{
	unsigned int high, low;
173 174 175 176 177 178
	/* 01 Jan 1970 00:00:00 (Unix epoch) as nanoseconds since
	 * 01 Jan 1900 00:00:00 (RISC OS epoch)
	 */
	static const s64 nsec_unix_epoch_diff_risc_os_epoch =
							2208988800000000000LL;
	s64 nsec;
Linus Torvalds's avatar
Linus Torvalds committed
179 180 181 182

	if (ADFS_I(inode)->stamped == 0)
		goto cur_time;

183 184
	high = ADFS_I(inode)->loadaddr & 0xFF; /* top 8 bits of timestamp */
	low  = ADFS_I(inode)->execaddr;    /* bottom 32 bits of timestamp */
Linus Torvalds's avatar
Linus Torvalds committed
185

186 187 188 189
	/* convert 40-bit centi-seconds to 32-bit seconds
	 * going via nanoseconds to retain precision
	 */
	nsec = (((s64) high << 32) | (s64) low) * 10000000; /* cs to ns */
Linus Torvalds's avatar
Linus Torvalds committed
190 191

	/* Files dated pre  01 Jan 1970 00:00:00. */
192
	if (nsec < nsec_unix_epoch_diff_risc_os_epoch)
Linus Torvalds's avatar
Linus Torvalds committed
193 194
		goto too_early;

195 196
	/* convert from RISC OS to Unix epoch */
	nsec -= nsec_unix_epoch_diff_risc_os_epoch;
Linus Torvalds's avatar
Linus Torvalds committed
197

198
	*tv = ns_to_timespec(nsec);
Linus Torvalds's avatar
Linus Torvalds committed
199 200 201
	return;

 cur_time:
202
	*tv = CURRENT_TIME;
Linus Torvalds's avatar
Linus Torvalds committed
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
	return;

 too_early:
	tv->tv_sec = tv->tv_nsec = 0;
	return;
}

/*
 * Convert an Unix time to ADFS time.  We only do this if the entry has a
 * time/date stamp already.
 */
static void
adfs_unix2adfs_time(struct inode *inode, unsigned int secs)
{
	unsigned int high, low;

	if (ADFS_I(inode)->stamped) {
		/* convert 32-bit seconds to 40-bit centi-seconds */
		low  = (secs & 255) * 100;
		high = (secs / 256) * 100 + (low >> 8) + 0x336e996a;

		ADFS_I(inode)->loadaddr = (high >> 24) |
				(ADFS_I(inode)->loadaddr & ~0xff);
		ADFS_I(inode)->execaddr = (low & 255) | (high << 8);
	}
}

/*
 * Fill in the inode information from the object information.
 *
 * Note that this is an inode-less filesystem, so we can't use the inode
 * number to reference the metadata on the media.  Instead, we use the
 * inode number to hold the object ID, which in turn will tell us where
 * the data is held.  We also save the parent object ID, and with these
 * two, we can locate the metadata.
 *
 * This does mean that we rely on an objects parent remaining the same at
 * all times - we cannot cope with a cross-directory rename (yet).
 */
struct inode *
adfs_iget(struct super_block *sb, struct object_info *obj)
{
	struct inode *inode;

	inode = new_inode(sb);
	if (!inode)
		goto out;

	inode->i_uid	 = ADFS_SB(sb)->s_uid;
	inode->i_gid	 = ADFS_SB(sb)->s_gid;
	inode->i_ino	 = obj->file_id;
	inode->i_size	 = obj->size;
255
	set_nlink(inode, 2);
Linus Torvalds's avatar
Linus Torvalds committed
256 257 258 259 260 261 262 263 264 265 266 267 268
	inode->i_blocks	 = (inode->i_size + sb->s_blocksize - 1) >>
			    sb->s_blocksize_bits;

	/*
	 * we need to save the parent directory ID so that
	 * write_inode can update the directory information
	 * for this file.  This will need special handling
	 * for cross-directory renames.
	 */
	ADFS_I(inode)->parent_id = obj->parent_id;
	ADFS_I(inode)->loadaddr  = obj->loadaddr;
	ADFS_I(inode)->execaddr  = obj->execaddr;
	ADFS_I(inode)->attr      = obj->attr;
269 270
	ADFS_I(inode)->filetype  = obj->filetype;
	ADFS_I(inode)->stamped   = ((obj->loadaddr & 0xfff00000) == 0xfff00000);
Linus Torvalds's avatar
Linus Torvalds committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

	inode->i_mode	 = adfs_atts2mode(sb, inode);
	adfs_adfs2unix_time(&inode->i_mtime, inode);
	inode->i_atime = inode->i_mtime;
	inode->i_ctime = inode->i_mtime;

	if (S_ISDIR(inode->i_mode)) {
		inode->i_op	= &adfs_dir_inode_operations;
		inode->i_fop	= &adfs_dir_operations;
	} else if (S_ISREG(inode->i_mode)) {
		inode->i_op	= &adfs_file_inode_operations;
		inode->i_fop	= &adfs_file_operations;
		inode->i_mapping->a_ops = &adfs_aops;
		ADFS_I(inode)->mmu_private = inode->i_size;
	}

	insert_inode_hash(inode);

out:
	return inode;
}

/*
 * Validate and convert a changed access mode/time to their ADFS equivalents.
 * adfs_write_inode will actually write the information back to the directory
 * later.
 */
int
adfs_notify_change(struct dentry *dentry, struct iattr *attr)
{
301
	struct inode *inode = d_inode(dentry);
Linus Torvalds's avatar
Linus Torvalds committed
302 303 304 305
	struct super_block *sb = inode->i_sb;
	unsigned int ia_valid = attr->ia_valid;
	int error;
	
306
	error = setattr_prepare(dentry, attr);
Linus Torvalds's avatar
Linus Torvalds committed
307 308 309 310 311

	/*
	 * we can't change the UID or GID of any file -
	 * we have a global UID/GID in the superblock
	 */
312 313
	if ((ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, ADFS_SB(sb)->s_uid)) ||
	    (ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, ADFS_SB(sb)->s_gid)))
Linus Torvalds's avatar
Linus Torvalds committed
314 315 316 317 318
		error = -EPERM;

	if (error)
		goto out;

319
	/* XXX: this is missing some actual on-disk truncation.. */
Linus Torvalds's avatar
Linus Torvalds committed
320
	if (ia_valid & ATTR_SIZE)
321
		truncate_setsize(inode, attr->ia_size);
Linus Torvalds's avatar
Linus Torvalds committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

	if (ia_valid & ATTR_MTIME) {
		inode->i_mtime = attr->ia_mtime;
		adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec);
	}
	/*
	 * FIXME: should we make these == to i_mtime since we don't
	 * have the ability to represent them in our filesystem?
	 */
	if (ia_valid & ATTR_ATIME)
		inode->i_atime = attr->ia_atime;
	if (ia_valid & ATTR_CTIME)
		inode->i_ctime = attr->ia_ctime;
	if (ia_valid & ATTR_MODE) {
		ADFS_I(inode)->attr = adfs_mode2atts(sb, inode);
		inode->i_mode = adfs_atts2mode(sb, inode);
	}

	/*
	 * FIXME: should we be marking this inode dirty even if
	 * we don't have any metadata to write back?
	 */
	if (ia_valid & (ATTR_SIZE | ATTR_MTIME | ATTR_MODE))
		mark_inode_dirty(inode);
out:
	return error;
}

/*
 * write an existing inode back to the directory, and therefore the disk.
 * The adfs-specific inode data has already been updated by
 * adfs_notify_change()
 */
355
int adfs_write_inode(struct inode *inode, struct writeback_control *wbc)
Linus Torvalds's avatar
Linus Torvalds committed
356 357 358 359 360 361 362 363 364 365 366 367 368
{
	struct super_block *sb = inode->i_sb;
	struct object_info obj;
	int ret;

	obj.file_id	= inode->i_ino;
	obj.name_len	= 0;
	obj.parent_id	= ADFS_I(inode)->parent_id;
	obj.loadaddr	= ADFS_I(inode)->loadaddr;
	obj.execaddr	= ADFS_I(inode)->execaddr;
	obj.attr	= ADFS_I(inode)->attr;
	obj.size	= inode->i_size;

369
	ret = adfs_dir_update(sb, &obj, wbc->sync_mode == WB_SYNC_ALL);
Linus Torvalds's avatar
Linus Torvalds committed
370 371
	return ret;
}