Blame view

fs/hfsplus/wrapper.c 7.23 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /*
   *  linux/fs/hfsplus/wrapper.c
   *
   * Copyright (C) 2001
   * Brad Boyer (flar@allandria.com)
   * (C) 2003 Ardis Technologies <roman@ardistech.com>
   *
   * Handling of HFS wrappers around HFS+ volumes
   */
  
  #include <linux/fs.h>
  #include <linux/blkdev.h>
  #include <linux/cdrom.h>
  #include <linux/genhd.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
15
16
17
18
19
20
21
22
23
24
25
  #include <asm/unaligned.h>
  
  #include "hfsplus_fs.h"
  #include "hfsplus_raw.h"
  
  struct hfsplus_wd {
  	u32 ablk_size;
  	u16 ablk_start;
  	u16 embed_start;
  	u16 embed_count;
  };
52399b171   Christoph Hellwig   hfsplus: use raw ...
26
27
28
29
30
31
  static void hfsplus_end_io_sync(struct bio *bio, int err)
  {
  	if (err)
  		clear_bit(BIO_UPTODATE, &bio->bi_flags);
  	complete(bio->bi_private);
  }
6596528e3   Seth Forshee   hfsplus: ensure b...
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  /*
   * hfsplus_submit_bio - Perfrom block I/O
   * @sb: super block of volume for I/O
   * @sector: block to read or write, for blocks of HFSPLUS_SECTOR_SIZE bytes
   * @buf: buffer for I/O
   * @data: output pointer for location of requested data
   * @rw: direction of I/O
   *
   * The unit of I/O is hfsplus_min_io_size(sb), which may be bigger than
   * HFSPLUS_SECTOR_SIZE, and @buf must be sized accordingly. On reads
   * @data will return a pointer to the start of the requested sector,
   * which may not be the same location as @buf.
   *
   * If @sector is not aligned to the bdev logical block size it will
   * be rounded down. For writes this means that @buf should contain data
   * that starts at the rounded-down address. As long as the data was
   * read using hfsplus_submit_bio() and the same buffer is used things
   * will work correctly.
   */
  int hfsplus_submit_bio(struct super_block *sb, sector_t sector,
  		void *buf, void **data, int rw)
52399b171   Christoph Hellwig   hfsplus: use raw ...
53
54
55
  {
  	DECLARE_COMPLETION_ONSTACK(wait);
  	struct bio *bio;
50176ddef   Seth Forshee   hfsplus: add miss...
56
  	int ret = 0;
6596528e3   Seth Forshee   hfsplus: ensure b...
57
58
59
60
61
62
63
64
65
66
67
68
69
  	unsigned int io_size;
  	loff_t start;
  	int offset;
  
  	/*
  	 * Align sector to hardware sector size and find offset. We
  	 * assume that io_size is a power of two, which _should_
  	 * be true.
  	 */
  	io_size = hfsplus_min_io_size(sb);
  	start = (loff_t)sector << HFSPLUS_SECTOR_SHIFT;
  	offset = start & (io_size - 1);
  	sector &= ~((io_size >> HFSPLUS_SECTOR_SHIFT) - 1);
52399b171   Christoph Hellwig   hfsplus: use raw ...
70
71
72
  
  	bio = bio_alloc(GFP_NOIO, 1);
  	bio->bi_sector = sector;
6596528e3   Seth Forshee   hfsplus: ensure b...
73
  	bio->bi_bdev = sb->s_bdev;
52399b171   Christoph Hellwig   hfsplus: use raw ...
74
75
  	bio->bi_end_io = hfsplus_end_io_sync;
  	bio->bi_private = &wait;
6596528e3   Seth Forshee   hfsplus: ensure b...
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  	if (!(rw & WRITE) && data)
  		*data = (u8 *)buf + offset;
  
  	while (io_size > 0) {
  		unsigned int page_offset = offset_in_page(buf);
  		unsigned int len = min_t(unsigned int, PAGE_SIZE - page_offset,
  					 io_size);
  
  		ret = bio_add_page(bio, virt_to_page(buf), len, page_offset);
  		if (ret != len) {
  			ret = -EIO;
  			goto out;
  		}
  		io_size -= len;
  		buf = (u8 *)buf + len;
  	}
52399b171   Christoph Hellwig   hfsplus: use raw ...
92
93
94
95
96
  
  	submit_bio(rw, bio);
  	wait_for_completion(&wait);
  
  	if (!bio_flagged(bio, BIO_UPTODATE))
50176ddef   Seth Forshee   hfsplus: add miss...
97
  		ret = -EIO;
6596528e3   Seth Forshee   hfsplus: ensure b...
98
  out:
50176ddef   Seth Forshee   hfsplus: add miss...
99
  	bio_put(bio);
6596528e3   Seth Forshee   hfsplus: ensure b...
100
  	return ret < 0 ? ret : 0;
52399b171   Christoph Hellwig   hfsplus: use raw ...
101
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
102
103
104
105
  static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)
  {
  	u32 extent;
  	u16 attrib;
2179d372d   David Elliott   [PATCH] hfs: add ...
106
  	__be16 sig;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
107

2179d372d   David Elliott   [PATCH] hfs: add ...
108
109
110
  	sig = *(__be16 *)(bufptr + HFSP_WRAPOFF_EMBEDSIG);
  	if (sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIG) &&
  	    sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIGX))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
