| .. | .. | 
|---|
 | 1 | +// SPDX-License-Identifier: GPL-2.0-only  | 
|---|
| 1 | 2 |  /* | 
|---|
| 2 | 3 |   *  linux/fs/adfs/map.c | 
|---|
| 3 | 4 |   * | 
|---|
| 4 | 5 |   *  Copyright (C) 1997-2002 Russell King | 
|---|
| 5 |  | - *  | 
|---|
| 6 |  | - * This program is free software; you can redistribute it and/or modify  | 
|---|
| 7 |  | - * it under the terms of the GNU General Public License version 2 as  | 
|---|
| 8 |  | - * published by the Free Software Foundation.  | 
|---|
| 9 | 6 |   */ | 
|---|
| 10 |  | -#include <linux/buffer_head.h>  | 
|---|
 | 7 | +#include <linux/slab.h>  | 
|---|
 | 8 | +#include <linux/statfs.h>  | 
|---|
| 11 | 9 |  #include <asm/unaligned.h> | 
|---|
| 12 | 10 |  #include "adfs.h" | 
|---|
| 13 | 11 |   | 
|---|
| .. | .. | 
|---|
| 67 | 65 |   * output of: | 
|---|
| 68 | 66 |   *  gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c | 
|---|
| 69 | 67 |   */ | 
|---|
| 70 |  | -static int  | 
|---|
| 71 |  | -lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,  | 
|---|
| 72 |  | -	    const unsigned int frag_id, unsigned int *offset)  | 
|---|
 | 68 | +static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,  | 
|---|
 | 69 | +		       const u32 frag_id, unsigned int *offset)  | 
|---|
| 73 | 70 |  { | 
|---|
| 74 |  | -	const unsigned int mapsize = dm->dm_endbit;  | 
|---|
 | 71 | +	const unsigned int endbit = dm->dm_endbit;  | 
|---|
| 75 | 72 |  	const u32 idmask = (1 << idlen) - 1; | 
|---|
| 76 |  | -	unsigned char *map = dm->dm_bh->b_data + 4;  | 
|---|
 | 73 | +	unsigned char *map = dm->dm_bh->b_data;  | 
|---|
| 77 | 74 |  	unsigned int start = dm->dm_startbit; | 
|---|
| 78 |  | -	unsigned int mapptr;  | 
|---|
 | 75 | +	unsigned int freelink, fragend;  | 
|---|
| 79 | 76 |  	u32 frag; | 
|---|
 | 77 | +  | 
|---|
 | 78 | +	frag = GET_FRAG_ID(map, 8, idmask & 0x7fff);  | 
|---|
 | 79 | +	freelink = frag ? 8 + frag : 0;  | 
|---|
| 80 | 80 |   | 
|---|
| 81 | 81 |  	do { | 
|---|
| 82 | 82 |  		frag = GET_FRAG_ID(map, start, idmask); | 
|---|
| 83 |  | -		mapptr = start + idlen;  | 
|---|
| 84 | 83 |   | 
|---|
| 85 |  | -		/*  | 
|---|
| 86 |  | -		 * find end of fragment  | 
|---|
| 87 |  | -		 */  | 
|---|
| 88 |  | -		{  | 
|---|
| 89 |  | -			__le32 *_map = (__le32 *)map;  | 
|---|
| 90 |  | -			u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);  | 
|---|
| 91 |  | -			while (v == 0) {  | 
|---|
| 92 |  | -				mapptr = (mapptr & ~31) + 32;  | 
|---|
| 93 |  | -				if (mapptr >= mapsize)  | 
|---|
| 94 |  | -					goto error;  | 
|---|
| 95 |  | -				v = le32_to_cpu(_map[mapptr >> 5]);  | 
|---|
| 96 |  | -			}  | 
|---|
 | 84 | +		fragend = find_next_bit_le(map, endbit, start + idlen);  | 
|---|
 | 85 | +		if (fragend >= endbit)  | 
|---|
 | 86 | +			goto error;  | 
|---|
| 97 | 87 |   | 
|---|
| 98 |  | -			mapptr += 1 + ffz(~v);  | 
|---|
 | 88 | +		if (start == freelink) {  | 
|---|
 | 89 | +			freelink += frag & 0x7fff;  | 
|---|
 | 90 | +		} else if (frag == frag_id) {  | 
|---|
 | 91 | +			unsigned int length = fragend + 1 - start;  | 
|---|
 | 92 | +  | 
|---|
 | 93 | +			if (*offset < length)  | 
|---|
 | 94 | +				return start + *offset;  | 
|---|
 | 95 | +			*offset -= length;  | 
|---|
| 99 | 96 |  		} | 
|---|
| 100 | 97 |   | 
|---|
| 101 |  | -		if (frag == frag_id)  | 
|---|
| 102 |  | -			goto found;  | 
|---|
| 103 |  | -again:  | 
|---|
| 104 |  | -		start = mapptr;  | 
|---|
| 105 |  | -	} while (mapptr < mapsize);  | 
|---|
 | 98 | +		start = fragend + 1;  | 
|---|
 | 99 | +	} while (start < endbit);  | 
