static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
		     const struct LOG_REC_HDR *lrh, u32 op, void *data,
		     u32 dlen, u32 rec_len, const u64 *rlsn)
{
	int err = 0;
	struct ntfs_sb_info *sbi = log->ni->mi.sbi;
	struct inode *inode = NULL, *inode_parent;
	struct mft_inode *mi = NULL, *mi2_child = NULL;
	CLST rno = 0, rno_base = 0;
	struct INDEX_BUFFER *ib = NULL;
	struct MFT_REC *rec = NULL;
	struct ATTRIB *attr = NULL, *attr2;
	struct INDEX_HDR *hdr;
	struct INDEX_ROOT *root;
	struct NTFS_DE *e, *e1, *e2;
	struct NEW_ATTRIBUTE_SIZES *new_sz;
	struct ATTR_FILE_NAME *fname;
	struct OpenAttr *oa, *oa2;
	u32 nsize, t32, asize, used, esize, bmp_off, bmp_bits;
	u16 id, id2;
	u32 record_size = sbi->record_size;
	u64 t64;
	u16 roff = le16_to_cpu(lrh->record_off);
	u16 aoff = le16_to_cpu(lrh->attr_off);
	u64 lco = 0;
	u64 cbo = (u64)le16_to_cpu(lrh->cluster_off) << SECTOR_SHIFT;
	u64 tvo = le64_to_cpu(lrh->target_vcn) << sbi->cluster_bits;
	u64 vbo = cbo + tvo;
	void *buffer_le = NULL;
	u32 bytes = 0;
	bool a_dirty = false;
	u16 data_off;

	oa = oe->ptr;

	/* Big switch to prepare. */
	switch (op) {
	/* ============================================================
	 * Process MFT records, as described by the current log record.
	 * ============================================================
	 */
	case InitializeFileRecordSegment:
	case DeallocateFileRecordSegment:
	case WriteEndOfFileRecordSegment:
	case CreateAttribute:
	case DeleteAttribute:
	case UpdateResidentValue:
	case UpdateMappingPairs:
	case SetNewAttributeSizes:
	case AddIndexEntryRoot:
	case DeleteIndexEntryRoot:
	case SetIndexEntryVcnRoot:
	case UpdateFileNameRoot:
	case UpdateRecordDataRoot:
	case ZeroEndOfFileRecord:
		rno = vbo >> sbi->record_bits;
		inode = ilookup(sbi->sb, rno);
		if (inode) {
			mi = &ntfs_i(inode)->mi;
		} else if (op == InitializeFileRecordSegment) {
			mi = kzalloc(sizeof(struct mft_inode), GFP_NOFS);
			if (!mi)
				return -ENOMEM;
			err = mi_format_new(mi, sbi, rno, 0, false);
			if (err)
				goto out;
		} else {
			/* Read from disk. */
			err = mi_get(sbi, rno, &mi);
			if (err)
				return err;
		}
		rec = mi->mrec;

		if (op == DeallocateFileRecordSegment)
			goto skip_load_parent;

		if (InitializeFileRecordSegment != op) {
			if (rec->rhdr.sign == NTFS_BAAD_SIGNATURE)
				goto dirty_vol;
			if (!check_lsn(&rec->rhdr, rlsn))
				goto out;
			if (!check_file_record(rec, NULL, sbi))
				goto dirty_vol;
			attr = Add2Ptr(rec, roff);
		}

		if (is_rec_base(rec) || InitializeFileRecordSegment == op) {
			rno_base = rno;
			goto skip_load_parent;
		}

		rno_base = ino_get(&rec->parent_ref);
		inode_parent = ntfs_iget5(sbi->sb, &rec->parent_ref, NULL);
		if (IS_ERR(inode_parent))
			goto skip_load_parent;

		if (is_bad_inode(inode_parent)) {
			iput(inode_parent);
			goto skip_load_parent;
		}

		if (ni_load_mi_ex(ntfs_i(inode_parent), rno, &mi2_child)) {
			iput(inode_parent);
		} else {
			if (mi2_child->mrec != mi->mrec)
				memcpy(mi2_child->mrec, mi->mrec,
				       sbi->record_size);

			if (inode)
				iput(inode);
			else if (mi)
				mi_put(mi);

			inode = inode_parent;
			mi = mi2_child;
			rec = mi2_child->mrec;
			attr = Add2Ptr(rec, roff);
		}

skip_load_parent:
		inode_parent = NULL;
		break;

	/*
	 * Process attributes, as described by the current log record.
	 */
	case UpdateNonresidentValue:
	case AddIndexEntryAllocation:
	case DeleteIndexEntryAllocation:
	case WriteEndOfIndexBuffer:
	case SetIndexEntryVcnAllocation:
	case UpdateFileNameAllocation:
	case SetBitsInNonresidentBitMap:
	case ClearBitsInNonresidentBitMap:
	case UpdateRecordDataAllocation:
		attr = oa->attr;
		bytes = UpdateNonresidentValue == op ? dlen : 0;
		lco = (u64)le16_to_cpu(lrh->lcns_follow) << sbi->cluster_bits;

		if (attr->type == ATTR_ALLOC) {
			t32 = le32_to_cpu(oe->bytes_per_index);
			if (bytes < t32)
				bytes = t32;
		}

		if (!bytes)
			bytes = lco - cbo;

		bytes += roff;
		if (attr->type == ATTR_ALLOC)
			bytes = (bytes + 511) & ~511; // align

		buffer_le = kmalloc(bytes, GFP_NOFS);
		if (!buffer_le)
			return -ENOMEM;

		err = ntfs_read_run_nb(sbi, oa->run1, vbo, buffer_le, bytes,
				       NULL);
		if (err)
			goto out;

		if (attr->type == ATTR_ALLOC && *(int *)buffer_le)
			ntfs_fix_post_read(buffer_le, bytes, false);
		break;

	default:
		WARN_ON(1);
	}

	/* Big switch to do operation. */
	switch (op) {
	case InitializeFileRecordSegment:
		if (roff + dlen > record_size)
			goto dirty_vol;

		memcpy(Add2Ptr(rec, roff), data, dlen);
		mi->dirty = true;
		break;

	case DeallocateFileRecordSegment:
		clear_rec_inuse(rec);
		le16_add_cpu(&rec->seq, 1);
		mi->dirty = true;
		break;

	case WriteEndOfFileRecordSegment:
		attr2 = (struct ATTRIB *)data;
		if (!check_if_attr(rec, lrh) || roff + dlen > record_size)
			goto dirty_vol;

		memmove(attr, attr2, dlen);
		rec->used = cpu_to_le32(ALIGN(roff + dlen, 8));

		mi->dirty = true;
		break;

	case CreateAttribute:
		attr2 = (struct ATTRIB *)data;
		asize = le32_to_cpu(attr2->size);
		used = le32_to_cpu(rec->used);

		if (!check_if_attr(rec, lrh) || dlen < SIZEOF_RESIDENT ||
		    !IS_ALIGNED(asize, 8) ||
		    Add2Ptr(attr2, asize) > Add2Ptr(lrh, rec_len) ||
		    dlen > record_size - used) {
			goto dirty_vol;
		}

		memmove(Add2Ptr(attr, asize), attr, used - roff);
		memcpy(attr, attr2, asize);

		rec->used = cpu_to_le32(used + asize);
		id = le16_to_cpu(rec->next_attr_id);
		id2 = le16_to_cpu(attr2->id);
		if (id <= id2)
			rec->next_attr_id = cpu_to_le16(id2 + 1);
		if (is_attr_indexed(attr))
			le16_add_cpu(&rec->hard_links, 1);

		oa2 = find_loaded_attr(log, attr, rno_base);
		if (oa2) {
			void *p2 = kmemdup(attr, le32_to_cpu(attr->size),
					   GFP_NOFS);
			if (p2) {
				// run_close(oa2->run1);
				kfree(oa2->attr);
				oa2->attr = p2;
			}
		}

		mi->dirty = true;
		break;

	case DeleteAttribute:
		asize = le32_to_cpu(attr->size);
		used = le32_to_cpu(rec->used);

		if (!check_if_attr(rec, lrh))
			goto dirty_vol;

		rec->used = cpu_to_le32(used - asize);
		if (is_attr_indexed(attr))
			le16_add_cpu(&rec->hard_links, -1);

		memmove(attr, Add2Ptr(attr, asize), used - asize - roff);

		mi->dirty = true;
		break;

	case UpdateResidentValue:
		nsize = aoff + dlen;

		if (!check_if_attr(rec, lrh))
			goto dirty_vol;

		asize = le32_to_cpu(attr->size);
		used = le32_to_cpu(rec->used);

		if (lrh->redo_len == lrh->undo_len) {
			if (nsize > asize)
				goto dirty_vol;
			goto move_data;
		}

		if (nsize > asize && nsize - asize > record_size - used)
			goto dirty_vol;

		nsize = ALIGN(nsize, 8);
		data_off = le16_to_cpu(attr->res.data_off);

		if (nsize < asize) {
			memmove(Add2Ptr(attr, aoff), data, dlen);
			data = NULL; // To skip below memmove().
		}

		memmove(Add2Ptr(attr, nsize), Add2Ptr(attr, asize),
			used - le16_to_cpu(lrh->record_off) - asize);

		rec->used = cpu_to_le32(used + nsize - asize);
		attr->size = cpu_to_le32(nsize);
		attr->res.data_size = cpu_to_le32(aoff + dlen - data_off);

move_data:
		if (data)
			memmove(Add2Ptr(attr, aoff), data, dlen);

		oa2 = find_loaded_attr(log, attr, rno_base);
		if (oa2) {
			void *p2 = kmemdup(attr, le32_to_cpu(attr->size),
					   GFP_NOFS);
			if (p2) {
				// run_close(&oa2->run0);
				oa2->run1 = &oa2->run0;
				kfree(oa2->attr);
				oa2->attr = p2;
			}
		}

		mi->dirty = true;
		break;

	case UpdateMappingPairs:
		nsize = aoff + dlen;
		asize = le32_to_cpu(attr->size);
		used = le32_to_cpu(rec->used);

		if (!check_if_attr(rec, lrh) || !attr->non_res ||
		    aoff < le16_to_cpu(attr->nres.run_off) || aoff > asize ||
		    (nsize > asize && nsize - asize > record_size - used)) {
			goto dirty_vol;
		}

		nsize = ALIGN(nsize, 8);

		memmove(Add2Ptr(attr, nsize), Add2Ptr(attr, asize),
			used - le16_to_cpu(lrh->record_off) - asize);
		rec->used = cpu_to_le32(used + nsize - asize);
		attr->size = cpu_to_le32(nsize);
		memmove(Add2Ptr(attr, aoff), data, dlen);

		if (run_get_highest_vcn(le64_to_cpu(attr->nres.svcn),
					attr_run(attr), &t64)) {
			goto dirty_vol;
		}

		attr->nres.evcn = cpu_to_le64(t64);
		oa2 = find_loaded_attr(log, attr, rno_base);
		if (oa2 && oa2->attr->non_res)
			oa2->attr->nres.evcn = attr->nres.evcn;

		mi->dirty = true;
		break;

	case SetNewAttributeSizes:
		new_sz = data;
		if (!check_if_attr(rec, lrh) || !attr->non_res)
			goto dirty_vol;

		attr->nres.alloc_size = new_sz->alloc_size;
		attr->nres.data_size = new_sz->data_size;
		attr->nres.valid_size = new_sz->valid_size;

		if (dlen >= sizeof(struct NEW_ATTRIBUTE_SIZES))
			attr->nres.total_size = new_sz->total_size;

		oa2 = find_loaded_attr(log, attr, rno_base);
		if (oa2) {
			void *p2 = kmemdup(attr, le32_to_cpu(attr->size),
					   GFP_NOFS);
			if (p2) {
				kfree(oa2->attr);
				oa2->attr = p2;
			}
		}
		mi->dirty = true;
		break;

	case AddIndexEntryRoot:
		e = (struct NTFS_DE *)data;
		esize = le16_to_cpu(e->size);
		root = resident_data(attr);
		hdr = &root->ihdr;
		used = le32_to_cpu(hdr->used);

		if (!check_if_index_root(rec, lrh) ||
		    !check_if_root_index(attr, hdr, lrh) ||
		    Add2Ptr(data, esize) > Add2Ptr(lrh, rec_len) ||
		    esize > le32_to_cpu(rec->total) - le32_to_cpu(rec->used)) {
			goto dirty_vol;
		}

		e1 = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));

		change_attr_size(rec, attr, le32_to_cpu(attr->size) + esize);

		memmove(Add2Ptr(e1, esize), e1,
			PtrOffset(e1, Add2Ptr(hdr, used)));
		memmove(e1, e, esize);

		le32_add_cpu(&attr->res.data_size, esize);
		hdr->used = cpu_to_le32(used + esize);
		le32_add_cpu(&hdr->total, esize);

		mi->dirty = true;
		break;

	case DeleteIndexEntryRoot:
		root = resident_data(attr);
		hdr = &root->ihdr;
		used = le32_to_cpu(hdr->used);

		if (!check_if_index_root(rec, lrh) ||
		    !check_if_root_index(attr, hdr, lrh)) {
			goto dirty_vol;
		}

		e1 = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));
		esize = le16_to_cpu(e1->size);
		e2 = Add2Ptr(e1, esize);

		memmove(e1, e2, PtrOffset(e2, Add2Ptr(hdr, used)));

		le32_sub_cpu(&attr->res.data_size, esize);
		hdr->used = cpu_to_le32(used - esize);
		le32_sub_cpu(&hdr->total, esize);

		change_attr_size(rec, attr, le32_to_cpu(attr->size) - esize);

		mi->dirty = true;
		break;

	case SetIndexEntryVcnRoot:
		root = resident_data(attr);
		hdr = &root->ihdr;

		if (!check_if_index_root(rec, lrh) ||
		    !check_if_root_index(attr, hdr, lrh)) {
			goto dirty_vol;
		}

		e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));

		de_set_vbn_le(e, *(__le64 *)data);
		mi->dirty = true;
		break;

	case UpdateFileNameRoot:
		root = resident_data(attr);
		hdr = &root->ihdr;

		if (!check_if_index_root(rec, lrh) ||
		    !check_if_root_index(attr, hdr, lrh)) {
			goto dirty_vol;
		}

		e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));
		fname = (struct ATTR_FILE_NAME *)(e + 1);
		memmove(&fname->dup, data, sizeof(fname->dup)); //
		mi->dirty = true;
		break;

	case UpdateRecordDataRoot:
		root = resident_data(attr);
		hdr = &root->ihdr;

		if (!check_if_index_root(rec, lrh) ||
		    !check_if_root_index(attr, hdr, lrh)) {
			goto dirty_vol;
		}

		e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off));

		memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen);

		mi->dirty = true;
		break;

	case ZeroEndOfFileRecord:
		if (roff + dlen > record_size)
			goto dirty_vol;

		memset(attr, 0, dlen);
		mi->dirty = true;
		break;

	case UpdateNonresidentValue:
		if (lco < cbo + roff + dlen)
			goto dirty_vol;

		memcpy(Add2Ptr(buffer_le, roff), data, dlen);

		a_dirty = true;
		if (attr->type == ATTR_ALLOC)
			ntfs_fix_pre_write(buffer_le, bytes);
		break;

	case AddIndexEntryAllocation:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = data;
		esize = le16_to_cpu(e->size);
		e1 = Add2Ptr(ib, aoff);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;
		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;

		used = le32_to_cpu(hdr->used);

		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff) ||
		    Add2Ptr(e, esize) > Add2Ptr(lrh, rec_len) ||
		    used + esize > le32_to_cpu(hdr->total)) {
			goto dirty_vol;
		}

		memmove(Add2Ptr(e1, esize), e1,
			PtrOffset(e1, Add2Ptr(hdr, used)));
		memcpy(e1, e, esize);

		hdr->used = cpu_to_le32(used + esize);

		a_dirty = true;

		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	case DeleteIndexEntryAllocation:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = Add2Ptr(ib, aoff);
		esize = le16_to_cpu(e->size);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;
		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;

		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff)) {
			goto dirty_vol;
		}

		e1 = Add2Ptr(e, esize);
		nsize = esize;
		used = le32_to_cpu(hdr->used);

		memmove(e, e1, PtrOffset(e1, Add2Ptr(hdr, used)));

		hdr->used = cpu_to_le32(used - nsize);

		a_dirty = true;

		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	case WriteEndOfIndexBuffer:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = Add2Ptr(ib, aoff);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;
		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;
		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff) ||
		    aoff + dlen > offsetof(struct INDEX_BUFFER, ihdr) +
					  le32_to_cpu(hdr->total)) {
			goto dirty_vol;
		}

		hdr->used = cpu_to_le32(dlen + PtrOffset(hdr, e));
		memmove(e, data, dlen);

		a_dirty = true;
		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	case SetIndexEntryVcnAllocation:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = Add2Ptr(ib, aoff);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;

		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;
		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff)) {
			goto dirty_vol;
		}

		de_set_vbn_le(e, *(__le64 *)data);

		a_dirty = true;
		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	case UpdateFileNameAllocation:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = Add2Ptr(ib, aoff);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;

		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;
		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff)) {
			goto dirty_vol;
		}

		fname = (struct ATTR_FILE_NAME *)(e + 1);
		memmove(&fname->dup, data, sizeof(fname->dup));

		a_dirty = true;
		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	case SetBitsInNonresidentBitMap:
		bmp_off =
			le32_to_cpu(((struct BITMAP_RANGE *)data)->bitmap_off);
		bmp_bits = le32_to_cpu(((struct BITMAP_RANGE *)data)->bits);

		if (cbo + (bmp_off + 7) / 8 > lco ||
		    cbo + ((bmp_off + bmp_bits + 7) / 8) > lco) {
			goto dirty_vol;
		}

		__bitmap_set(Add2Ptr(buffer_le, roff), bmp_off, bmp_bits);
		a_dirty = true;
		break;

	case ClearBitsInNonresidentBitMap:
		bmp_off =
			le32_to_cpu(((struct BITMAP_RANGE *)data)->bitmap_off);
		bmp_bits = le32_to_cpu(((struct BITMAP_RANGE *)data)->bits);

		if (cbo + (bmp_off + 7) / 8 > lco ||
		    cbo + ((bmp_off + bmp_bits + 7) / 8) > lco) {
			goto dirty_vol;
		}

		__bitmap_clear(Add2Ptr(buffer_le, roff), bmp_off, bmp_bits);
		a_dirty = true;
		break;

	case UpdateRecordDataAllocation:
		ib = Add2Ptr(buffer_le, roff);
		hdr = &ib->ihdr;
		e = Add2Ptr(ib, aoff);

		if (is_baad(&ib->rhdr))
			goto dirty_vol;

		if (!check_lsn(&ib->rhdr, rlsn))
			goto out;
		if (!check_index_buffer(ib, bytes) ||
		    !check_if_alloc_index(hdr, aoff)) {
			goto dirty_vol;
		}

		memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen);

		a_dirty = true;
		ntfs_fix_pre_write(&ib->rhdr, bytes);
		break;

	default:
		WARN_ON(1);
	}

	if (rlsn) {
		__le64 t64 = cpu_to_le64(*rlsn);

		if (rec)
			rec->rhdr.lsn = t64;
		if (ib)
			ib->rhdr.lsn = t64;
	}

	if (mi && mi->dirty) {
		err = mi_write(mi, 0);
		if (err)
			goto out;
	}

	if (a_dirty) {
		attr = oa->attr;
		err = ntfs_sb_write_run(sbi, oa->run1, vbo, buffer_le, bytes, 0);
		if (err)
			goto out;
	}

out:

	if (inode)
		iput(inode);
	else if (mi != mi2_child)
		mi_put(mi);

	kfree(buffer_le);

	return err;

dirty_vol:
	log->set_dirty = true;
	goto out;
}