111
112
113
114
115
116
  		return 0;
  
  	attrib = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ATTRIB));
  	if (!(attrib & HFSP_WRAP_ATTRIB_SLOCK) ||
  	   !(attrib & HFSP_WRAP_ATTRIB_SPARED))
  		return 0;
2753cc281   Anton Salikhmetov   hfsplus: over 80 ...
117
118
  	wd->ablk_size =
  		be32_to_cpu(*(__be32 *)(bufptr + HFSP_WRAPOFF_ABLKSIZE));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
119
120
121
122
  	if (wd->ablk_size < HFSPLUS_SECTOR_SIZE)
  		return 0;
  	if (wd->ablk_size % HFSPLUS_SECTOR_SIZE)
  		return 0;
2753cc281   Anton Salikhmetov   hfsplus: over 80 ...
123
124
  	wd->ablk_start =
  		be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ABLKSTART));
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
125

8b3789e5d   Harvey Harrison   hfsplus: use get/...
126
  	extent = get_unaligned_be32(bufptr + HFSP_WRAPOFF_EMBEDEXT);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
  	wd->embed_start = (extent >> 16) & 0xFFFF;
  	wd->embed_count = extent & 0xFFFF;
  
  	return 1;
  }
  
  static int hfsplus_get_last_session(struct super_block *sb,
  				    sector_t *start, sector_t *size)
  {
  	struct cdrom_multisession ms_info;
  	struct cdrom_tocentry te;
  	int res;
  
  	/* default values */
  	*start = 0;
  	*size = sb->s_bdev->bd_inode->i_size >> 9;
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
143
144
  	if (HFSPLUS_SB(sb)->session >= 0) {
  		te.cdte_track = HFSPLUS_SB(sb)->session;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
  		te.cdte_format = CDROM_LBA;
2753cc281   Anton Salikhmetov   hfsplus: over 80 ...
146
147
  		res = ioctl_by_bdev(sb->s_bdev,
  			CDROMREADTOCENTRY, (unsigned long)&te);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
148
149
150
151
  		if (!res && (te.cdte_ctrl & CDROM_DATA_TRACK) == 4) {
  			*start = (sector_t)te.cdte_addr.lba << 2;
  			return 0;
  		}
634725a92   Roman Zippel   [PATCH] hfs: clea...
152
153
  		printk(KERN_ERR "hfs: invalid session number or type of track
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
154
155
156
  		return -EINVAL;
  	}
  	ms_info.addr_format = CDROM_LBA;
2753cc281   Anton Salikhmetov   hfsplus: over 80 ...
157
158
  	res = ioctl_by_bdev(sb->s_bdev, CDROMMULTISESSION,
  		(unsigned long)&ms_info);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
159
160
161
162
163
164
165
166
167
  	if (!res && ms_info.xa_flag)
  		*start = (sector_t)ms_info.addr.lba << 2;
  	return 0;
  }
  
  /* Find the volume header and fill in some minimum bits in superblock */
  /* Takes in super block, returns true if good data read */
  int hfsplus_read_wrapper(struct super_block *sb)
  {
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
168
  	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
169
170
171
  	struct hfsplus_wd wd;
  	sector_t part_start, part_size;
  	u32 blocksize;
52399b171   Christoph Hellwig   hfsplus: use raw ...
172
  	int error = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
173

52399b171   Christoph Hellwig   hfsplus: use raw ...
174
  	error = -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
175
176
  	blocksize = sb_min_blocksize(sb, HFSPLUS_SECTOR_SIZE);
  	if (!blocksize)
52399b171   Christoph Hellwig   hfsplus: use raw ...
177
  		goto out;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
178
179
  
  	if (hfsplus_get_last_session(sb, &part_start, &part_size))
52399b171   Christoph Hellwig   hfsplus: use raw ...
180
  		goto out;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
181

52399b171   Christoph Hellwig   hfsplus: use raw ...
182
  	error = -ENOMEM;
6596528e3   Seth Forshee   hfsplus: ensure b...
183
184
  	sbi->s_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
  	if (!sbi->s_vhdr_buf)
52399b171   Christoph Hellwig   hfsplus: use raw ...
185
  		goto out;
6596528e3   Seth Forshee   hfsplus: ensure b...
186
187
  	sbi->s_backup_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
  	if (!sbi->s_backup_vhdr_buf)
52399b171   Christoph Hellwig   hfsplus: use raw ...
188
189
190
  		goto out_free_vhdr;
  
  reread:
6596528e3   Seth Forshee   hfsplus: ensure b...
191
192
193
  	error = hfsplus_submit_bio(sb, part_start + HFSPLUS_VOLHEAD_SECTOR,
  				   sbi->s_vhdr_buf, (void **)&sbi->s_vhdr,
  				   READ);
52399b171   Christoph Hellwig   hfsplus: use raw ...
194
195
196
197
198
199
200
201
202
203
204
205
  	if (error)
  		goto out_free_backup_vhdr;
  
  	error = -EINVAL;
  	switch (sbi->s_vhdr->signature) {
  	case cpu_to_be16(HFSPLUS_VOLHEAD_SIGX):
  		set_bit(HFSPLUS_SB_HFSX, &sbi->flags);
  		/*FALLTHRU*/
  	case cpu_to_be16(HFSPLUS_VOLHEAD_SIG):
  		break;
  	case cpu_to_be16(HFSP_WRAP_MAGIC):
  		if (!hfsplus_read_mdb(sbi->s_vhdr, &wd))
a1dbcef01   Chuck Ebbert   hfsplus: fix two ...
206
  			goto out_free_backup_vhdr;
52399b171   Christoph Hellwig   hfsplus: use raw ...
207
  		wd.ablk_size >>= HFSPLUS_SECTOR_SHIFT;
4ba2d5fdc   Christoph Hellwig   hfsplus: fix over...
208
209
210
  		part_start += (sector_t)wd.ablk_start +
  			       (sector_t)wd.embed_start * wd.ablk_size;
  		part_size = (sector_t)wd.embed_count * wd.ablk_size;
52399b171   Christoph Hellwig   hfsplus: use raw ...
211
212
213
214
215
  		goto reread;
  	default:
  		/*
  		 * Check for a partition block.
  		 *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
216
217
218
  		 * (should do this only for cdrom/loop though)
  		 */
  		if (hfs_part_find(sb, &part_start, &part_size))
a1dbcef01   Chuck Ebbert   hfsplus: fix two ...
219
  			goto out_free_backup_vhdr;
52399b171   Christoph Hellwig   hfsplus: use raw ...
220
221
  		goto reread;
  	}
6596528e3   Seth Forshee   hfsplus: ensure b...
222
223
224
  	error = hfsplus_submit_bio(sb, part_start + part_size - 2,
  				   sbi->s_backup_vhdr_buf,
  				   (void **)&sbi->s_backup_vhdr, READ);
52399b171   Christoph Hellwig   hfsplus: use raw ...
225
226
227
228
229
230
231
232
233
  	if (error)
  		goto out_free_backup_vhdr;
  
  	error = -EINVAL;
  	if (sbi->s_backup_vhdr->signature != sbi->s_vhdr->signature) {
  		printk(KERN_WARNING
  			"hfs: invalid secondary volume header
  ");
  		goto out_free_backup_vhdr;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
234
  	}
52399b171   Christoph Hellwig   hfsplus: use raw ...
235
  	blocksize = be32_to_cpu(sbi->s_vhdr->blocksize);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236

52399b171   Christoph Hellwig   hfsplus: use raw ...
237
238
  	/*
  	 * Block size must be at least as large as a sector and a multiple of 2.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
239
  	 */
52399b171   Christoph Hellwig   hfsplus: use raw ...
240
241
  	if (blocksize < HFSPLUS_SECTOR_SIZE || ((blocksize - 1) & blocksize))
  		goto out_free_backup_vhdr;
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
242
243
  	sbi->alloc_blksz = blocksize;
  	sbi->alloc_blksz_shift = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
244
  	while ((blocksize >>= 1) != 0)
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
245
246
  		sbi->alloc_blksz_shift++;
  	blocksize = min(sbi->alloc_blksz, (u32)PAGE_SIZE);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
247

52399b171   Christoph Hellwig   hfsplus: use raw ...
248
249
250
  	/*
  	 * Align block size to block offset.
  	 */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
251
252
253
254
  	while (part_start & ((blocksize >> HFSPLUS_SECTOR_SHIFT) - 1))
  		blocksize >>= 1;
  
  	if (sb_set_blocksize(sb, blocksize) != blocksize) {
2753cc281   Anton Salikhmetov   hfsplus: over 80 ...
255
256
257
  		printk(KERN_ERR "hfs: unable to set blocksize to %u!
  ",
  			blocksize);
52399b171   Christoph Hellwig   hfsplus: use raw ...
258
  		goto out_free_backup_vhdr;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
259
  	}
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
260
261
  	sbi->blockoffset =
  		part_start >> (sb->s_blocksize_bits - HFSPLUS_SECTOR_SHIFT);
52399b171   Christoph Hellwig   hfsplus: use raw ...
262
  	sbi->part_start = part_start;
dd73a01a3   Christoph Hellwig   hfsplus: fix HFSP...
263
264
  	sbi->sect_count = part_size;
  	sbi->fs_shift = sbi->alloc_blksz_shift - sb->s_blocksize_bits;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
265
  	return 0;
52399b171   Christoph Hellwig   hfsplus: use raw ...
266
267
  
  out_free_backup_vhdr:
f588c960f   Seth Forshee   hfsplus: Fix kfre...
268
  	kfree(sbi->s_backup_vhdr_buf);
52399b171   Christoph Hellwig   hfsplus: use raw ...
269
  out_free_vhdr:
f588c960f   Seth Forshee   hfsplus: Fix kfre...
270
  	kfree(sbi->s_vhdr_buf);
52399b171   Christoph Hellwig   hfsplus: use raw ...
271
272
  out:
  	return error;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
273
  }