|---|
| 106 | 100 |  	return -1; | 
|---|
| 107 | 101 |   | 
|---|
| 108 | 102 |  error: | 
|---|
| 109 | 103 |  	printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n", | 
|---|
| 110 |  | -		frag, start, mapptr);  | 
|---|
 | 104 | +		frag, start, fragend);  | 
|---|
| 111 | 105 |  	return -1; | 
|---|
| 112 |  | -  | 
|---|
| 113 |  | -found:  | 
|---|
| 114 |  | -	{  | 
|---|
| 115 |  | -		int length = mapptr - start;  | 
|---|
| 116 |  | -		if (*offset >= length) {  | 
|---|
| 117 |  | -			*offset -= length;  | 
|---|
| 118 |  | -			goto again;  | 
|---|
| 119 |  | -		}  | 
|---|
| 120 |  | -	}  | 
|---|
| 121 |  | -	return start + *offset;  | 
|---|
| 122 | 106 |  } | 
|---|
| 123 | 107 |   | 
|---|
| 124 | 108 |  /* | 
|---|
| .. | .. | 
|---|
| 130 | 114 |  static unsigned int | 
|---|
| 131 | 115 |  scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm) | 
|---|
| 132 | 116 |  { | 
|---|
| 133 |  | -	const unsigned int mapsize = dm->dm_endbit + 32;  | 
|---|
 | 117 | +	const unsigned int endbit = dm->dm_endbit;  | 
|---|
| 134 | 118 |  	const unsigned int idlen  = asb->s_idlen; | 
|---|
| 135 | 119 |  	const unsigned int frag_idlen = idlen <= 15 ? idlen : 15; | 
|---|
| 136 | 120 |  	const u32 idmask = (1 << frag_idlen) - 1; | 
|---|
| 137 | 121 |  	unsigned char *map = dm->dm_bh->b_data; | 
|---|
| 138 |  | -	unsigned int start = 8, mapptr;  | 
|---|
 | 122 | +	unsigned int start = 8, fragend;  | 
|---|
| 139 | 123 |  	u32 frag; | 
|---|
| 140 | 124 |  	unsigned long total = 0; | 
|---|
| 141 | 125 |   | 
|---|
| .. | .. | 
|---|
| 154 | 138 |  	do { | 
|---|
| 155 | 139 |  		start += frag; | 
|---|
| 156 | 140 |   | 
|---|
| 157 |  | -		/*  | 
|---|
| 158 |  | -		 * get fragment id  | 
|---|
| 159 |  | -		 */  | 
|---|
| 160 | 141 |  		frag = GET_FRAG_ID(map, start, idmask); | 
|---|
| 161 |  | -		mapptr = start + idlen;  | 
|---|
| 162 | 142 |   | 
|---|
| 163 |  | -		/*  | 
|---|
| 164 |  | -		 * find end of fragment  | 
|---|
| 165 |  | -		 */  | 
|---|
| 166 |  | -		{  | 
|---|
| 167 |  | -			__le32 *_map = (__le32 *)map;  | 
|---|
| 168 |  | -			u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);  | 
|---|
| 169 |  | -			while (v == 0) {  | 
|---|
| 170 |  | -				mapptr = (mapptr & ~31) + 32;  | 
|---|
| 171 |  | -				if (mapptr >= mapsize)  | 
|---|
| 172 |  | -					goto error;  | 
|---|
| 173 |  | -				v = le32_to_cpu(_map[mapptr >> 5]);  | 
|---|
| 174 |  | -			}  | 
|---|
 | 143 | +		fragend = find_next_bit_le(map, endbit, start + idlen);  | 
|---|
 | 144 | +		if (fragend >= endbit)  | 
|---|
 | 145 | +			goto error;  | 
|---|
| 175 | 146 |   | 
|---|
| 176 |  | -			mapptr += 1 + ffz(~v);  | 
|---|
| 177 |  | -		}  | 
|---|
| 178 |  | -  | 
|---|
| 179 |  | -		total += mapptr - start;  | 
|---|
 | 147 | +		total += fragend + 1 - start;  | 
