1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ext4_utils.h"
18#include "ext4.h"
19#include "ext4_extents.h"
20#include "extent.h"
21
22#include <sparse/sparse.h>
23
24#include <stdlib.h>
25#include <stdio.h>
26
27
28/* Creates data buffers for the first backing_len bytes of a block allocation
29   and queues them to be written */
30static u8 *extent_create_backing(struct block_allocation *alloc,
31	u64 backing_len)
32{
33	u8 *data = calloc(backing_len, 1);
34	if (!data)
35		critical_error_errno("calloc");
36
37	u8 *ptr = data;
38	for (; alloc != NULL && backing_len > 0; get_next_region(alloc)) {
39		u32 region_block;
40		u32 region_len;
41		u32 len;
42		get_region(alloc, &region_block, &region_len);
43
44		len = min(region_len * info.block_size, backing_len);
45
46		sparse_file_add_data(info.sparse_file, ptr, len, region_block);
47		ptr += len;
48		backing_len -= len;
49	}
50
51	return data;
52}
53
54/* Queues each chunk of a file to be written to contiguous data block
55   regions */
56static void extent_create_backing_file(struct block_allocation *alloc,
57	u64 backing_len, const char *filename)
58{
59	off64_t offset = 0;
60	for (; alloc != NULL && backing_len > 0; get_next_region(alloc)) {
61		u32 region_block;
62		u32 region_len;
63		u32 len;
64		get_region(alloc, &region_block, &region_len);
65
66		len = min(region_len * info.block_size, backing_len);
67
68		sparse_file_add_file(info.sparse_file, filename, offset, len,
69				region_block);
70		offset += len;
71		backing_len -= len;
72	}
73}
74
75static struct block_allocation *do_inode_allocate_extents(
76	struct ext4_inode *inode, u64 len)
77{
78	u32 block_len = DIV_ROUND_UP(len, info.block_size);
79	struct block_allocation *alloc = allocate_blocks(block_len + 1);
80	u32 extent_block = 0;
81	u32 file_block = 0;
82	struct ext4_extent *extent;
83	u64 blocks;
84
85	if (alloc == NULL) {
86		error("Failed to allocate %d blocks\n", block_len + 1);
87		return NULL;
88	}
89
90	int allocation_len = block_allocation_num_regions(alloc);
91	if (allocation_len <= 3) {
92		reduce_allocation(alloc, 1);
93	} else {
94		reserve_oob_blocks(alloc, 1);
95		extent_block = get_oob_block(alloc, 0);
96	}
97
98	if (!extent_block) {
99		struct ext4_extent_header *hdr =
100			(struct ext4_extent_header *)&inode->i_block[0];
101		hdr->eh_magic = EXT4_EXT_MAGIC;
102		hdr->eh_entries = allocation_len;
103		hdr->eh_max = 3;
104		hdr->eh_generation = 0;
105		hdr->eh_depth = 0;
106
107		extent = (struct ext4_extent *)&inode->i_block[3];
108	} else {
109		struct ext4_extent_header *hdr =
110			(struct ext4_extent_header *)&inode->i_block[0];
111		hdr->eh_magic = EXT4_EXT_MAGIC;
112		hdr->eh_entries = 1;
113		hdr->eh_max = 3;
114		hdr->eh_generation = 0;
115		hdr->eh_depth = 1;
116
117		struct ext4_extent_idx *idx =
118			(struct ext4_extent_idx *)&inode->i_block[3];
119		idx->ei_block = 0;
120		idx->ei_leaf_lo = extent_block;
121		idx->ei_leaf_hi = 0;
122		idx->ei_unused = 0;
123
124		u8 *data = calloc(info.block_size, 1);
125		if (!data)
126			critical_error_errno("calloc");
127
128		sparse_file_add_data(info.sparse_file, data, info.block_size,
129				extent_block);
130
131		if (((int)(info.block_size - sizeof(struct ext4_extent_header) /
132				sizeof(struct ext4_extent))) < allocation_len) {
133			error("File size %llu is too big to fit in a single extent block\n",
134					len);
135			return NULL;
136		}
137
138		hdr = (struct ext4_extent_header *)data;
139		hdr->eh_magic = EXT4_EXT_MAGIC;
140		hdr->eh_entries = allocation_len;
141		hdr->eh_max = (info.block_size - sizeof(struct ext4_extent_header)) /
142			sizeof(struct ext4_extent);
143		hdr->eh_generation = 0;
144		hdr->eh_depth = 0;
145
146		extent = (struct ext4_extent *)(data +
147			sizeof(struct ext4_extent_header));
148	}
149
150	for (; !last_region(alloc); extent++, get_next_region(alloc)) {
151		u32 region_block;
152		u32 region_len;
153
154		get_region(alloc, &region_block, &region_len);
155		extent->ee_block = file_block;
156		extent->ee_len = region_len;
157		extent->ee_start_hi = 0;
158		extent->ee_start_lo = region_block;
159		file_block += region_len;
160	}
161
162	if (extent_block)
163		block_len += 1;
164
165	blocks = (u64)block_len * info.block_size / 512;
166
167	inode->i_flags |= EXT4_EXTENTS_FL;
168	inode->i_size_lo = len;
169	inode->i_size_high = len >> 32;
170	inode->i_blocks_lo = blocks;
171	inode->osd2.linux2.l_i_blocks_high = blocks >> 32;
172
173	rewind_alloc(alloc);
174
175	return alloc;
176}
177
178/* Allocates enough blocks to hold len bytes, with backing_len bytes in a data
179   buffer, and connects them to an inode.  Returns a pointer to the data
180   buffer. */
181u8 *inode_allocate_data_extents(struct ext4_inode *inode, u64 len,
182	u64 backing_len)
183{
184	struct block_allocation *alloc;
185	u8 *data = NULL;
186
187	alloc = do_inode_allocate_extents(inode, len);
188	if (alloc == NULL) {
189		error("failed to allocate extents for %llu bytes", len);
190		return NULL;
191	}
192
193	if (backing_len) {
194		data = extent_create_backing(alloc, backing_len);
195		if (!data)
196			error("failed to create backing for %llu bytes", backing_len);
197	}
198
199	free_alloc(alloc);
200
201	return data;
202}
203
204/* Allocates enough blocks to hold len bytes, queues them to be written
205   from a file, and connects them to an inode. */
206void inode_allocate_file_extents(struct ext4_inode *inode, u64 len,
207	const char *filename)
208{
209	struct block_allocation *alloc;
210
211	alloc = do_inode_allocate_extents(inode, len);
212	if (alloc == NULL) {
213		error("failed to allocate extents for %llu bytes", len);
214		return;
215	}
216
217	extent_create_backing_file(alloc, len, filename);
218
219	free_alloc(alloc);
220}
221
222/* Allocates enough blocks to hold len bytes and connects them to an inode */
223void inode_allocate_extents(struct ext4_inode *inode, u64 len)
224{
225	struct block_allocation *alloc;
226
227	alloc = do_inode_allocate_extents(inode, len);
228	if (alloc == NULL) {
229		error("failed to allocate extents for %llu bytes", len);
230		return;
231	}
232
233	free_alloc(alloc);
234}
235