brintos

brintos / linux-shallow public Read only

0
0
Text · 7.2 KiB · 05e9634 Raw
325 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *  linux/fs/adfs/dir_f.c4 *5 * Copyright (C) 1997-1999 Russell King6 *7 *  E and F format directory handling8 */9#include "adfs.h"10#include "dir_f.h"11 12/*13 * Read an (unaligned) value of length 1..4 bytes14 */15static inline unsigned int adfs_readval(unsigned char *p, int len)16{17	unsigned int val = 0;18 19	switch (len) {20	case 4:		val |= p[3] << 24;21		fallthrough;22	case 3:		val |= p[2] << 16;23		fallthrough;24	case 2:		val |= p[1] << 8;25		fallthrough;26	default:	val |= p[0];27	}28	return val;29}30 31static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)32{33	switch (len) {34	case 4:		p[3] = val >> 24;35		fallthrough;36	case 3:		p[2] = val >> 16;37		fallthrough;38	case 2:		p[1] = val >> 8;39		fallthrough;40	default:	p[0] = val;41	}42}43 44#define ror13(v) ((v >> 13) | (v << 19))45 46#define dir_u8(idx)				\47	({ int _buf = idx >> blocksize_bits;	\48	   int _off = idx - (_buf << blocksize_bits);\49	  *(u8 *)(bh[_buf]->b_data + _off);	\50	})51 52#define dir_u32(idx)				\53	({ int _buf = idx >> blocksize_bits;	\54	   int _off = idx - (_buf << blocksize_bits);\55	  *(__le32 *)(bh[_buf]->b_data + _off);	\56	})57 58#define bufoff(_bh,_idx)			\59	({ int _buf = _idx >> blocksize_bits;	\60	   int _off = _idx - (_buf << blocksize_bits);\61	  (void *)(_bh[_buf]->b_data + _off);	\62	})63 64/*65 * There are some algorithms that are nice in66 * assembler, but a bitch in C...  This is one67 * of them.68 */69static u870adfs_dir_checkbyte(const struct adfs_dir *dir)71{72	struct buffer_head * const *bh = dir->bh;73	const int blocksize_bits = dir->sb->s_blocksize_bits;74	union { __le32 *ptr32; u8 *ptr8; } ptr, end;75	u32 dircheck = 0;76	int last = 5 - 26;77	int i = 0;78 79	/*80	 * Accumulate each word up to the last whole81	 * word of the last directory entry.  This82	 * can spread across several buffer heads.83	 */84	do {85		last += 26;86		do {87			dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);88 89			i += sizeof(u32);90		} while (i < (last & ~3));91	} while (dir_u8(last) != 0);92 93	/*94	 * Accumulate the last few bytes.  These95	 * bytes will be within the same bh.96	 */97	if (i != last) {98		ptr.ptr8 = bufoff(bh, i);99		end.ptr8 = ptr.ptr8 + last - i;100 101		do {102			dircheck = *ptr.ptr8++ ^ ror13(dircheck);103		} while (ptr.ptr8 < end.ptr8);104	}105 106	/*107	 * The directory tail is in the final bh108	 * Note that contary to the RISC OS PRMs,109	 * the first few bytes are NOT included110	 * in the check.  All bytes are in the111	 * same bh.112	 */113	ptr.ptr8 = bufoff(bh, 2008);114	end.ptr8 = ptr.ptr8 + 36;115 116	do {117		__le32 v = *ptr.ptr32++;118		dircheck = le32_to_cpu(v) ^ ror13(dircheck);119	} while (ptr.ptr32 < end.ptr32);120 121	return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;122}123 124static int adfs_f_validate(struct adfs_dir *dir)125{126	struct adfs_dirheader *head = dir->dirhead;127	struct adfs_newdirtail *tail = dir->newtail;128 129	if (head->startmasseq != tail->endmasseq ||130	    tail->dirlastmask || tail->reserved[0] || tail->reserved[1] ||131	    (memcmp(&head->startname, "Nick", 4) &&132	     memcmp(&head->startname, "Hugo", 4)) ||133	    memcmp(&head->startname, &tail->endname, 4) ||134	    adfs_dir_checkbyte(dir) != tail->dircheckbyte)135		return -EIO;136 137	return 0;138}139 140/* Read and check that a directory is valid */141static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size,142		       struct adfs_dir *dir)143{144	const unsigned int blocksize_bits = sb->s_blocksize_bits;145	int ret;146 147	if (size && size != ADFS_NEWDIR_SIZE)148		return -EIO;149 150	ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir);151	if (ret)152		return ret;153 154	dir->dirhead = bufoff(dir->bh, 0);155	dir->newtail = bufoff(dir->bh, 2007);156 157	if (adfs_f_validate(dir))158		goto bad_dir;159 160	dir->parent_id = adfs_readval(dir->newtail->dirparent, 3);161 162	return 0;163 164bad_dir:165	adfs_error(sb, "dir %06x is corrupted", indaddr);166	adfs_dir_relse(dir);167 168	return -EIO;169}170 171/*172 * convert a disk-based directory entry to a Linux ADFS directory entry173 */174static inline void175adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj,176	struct adfs_direntry *de)177{178	unsigned int name_len;179 180	for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) {181		if (de->dirobname[name_len] < ' ')182			break;183 184		obj->name[name_len] = de->dirobname[name_len];185	}186 187	obj->name_len =	name_len;188	obj->indaddr  = adfs_readval(de->dirinddiscadd, 3);189	obj->loadaddr = adfs_readval(de->dirload, 4);190	obj->execaddr = adfs_readval(de->direxec, 4);191	obj->size     = adfs_readval(de->dirlen,  4);192	obj->attr     = de->newdiratts;193 194	adfs_object_fixup(dir, obj);195}196 197/*198 * convert a Linux ADFS directory entry to a disk-based directory entry199 */200static inline void201adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)202{203	adfs_writeval(de->dirinddiscadd, 3, obj->indaddr);204	adfs_writeval(de->dirload, 4, obj->loadaddr);205	adfs_writeval(de->direxec, 4, obj->execaddr);206	adfs_writeval(de->dirlen,  4, obj->size);207	de->newdiratts = obj->attr;208}209 210/*211 * get a directory entry.  Note that the caller is responsible212 * for holding the relevant locks.213 */214static int215__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)216{217	struct adfs_direntry de;218	int ret;219 220	ret = adfs_dir_copyfrom(&de, dir, pos, 26);221	if (ret)222		return ret;223 224	if (!de.dirobname[0])225		return -ENOENT;226 227	adfs_dir2obj(dir, obj, &de);228 229	return 0;230}231 232static int233adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)234{235	if (fpos >= ADFS_NUM_DIR_ENTRIES)236		return -ENOENT;237 238	dir->pos = 5 + fpos * 26;239	return 0;240}241 242static int243adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)244{245	unsigned int ret;246 247	ret = __adfs_dir_get(dir, dir->pos, obj);248	if (ret == 0)249		dir->pos += 26;250 251	return ret;252}253 254static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx)255{256	struct object_info obj;257	int pos = 5 + (ctx->pos - 2) * 26;258 259	while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) {260		if (__adfs_dir_get(dir, pos, &obj))261			break;262		if (!dir_emit(ctx, obj.name, obj.name_len,263			      obj.indaddr, DT_UNKNOWN))264			break;265		pos += 26;266		ctx->pos++;267	}268	return 0;269}270 271static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)272{273	struct adfs_direntry de;274	int offset, ret;275 276	offset = 5 - (int)sizeof(de);277 278	do {279		offset += sizeof(de);280		ret = adfs_dir_copyfrom(&de, dir, offset, sizeof(de));281		if (ret) {282			adfs_error(dir->sb, "error reading directory entry");283			return -ENOENT;284		}285		if (!de.dirobname[0]) {286			adfs_error(dir->sb, "unable to locate entry to update");287			return -ENOENT;288		}289	} while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr);290 291	/* Update the directory entry with the new object state */292	adfs_obj2dir(&de, obj);293 294	/* Write the directory entry back to the directory */295	return adfs_dir_copyto(dir, offset, &de, 26);296}297 298static int adfs_f_commit(struct adfs_dir *dir)299{300	int ret;301 302	/* Increment directory sequence number */303	dir->dirhead->startmasseq += 1;304	dir->newtail->endmasseq += 1;305 306	/* Update directory check byte */307	dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir);308 309	/* Make sure the directory still validates correctly */310	ret = adfs_f_validate(dir);311	if (ret)312		adfs_msg(dir->sb, KERN_ERR, "error: update broke directory");313 314	return ret;315}316 317const struct adfs_dir_ops adfs_f_dir_ops = {318	.read		= adfs_f_read,319	.iterate	= adfs_f_iterate,320	.setpos		= adfs_f_setpos,321	.getnext	= adfs_f_getnext,322	.update		= adfs_f_update,323	.commit		= adfs_f_commit,324};325