|---|
| 180 | 148 |  	} while (frag >= idlen + 1); | 
|---|
| 181 | 149 |   | 
|---|
| 182 | 150 |  	if (frag != 0) | 
|---|
| .. | .. | 
|---|
| 188 | 156 |  	return 0; | 
|---|
| 189 | 157 |  } | 
|---|
| 190 | 158 |   | 
|---|
| 191 |  | -static int  | 
|---|
| 192 |  | -scan_map(struct adfs_sb_info *asb, unsigned int zone,  | 
|---|
| 193 |  | -	 const unsigned int frag_id, unsigned int mapoff)  | 
|---|
 | 159 | +static int scan_map(struct adfs_sb_info *asb, unsigned int zone,  | 
|---|
 | 160 | +		    const u32 frag_id, unsigned int mapoff)  | 
|---|
| 194 | 161 |  { | 
|---|
| 195 | 162 |  	const unsigned int idlen = asb->s_idlen; | 
|---|
| 196 | 163 |  	struct adfs_discmap *dm, *dm_end; | 
|---|
| .. | .. | 
|---|
| 226 | 193 |   *  total_free = E(free_in_zone_n) | 
|---|
| 227 | 194 |   *              nzones | 
|---|
| 228 | 195 |   */ | 
|---|
| 229 |  | -unsigned int  | 
|---|
| 230 |  | -adfs_map_free(struct super_block *sb)  | 
|---|
 | 196 | +void adfs_map_statfs(struct super_block *sb, struct kstatfs *buf)  | 
|---|
| 231 | 197 |  { | 
|---|
| 232 | 198 |  	struct adfs_sb_info *asb = ADFS_SB(sb); | 
|---|
 | 199 | +	struct adfs_discrecord *dr = adfs_map_discrecord(asb->s_map);  | 
|---|
| 233 | 200 |  	struct adfs_discmap *dm; | 
|---|
| 234 | 201 |  	unsigned int total = 0; | 
|---|
| 235 | 202 |  	unsigned int zone; | 
|---|
| .. | .. | 
|---|
| 241 | 208 |  		total += scan_free_map(asb, dm++); | 
|---|
| 242 | 209 |  	} while (--zone > 0); | 
|---|
| 243 | 210 |   | 
|---|
| 244 |  | -	return signed_asl(total, asb->s_map2blk);  | 
|---|
 | 211 | +	buf->f_blocks  = adfs_disc_size(dr) >> sb->s_blocksize_bits;  | 
|---|
 | 212 | +	buf->f_files   = asb->s_ids_per_zone * asb->s_map_size;  | 
|---|
 | 213 | +	buf->f_bavail  =  | 
|---|
 | 214 | +	buf->f_bfree   = signed_asl(total, asb->s_map2blk);  | 
|---|
| 245 | 215 |  } | 
|---|
| 246 | 216 |   | 
|---|
| 247 |  | -int  | 
|---|
| 248 |  | -adfs_map_lookup(struct super_block *sb, unsigned int frag_id,  | 
|---|
| 249 |  | -		unsigned int offset)  | 
|---|
 | 217 | +int adfs_map_lookup(struct super_block *sb, u32 frag_id, unsigned int offset)  | 
|---|
| 250 | 218 |  { | 
|---|
| 251 | 219 |  	struct adfs_sb_info *asb = ADFS_SB(sb); | 
|---|
| 252 | 220 |  	unsigned int zone, mapoff; | 
|---|
| .. | .. | 
|---|
| 288 | 256 |  		   frag_id, zone, asb->s_map_size); | 
|---|
| 289 | 257 |  	return 0; | 
|---|
| 290 | 258 |  } | 
|---|
 | 259 | +  | 
|---|
 | 260 | +static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map)  | 
