xref: /openbmc/linux/fs/ntfs/bitmap.c (revision 58e16d792a6a8c6b750f637a4649967fcac853dc)
1*a1d312deSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  * bitmap.c - NTFS kernel bitmap handling.  Part of the Linux-NTFS project.
41da177e4SLinus Torvalds  *
518efefa9SAnton Altaparmakov  * Copyright (c) 2004-2005 Anton Altaparmakov
61da177e4SLinus Torvalds  */
71da177e4SLinus Torvalds 
81da177e4SLinus Torvalds #ifdef NTFS_RW
91da177e4SLinus Torvalds 
101da177e4SLinus Torvalds #include <linux/pagemap.h>
111da177e4SLinus Torvalds 
121da177e4SLinus Torvalds #include "bitmap.h"
131da177e4SLinus Torvalds #include "debug.h"
141da177e4SLinus Torvalds #include "aops.h"
151da177e4SLinus Torvalds #include "ntfs.h"
161da177e4SLinus Torvalds 
171da177e4SLinus Torvalds /**
181da177e4SLinus Torvalds  * __ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value
191da177e4SLinus Torvalds  * @vi:			vfs inode describing the bitmap
201da177e4SLinus Torvalds  * @start_bit:		first bit to set
211da177e4SLinus Torvalds  * @count:		number of bits to set
221da177e4SLinus Torvalds  * @value:		value to set the bits to (i.e. 0 or 1)
23c49c3111SRichard Knutsson  * @is_rollback:	if 'true' this is a rollback operation
241da177e4SLinus Torvalds  *
251da177e4SLinus Torvalds  * Set @count bits starting at bit @start_bit in the bitmap described by the
261da177e4SLinus Torvalds  * vfs inode @vi to @value, where @value is either 0 or 1.
271da177e4SLinus Torvalds  *
28c49c3111SRichard Knutsson  * @is_rollback should always be 'false', it is for internal use to rollback
291da177e4SLinus Torvalds  * errors.  You probably want to use ntfs_bitmap_set_bits_in_run() instead.
301da177e4SLinus Torvalds  *
311da177e4SLinus Torvalds  * Return 0 on success and -errno on error.
321da177e4SLinus Torvalds  */
__ntfs_bitmap_set_bits_in_run(struct inode * vi,const s64 start_bit,const s64 count,const u8 value,const bool is_rollback)331da177e4SLinus Torvalds int __ntfs_bitmap_set_bits_in_run(struct inode *vi, const s64 start_bit,
34c49c3111SRichard Knutsson 		const s64 count, const u8 value, const bool is_rollback)
351da177e4SLinus Torvalds {
361da177e4SLinus Torvalds 	s64 cnt = count;
371da177e4SLinus Torvalds 	pgoff_t index, end_index;
381da177e4SLinus Torvalds 	struct address_space *mapping;
391da177e4SLinus Torvalds 	struct page *page;
401da177e4SLinus Torvalds 	u8 *kaddr;
411da177e4SLinus Torvalds 	int pos, len;
421da177e4SLinus Torvalds 	u8 bit;
431da177e4SLinus Torvalds 
441da177e4SLinus Torvalds 	BUG_ON(!vi);
451da177e4SLinus Torvalds 	ntfs_debug("Entering for i_ino 0x%lx, start_bit 0x%llx, count 0x%llx, "
461da177e4SLinus Torvalds 			"value %u.%s", vi->i_ino, (unsigned long long)start_bit,
471da177e4SLinus Torvalds 			(unsigned long long)cnt, (unsigned int)value,
481da177e4SLinus Torvalds 			is_rollback ? " (rollback)" : "");
491da177e4SLinus Torvalds 	BUG_ON(start_bit < 0);
501da177e4SLinus Torvalds 	BUG_ON(cnt < 0);
511da177e4SLinus Torvalds 	BUG_ON(value > 1);
521da177e4SLinus Torvalds 	/*
531da177e4SLinus Torvalds 	 * Calculate the indices for the pages containing the first and last
541da177e4SLinus Torvalds 	 * bits, i.e. @start_bit and @start_bit + @cnt - 1, respectively.
551da177e4SLinus Torvalds 	 */
5609cbfeafSKirill A. Shutemov 	index = start_bit >> (3 + PAGE_SHIFT);
5709cbfeafSKirill A. Shutemov 	end_index = (start_bit + cnt - 1) >> (3 + PAGE_SHIFT);
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds 	/* Get the page containing the first bit (@start_bit). */
601da177e4SLinus Torvalds 	mapping = vi->i_mapping;
611da177e4SLinus Torvalds 	page = ntfs_map_page(mapping, index);
621da177e4SLinus Torvalds 	if (IS_ERR(page)) {
631da177e4SLinus Torvalds 		if (!is_rollback)
641da177e4SLinus Torvalds 			ntfs_error(vi->i_sb, "Failed to map first page (error "
651da177e4SLinus Torvalds 					"%li), aborting.", PTR_ERR(page));
661da177e4SLinus Torvalds 		return PTR_ERR(page);
671da177e4SLinus Torvalds 	}
681da177e4SLinus Torvalds 	kaddr = page_address(page);
691da177e4SLinus Torvalds 
701da177e4SLinus Torvalds 	/* Set @pos to the position of the byte containing @start_bit. */
7109cbfeafSKirill A. Shutemov 	pos = (start_bit >> 3) & ~PAGE_MASK;
721da177e4SLinus Torvalds 
731da177e4SLinus Torvalds 	/* Calculate the position of @start_bit in the first byte. */
741da177e4SLinus Torvalds 	bit = start_bit & 7;
751da177e4SLinus Torvalds 
761da177e4SLinus Torvalds 	/* If the first byte is partial, modify the appropriate bits in it. */
771da177e4SLinus Torvalds 	if (bit) {
781da177e4SLinus Torvalds 		u8 *byte = kaddr + pos;
7918efefa9SAnton Altaparmakov 		while ((bit & 7) && cnt) {
8018efefa9SAnton Altaparmakov 			cnt--;
811da177e4SLinus Torvalds 			if (value)
821da177e4SLinus Torvalds 				*byte |= 1 << bit++;
831da177e4SLinus Torvalds 			else
841da177e4SLinus Torvalds 				*byte &= ~(1 << bit++);
851da177e4SLinus Torvalds 		}
861da177e4SLinus Torvalds 		/* If we are done, unmap the page and return success. */
871da177e4SLinus Torvalds 		if (!cnt)
881da177e4SLinus Torvalds 			goto done;
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds 		/* Update @pos to the new position. */
911da177e4SLinus Torvalds 		pos++;
921da177e4SLinus Torvalds 	}
931da177e4SLinus Torvalds 	/*
941da177e4SLinus Torvalds 	 * Depending on @value, modify all remaining whole bytes in the page up
951da177e4SLinus Torvalds 	 * to @cnt.
961da177e4SLinus Torvalds 	 */
9709cbfeafSKirill A. Shutemov 	len = min_t(s64, cnt >> 3, PAGE_SIZE - pos);
981da177e4SLinus Torvalds 	memset(kaddr + pos, value ? 0xff : 0, len);
991da177e4SLinus Torvalds 	cnt -= len << 3;
1001da177e4SLinus Torvalds 
1011da177e4SLinus Torvalds 	/* Update @len to point to the first not-done byte in the page. */
1021da177e4SLinus Torvalds 	if (cnt < 8)
1031da177e4SLinus Torvalds 		len += pos;
1041da177e4SLinus Torvalds 
1051da177e4SLinus Torvalds 	/* If we are not in the last page, deal with all subsequent pages. */
1061da177e4SLinus Torvalds 	while (index < end_index) {
1071da177e4SLinus Torvalds 		BUG_ON(cnt <= 0);
1081da177e4SLinus Torvalds 
1091da177e4SLinus Torvalds 		/* Update @index and get the next page. */
1101da177e4SLinus Torvalds 		flush_dcache_page(page);
1111da177e4SLinus Torvalds 		set_page_dirty(page);
1121da177e4SLinus Torvalds 		ntfs_unmap_page(page);
1131da177e4SLinus Torvalds 		page = ntfs_map_page(mapping, ++index);
1141da177e4SLinus Torvalds 		if (IS_ERR(page))
1151da177e4SLinus Torvalds 			goto rollback;
1161da177e4SLinus Torvalds 		kaddr = page_address(page);
1171da177e4SLinus Torvalds 		/*
1181da177e4SLinus Torvalds 		 * Depending on @value, modify all remaining whole bytes in the
1191da177e4SLinus Torvalds 		 * page up to @cnt.
1201da177e4SLinus Torvalds 		 */
12109cbfeafSKirill A. Shutemov 		len = min_t(s64, cnt >> 3, PAGE_SIZE);
1221da177e4SLinus Torvalds 		memset(kaddr, value ? 0xff : 0, len);
1231da177e4SLinus Torvalds 		cnt -= len << 3;
1241da177e4SLinus Torvalds 	}
1251da177e4SLinus Torvalds 	/*
1261da177e4SLinus Torvalds 	 * The currently mapped page is the last one.  If the last byte is
1271da177e4SLinus Torvalds 	 * partial, modify the appropriate bits in it.  Note, @len is the
1281da177e4SLinus Torvalds 	 * position of the last byte inside the page.
1291da177e4SLinus Torvalds 	 */
1301da177e4SLinus Torvalds 	if (cnt) {
1311da177e4SLinus Torvalds 		u8 *byte;
1321da177e4SLinus Torvalds 
1331da177e4SLinus Torvalds 		BUG_ON(cnt > 7);
1341da177e4SLinus Torvalds 
1351da177e4SLinus Torvalds 		bit = cnt;
1361da177e4SLinus Torvalds 		byte = kaddr + len;
1371da177e4SLinus Torvalds 		while (bit--) {
1381da177e4SLinus Torvalds 			if (value)
1391da177e4SLinus Torvalds 				*byte |= 1 << bit;
1401da177e4SLinus Torvalds 			else
1411da177e4SLinus Torvalds 				*byte &= ~(1 << bit);
1421da177e4SLinus Torvalds 		}
1431da177e4SLinus Torvalds 	}
1441da177e4SLinus Torvalds done:
1451da177e4SLinus Torvalds 	/* We are done.  Unmap the page and return success. */
1461da177e4SLinus Torvalds 	flush_dcache_page(page);
1471da177e4SLinus Torvalds 	set_page_dirty(page);
1481da177e4SLinus Torvalds 	ntfs_unmap_page(page);
1491da177e4SLinus Torvalds 	ntfs_debug("Done.");
1501da177e4SLinus Torvalds 	return 0;
1511da177e4SLinus Torvalds rollback:
1521da177e4SLinus Torvalds 	/*
1531da177e4SLinus Torvalds 	 * Current state:
1541da177e4SLinus Torvalds 	 *	- no pages are mapped
1551da177e4SLinus Torvalds 	 *	- @count - @cnt is the number of bits that have been modified
1561da177e4SLinus Torvalds 	 */
1571da177e4SLinus Torvalds 	if (is_rollback)
1581da177e4SLinus Torvalds 		return PTR_ERR(page);
1591da177e4SLinus Torvalds 	if (count != cnt)
1601da177e4SLinus Torvalds 		pos = __ntfs_bitmap_set_bits_in_run(vi, start_bit, count - cnt,
161c49c3111SRichard Knutsson 				value ? 0 : 1, true);
1621da177e4SLinus Torvalds 	else
1631da177e4SLinus Torvalds 		pos = 0;
1641da177e4SLinus Torvalds 	if (!pos) {
1651da177e4SLinus Torvalds 		/* Rollback was successful. */
1661da177e4SLinus Torvalds 		ntfs_error(vi->i_sb, "Failed to map subsequent page (error "
1671da177e4SLinus Torvalds 				"%li), aborting.", PTR_ERR(page));
1681da177e4SLinus Torvalds 	} else {
1691da177e4SLinus Torvalds 		/* Rollback failed. */
1701da177e4SLinus Torvalds 		ntfs_error(vi->i_sb, "Failed to map subsequent page (error "
1711da177e4SLinus Torvalds 				"%li) and rollback failed (error %i).  "
1721da177e4SLinus Torvalds 				"Aborting and leaving inconsistent metadata.  "
1731da177e4SLinus Torvalds 				"Unmount and run chkdsk.", PTR_ERR(page), pos);
1741da177e4SLinus Torvalds 		NVolSetErrors(NTFS_SB(vi->i_sb));
1751da177e4SLinus Torvalds 	}
1761da177e4SLinus Torvalds 	return PTR_ERR(page);
1771da177e4SLinus Torvalds }
1781da177e4SLinus Torvalds 
1791da177e4SLinus Torvalds #endif /* NTFS_RW */
180