|---|
 | 261 | +{  | 
|---|
 | 262 | +	unsigned int v0, v1, v2, v3;  | 
|---|
 | 263 | +	int i;  | 
|---|
 | 264 | +  | 
|---|
 | 265 | +	v0 = v1 = v2 = v3 = 0;  | 
|---|
 | 266 | +	for (i = sb->s_blocksize - 4; i; i -= 4) {  | 
|---|
 | 267 | +		v0 += map[i]     + (v3 >> 8);  | 
|---|
 | 268 | +		v3 &= 0xff;  | 
|---|
 | 269 | +		v1 += map[i + 1] + (v0 >> 8);  | 
|---|
 | 270 | +		v0 &= 0xff;  | 
|---|
 | 271 | +		v2 += map[i + 2] + (v1 >> 8);  | 
|---|
 | 272 | +		v1 &= 0xff;  | 
|---|
 | 273 | +		v3 += map[i + 3] + (v2 >> 8);  | 
|---|
 | 274 | +		v2 &= 0xff;  | 
|---|
 | 275 | +	}  | 
|---|
 | 276 | +	v0 +=           v3 >> 8;  | 
|---|
 | 277 | +	v1 += map[1] + (v0 >> 8);  | 
|---|
 | 278 | +	v2 += map[2] + (v1 >> 8);  | 
|---|
 | 279 | +	v3 += map[3] + (v2 >> 8);  | 
|---|
 | 280 | +  | 
|---|
 | 281 | +	return v0 ^ v1 ^ v2 ^ v3;  | 
|---|
 | 282 | +}  | 
|---|
 | 283 | +  | 
|---|
 | 284 | +static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm)  | 
|---|
 | 285 | +{  | 
|---|
 | 286 | +	unsigned char crosscheck = 0, zonecheck = 1;  | 
|---|
 | 287 | +	int i;  | 
|---|
 | 288 | +  | 
|---|
 | 289 | +	for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) {  | 
|---|
 | 290 | +		unsigned char *map;  | 
|---|
 | 291 | +  | 
|---|
 | 292 | +		map = dm[i].dm_bh->b_data;  | 
|---|
 | 293 | +  | 
|---|
 | 294 | +		if (adfs_calczonecheck(sb, map) != map[0]) {  | 
|---|
 | 295 | +			adfs_error(sb, "zone %d fails zonecheck", i);  | 
|---|
 | 296 | +			zonecheck = 0;  | 
|---|
 | 297 | +		}  | 
|---|
 | 298 | +		crosscheck ^= map[3];  | 
|---|
 | 299 | +	}  | 
|---|
 | 300 | +	if (crosscheck != 0xff)  | 
|---|
 | 301 | +		adfs_error(sb, "crosscheck != 0xff");  | 
|---|
 | 302 | +	return crosscheck == 0xff && zonecheck;  | 
|---|
 | 303 | +}  | 
|---|
 | 304 | +  | 
|---|
 | 305 | +/*  | 
|---|
 | 306 | + * Layout the map - the first zone contains a copy of the disc record,  | 
|---|
 | 307 | + * and the last zone must be limited to the size of the filesystem.  | 
|---|
 | 308 | + */  | 
|---|
 | 309 | +static void adfs_map_layout(struct adfs_discmap *dm, unsigned int nzones,  | 
|---|
 | 310 | +			    struct adfs_discrecord *dr)  | 
|---|
 | 311 | +{  | 
|---|
 | 312 | +	unsigned int zone, zone_size;  | 
|---|
 | 313 | +	u64 size;  | 
|---|
 | 314 | +  | 
|---|
 | 315 | +	zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);  | 
|---|
 | 316 | +  | 
|---|
 | 317 | +	dm[0].dm_bh       = NULL;  | 
|---|
 | 318 | +	dm[0].dm_startblk = 0;  | 
|---|
 | 319 | +	dm[0].dm_startbit = 32 + ADFS_DR_SIZE_BITS;  | 
|---|
 | 320 | +	dm[0].dm_endbit   = 32 + zone_size;  | 
|---|
 | 321 | +  | 
|---|
 | 322 | +	for (zone = 1; zone < nzones; zone++) {  | 
|---|
 | 323 | +		dm[zone].dm_bh       = NULL;  | 
|---|
 | 324 | +		dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS;  | 
|---|
 | 325 | +		dm[zone].dm_startbit = 32;  | 
|---|
 | 326 | +		dm[zone].dm_endbit   = 32 + zone_size;  | 
|---|
 | 327 | +	}  | 
|---|
 | 328 | +  | 
|---|
 | 329 | +	size = adfs_disc_size(dr) >> dr->log2bpmb;  | 
|---|
 | 330 | +	size -= (nzones - 1) * zone_size - ADFS_DR_SIZE_BITS;  | 
|---|
 | 331 | +	dm[nzones - 1].dm_endbit = 32 + size;  | 
|---|
 | 332 | +}  | 
|---|
 | 333 | +  | 
|---|
 | 334 | +static int adfs_map_read(struct adfs_discmap *dm, struct super_block *sb,  | 
|---|
 | 335 | +			 unsigned int map_addr, unsigned int nzones)  | 
|---|
 | 336 | +{  | 
|---|
 | 337 | +	unsigned int zone;  | 
|---|
 | 338 | +  | 
|---|
 | 339 | +	for (zone = 0; zone < nzones; zone++) {  | 
|---|
 | 340 | +		dm[zone].dm_bh = sb_bread(sb, map_addr + zone);  | 
|---|
 | 341 | +		if (!dm[zone].dm_bh)  | 
|---|
 | 342 | +			return -EIO;  | 
|---|
 | 343 | +	}  | 
|---|
 | 344 | +  | 
|---|
 | 345 | +	return 0;  | 
|---|
 | 346 | +}  | 
|---|
 | 347 | +  | 
|---|
 | 348 | +static void adfs_map_relse(struct adfs_discmap *dm, unsigned int nzones)  | 
|---|
 | 349 | +{  | 
|---|
 | 350 | +	unsigned int zone;  | 
|---|
 | 351 | +  | 
|---|
 | 352 | +	for (zone = 0; zone < nzones; zone++)  | 
|---|
 | 353 | +		brelse(dm[zone].dm_bh);  | 
|---|
 | 354 | +}  | 
|---|
 | 355 | +  | 
|---|
 | 356 | +struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr)  | 
|---|
 | 357 | +{  | 
|---|
 | 358 | +	struct adfs_sb_info *asb = ADFS_SB(sb);  | 
|---|
 | 359 | +	struct adfs_discmap *dm;  | 
|---|
 | 360 | +	unsigned int map_addr, zone_size, nzones;  | 
|---|
 | 361 | +	int ret;  | 
|---|
 | 362 | +  | 
|---|
 | 363 | +	nzones    = dr->nzones | dr->nzones_high << 8;  | 
|---|
 | 364 | +	zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);  | 
|---|
 | 365 | +  | 
|---|
 | 366 | +	asb->s_idlen = dr->idlen;  | 
|---|
 | 367 | +	asb->s_map_size = nzones;  | 
|---|
 | 368 | +	asb->s_map2blk = dr->log2bpmb - dr->log2secsize;  | 
|---|
 | 369 | +	asb->s_log2sharesize = dr->log2sharesize;  | 
|---|
 | 370 | +	asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);  | 
|---|
 | 371 | +  | 
|---|
 | 372 | +	map_addr = (nzones >> 1) * zone_size -  | 
|---|
 | 373 | +		     ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0);  | 
|---|
 | 374 | +	map_addr = signed_asl(map_addr, asb->s_map2blk);  | 
|---|
 | 375 | +  | 
|---|
 | 376 | +	dm = kmalloc_array(nzones, sizeof(*dm), GFP_KERNEL);  | 
|---|
 | 377 | +	if (dm == NULL) {  | 
|---|
 | 378 | +		adfs_error(sb, "not enough memory");  | 
|---|
 | 379 | +		return ERR_PTR(-ENOMEM);  | 
|---|
 | 380 | +	}  | 
|---|
 | 381 | +  | 
|---|
 | 382 | +	adfs_map_layout(dm, nzones, dr);  | 
|---|
 | 383 | +  | 
|---|
 | 384 | +	ret = adfs_map_read(dm, sb, map_addr, nzones);  | 
|---|
 | 385 | +	if (ret) {  | 
|---|
 | 386 | +		adfs_error(sb, "unable to read map");  | 
|---|
 | 387 | +		goto error_free;  | 
|---|
 | 388 | +	}  | 
|---|
 | 389 | +  | 
|---|
 | 390 | +	if (adfs_checkmap(sb, dm))  | 
|---|
 | 391 | +		return dm;  | 
|---|
 | 392 | +  | 
|---|
 | 393 | +	adfs_error(sb, "map corrupted");  | 
|---|
 | 394 | +  | 
|---|
 | 395 | +error_free:  | 
|---|
 | 396 | +	adfs_map_relse(dm, nzones);  | 
|---|
 | 397 | +	kfree(dm);  | 
|---|
 | 398 | +	return ERR_PTR(-EIO);  | 
|---|
 | 399 | +}  | 
|---|
 | 400 | +  | 
|---|
 | 401 | +void adfs_free_map(struct super_block *sb)  | 
|---|
 | 402 | +{  | 
|---|
 | 403 | +	struct adfs_sb_info *asb = ADFS_SB(sb);  | 
|---|
 | 404 | +  | 
|---|
 | 405 | +	adfs_map_relse(asb->s_map, asb->s_map_size);  | 
|---|
 | 406 | +	kfree(asb->s_map);  | 
|---|
 | 407 | +}  | 
|---|