1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2002,2003,2004,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
4 *
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <grub/disk.h>
20 #include <grub/err.h>
21 #include <grub/mm.h>
22 #include <grub/types.h>
23 #include <grub/partition.h>
24 #include <grub/misc.h>
25 #include <grub/time.h>
26 #include <grub/file.h>
27
28 GRUB_EXPORT(grub_disk_dev_register);
29 GRUB_EXPORT(grub_disk_dev_unregister);
30 GRUB_EXPORT(grub_disk_dev_iterate);
31
32 GRUB_EXPORT(grub_disk_open);
33 GRUB_EXPORT(grub_disk_close);
34 GRUB_EXPORT(grub_disk_read);
35 GRUB_EXPORT(grub_disk_read_ex);
36 GRUB_EXPORT(grub_disk_write);
37
38 GRUB_EXPORT(grub_disk_get_size);
39 GRUB_EXPORT(grub_disk_firmware_fini);
40 GRUB_EXPORT(grub_disk_firmware_is_tainted);
41
42 GRUB_EXPORT(grub_disk_ata_pass_through);
43
44 #define GRUB_CACHE_TIMEOUT 2
45
46 /* The last time the disk was used. */
47 static grub_uint64_t grub_last_time = 0;
48
49
50 /* Disk cache. */
51 struct grub_disk_cache
52 {
53 enum grub_disk_dev_id dev_id;
54 unsigned long disk_id;
55 grub_disk_addr_t sector;
56 char *data;
57 int lock;
58 };
59
60 static struct grub_disk_cache grub_disk_cache_table[GRUB_DISK_CACHE_NUM];
61
62 void (*grub_disk_firmware_fini) (void);
63 int grub_disk_firmware_is_tainted;
64
65 grub_err_t (* grub_disk_ata_pass_through) (grub_disk_t,
66 struct grub_disk_ata_pass_through_parms *);
67
68
69 #if 0
70 static unsigned long grub_disk_cache_hits;
71 static unsigned long grub_disk_cache_misses;
72
73 void
74 grub_disk_cache_get_performance (unsigned long *hits, unsigned long *misses)
75 {
76 *hits = grub_disk_cache_hits;
77 *misses = grub_disk_cache_misses;
78 }
79 #endif
80
81 static unsigned
grub_disk_cache_get_index(unsigned long dev_id,unsigned long disk_id,grub_disk_addr_t sector)82 grub_disk_cache_get_index (unsigned long dev_id, unsigned long disk_id,
83 grub_disk_addr_t sector)
84 {
85 return ((dev_id * 524287UL + disk_id * 2606459UL
86 + ((unsigned) (sector >> GRUB_DISK_CACHE_BITS)))
87 % GRUB_DISK_CACHE_NUM);
88 }
89
90 static void
grub_disk_cache_invalidate(unsigned long dev_id,unsigned long disk_id,grub_disk_addr_t sector)91 grub_disk_cache_invalidate (unsigned long dev_id, unsigned long disk_id,
92 grub_disk_addr_t sector)
93 {
94 unsigned index;
95 struct grub_disk_cache *cache;
96
97 sector &= ~(GRUB_DISK_CACHE_SIZE - 1);
98 index = grub_disk_cache_get_index (dev_id, disk_id, sector);
99 cache = grub_disk_cache_table + index;
100
101 if (cache->dev_id == dev_id && cache->disk_id == disk_id
102 && cache->sector == sector && cache->data)
103 {
104 cache->lock = 1;
105 grub_free (cache->data);
106 cache->data = 0;
107 cache->lock = 0;
108 }
109 }
110
111 void
grub_disk_cache_invalidate_all(void)112 grub_disk_cache_invalidate_all (void)
113 {
114 unsigned i;
115
116 for (i = 0; i < GRUB_DISK_CACHE_NUM; i++)
117 {
118 struct grub_disk_cache *cache = grub_disk_cache_table + i;
119
120 if (cache->data && ! cache->lock)
121 {
122 grub_free (cache->data);
123 cache->data = 0;
124 }
125 }
126 }
127
128 static char *
grub_disk_cache_fetch(unsigned long dev_id,unsigned long disk_id,grub_disk_addr_t sector)129 grub_disk_cache_fetch (unsigned long dev_id, unsigned long disk_id,
130 grub_disk_addr_t sector)
131 {
132 struct grub_disk_cache *cache;
133 unsigned index;
134
135 index = grub_disk_cache_get_index (dev_id, disk_id, sector);
136 cache = grub_disk_cache_table + index;
137
138 if (cache->dev_id == dev_id && cache->disk_id == disk_id
139 && cache->sector == sector)
140 {
141 cache->lock = 1;
142 #if 0
143 grub_disk_cache_hits++;
144 #endif
145 return cache->data;
146 }
147
148 #if 0
149 grub_disk_cache_misses++;
150 #endif
151
152 return 0;
153 }
154
155 static void
grub_disk_cache_unlock(unsigned long dev_id,unsigned long disk_id,grub_disk_addr_t sector)156 grub_disk_cache_unlock (unsigned long dev_id, unsigned long disk_id,
157 grub_disk_addr_t sector)
158 {
159 struct grub_disk_cache *cache;
160 unsigned index;
161
162 index = grub_disk_cache_get_index (dev_id, disk_id, sector);
163 cache = grub_disk_cache_table + index;
164
165 if (cache->dev_id == dev_id && cache->disk_id == disk_id
166 && cache->sector == sector)
167 cache->lock = 0;
168 }
169
170 static grub_err_t
grub_disk_cache_store(unsigned long dev_id,unsigned long disk_id,grub_disk_addr_t sector,const char * data)171 grub_disk_cache_store (unsigned long dev_id, unsigned long disk_id,
172 grub_disk_addr_t sector, const char *data)
173 {
174 unsigned index;
175 struct grub_disk_cache *cache;
176
177 index = grub_disk_cache_get_index (dev_id, disk_id, sector);
178 cache = grub_disk_cache_table + index;
179
180 cache->lock = 1;
181 grub_free (cache->data);
182 cache->data = 0;
183 cache->lock = 0;
184
185 cache->data = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
186 if (! cache->data)
187 return grub_errno;
188
189 grub_memcpy (cache->data, data,
190 GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
191 cache->dev_id = dev_id;
192 cache->disk_id = disk_id;
193 cache->sector = sector;
194
195 return GRUB_ERR_NONE;
196 }
197
198
199
200 static grub_disk_dev_t grub_disk_dev_list;
201
202 void
grub_disk_dev_register(grub_disk_dev_t dev)203 grub_disk_dev_register (grub_disk_dev_t dev)
204 {
205 dev->next = grub_disk_dev_list;
206 grub_disk_dev_list = dev;
207 }
208
209 void
grub_disk_dev_unregister(grub_disk_dev_t dev)210 grub_disk_dev_unregister (grub_disk_dev_t dev)
211 {
212 grub_disk_dev_t *p, q;
213
214 for (p = &grub_disk_dev_list, q = *p; q; p = &(q->next), q = q->next)
215 if (q == dev)
216 {
217 *p = q->next;
218 break;
219 }
220 }
221
222 int
grub_disk_dev_iterate(int (* hook)(const char * name,void * closure),void * closure)223 grub_disk_dev_iterate (int (*hook) (const char *name, void *closure),
224 void *closure)
225 {
226 grub_disk_dev_t p;
227
228 for (p = grub_disk_dev_list; p; p = p->next)
229 if (p->iterate && (p->iterate) (hook, closure))
230 return 1;
231
232 return 0;
233 }
234
235 /* Return the location of the first ',', if any, which is not
236 escaped by a '\'. */
237 static const char *
find_part_sep(const char * name)238 find_part_sep (const char *name)
239 {
240 const char *p = name;
241 char c;
242
243 while ((c = *p++) != '\0')
244 {
245 if (c == '\\' && *p == ',')
246 p++;
247 else if (c == ',')
248 return p - 1;
249 }
250 return NULL;
251 }
252
253 grub_disk_t
grub_disk_open(const char * name)254 grub_disk_open (const char *name)
255 {
256 const char *p;
257 grub_disk_t disk;
258 grub_disk_dev_t dev;
259 char *raw = (char *) name;
260 grub_uint64_t current_time;
261
262 grub_dprintf ("disk", "Opening `%s'...\n", name);
263
264 disk = (grub_disk_t) grub_zalloc (sizeof (*disk));
265 if (! disk)
266 return 0;
267
268 disk->name = grub_strdup (name);
269 if (! disk->name)
270 goto fail;
271
272 p = find_part_sep (name);
273 if (p)
274 {
275 grub_size_t len = p - name;
276
277 raw = grub_malloc (len + 1);
278 if (! raw)
279 goto fail;
280
281 grub_memcpy (raw, name, len);
282 raw[len] = '\0';
283 }
284
285 for (dev = grub_disk_dev_list; dev; dev = dev->next)
286 {
287 if ((dev->open) (raw, disk) == GRUB_ERR_NONE)
288 break;
289 else if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
290 grub_errno = GRUB_ERR_NONE;
291 else
292 goto fail;
293 }
294
295 if (! dev)
296 {
297 grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such disk");
298 goto fail;
299 }
300
301 if (p && ! disk->has_partitions)
302 {
303 grub_error (GRUB_ERR_BAD_DEVICE, "no partition on this disk");
304 goto fail;
305 }
306
307 disk->dev = dev;
308
309 if (p)
310 {
311 disk->partition = grub_partition_probe (disk, p + 1);
312 if (! disk->partition)
313 {
314 grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such partition");
315 goto fail;
316 }
317 }
318
319 /* The cache will be invalidated about 2 seconds after a device was
320 closed. */
321 current_time = grub_get_time_ms ();
322
323 if (current_time > (grub_last_time
324 + GRUB_CACHE_TIMEOUT * 1000))
325 grub_disk_cache_invalidate_all ();
326
327 grub_last_time = current_time;
328
329 fail:
330
331 if (raw && raw != name)
332 grub_free (raw);
333
334 if (grub_errno != GRUB_ERR_NONE)
335 {
336 grub_error_push ();
337 grub_dprintf ("disk", "Opening `%s' failed.\n", name);
338 grub_error_pop ();
339
340 grub_disk_close (disk);
341 return 0;
342 }
343
344 return disk;
345 }
346
347 void
grub_disk_close(grub_disk_t disk)348 grub_disk_close (grub_disk_t disk)
349 {
350 grub_partition_t part;
351 grub_dprintf ("disk", "Closing `%s'.\n", disk->name);
352
353 if (disk->dev && disk->dev->close)
354 (disk->dev->close) (disk);
355
356 /* Reset the timer. */
357 grub_last_time = grub_get_time_ms ();
358
359 while (disk->partition)
360 {
361 part = disk->partition->parent;
362 grub_free (disk->partition);
363 disk->partition = part;
364 }
365 grub_free ((void *) disk->name);
366 grub_free (disk);
367 }
368
369 /* This function performs three tasks:
370 - Make sectors disk relative from partition relative.
371 - Normalize offset to be less than the sector size.
372 - Verify that the range is inside the partition. */
373 static grub_err_t
grub_disk_adjust_range(grub_disk_t disk,grub_disk_addr_t * sector,grub_off_t * offset,grub_size_t size)374 grub_disk_adjust_range (grub_disk_t disk, grub_disk_addr_t *sector,
375 grub_off_t *offset, grub_size_t size)
376 {
377 *sector += *offset >> GRUB_DISK_SECTOR_BITS;
378 *offset &= GRUB_DISK_SECTOR_SIZE - 1;
379 /*
380 grub_partition_t part;
381 for (part = disk->partition; part; part = part->parent)
382 {
383 grub_disk_addr_t start;
384 grub_uint64_t len;
385
386 start = part->start;
387 len = part->len;
388
389 if (*sector >= len
390 || len - *sector < ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1)
391 >> GRUB_DISK_SECTOR_BITS))
392 return grub_error (GRUB_ERR_OUT_OF_RANGE, "out of partition");
393
394 *sector += start;
395 }
396
397 if (disk->total_sectors <= *sector
398 || ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1)
399 >> GRUB_DISK_SECTOR_BITS) > disk->total_sectors - *sector)
400 return grub_error (GRUB_ERR_OUT_OF_RANGE, "out of disk");
401 */
402 return GRUB_ERR_NONE;
403
404 }
405
406 /* Read data from the disk. */
407 grub_err_t
grub_disk_read(grub_disk_t disk,grub_disk_addr_t sector,grub_off_t offset,grub_size_t size,void * buf)408 grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
409 grub_off_t offset, grub_size_t size, void *buf)
410 {
411 char *tmp_buf;
412 unsigned real_offset;
413 if ((int)size < 1) {
414 return grub_errno;
415 }
416
417 /* First of all, check if the region is within the disk. */
418 if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE)
419 {
420 grub_error_push ();
421 grub_dprintf ("disk", "Read out of range: sector 0x%llx (%s).\n",
422 (unsigned long long) sector, grub_errmsg);
423 grub_error_pop ();
424 return grub_errno;
425 }
426
427 real_offset = offset;
428
429 /* Allocate a temporary buffer. */
430 tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
431 if (! tmp_buf) {
432 return grub_errno;
433 }
434
435 /* Until SIZE is zero... */
436 while (size)
437 {
438 char *data;
439 grub_disk_addr_t start_sector;
440 grub_size_t len;
441 grub_size_t pos;
442
443 /* For reading bulk data. */
444 start_sector = sector & ~(GRUB_DISK_CACHE_SIZE - 1);
445 pos = (sector - start_sector) << GRUB_DISK_SECTOR_BITS;
446 len = ((GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS)
447 - pos - real_offset);
448 if (len > size)
449 len = size;
450
451 /* Fetch the cache. */
452 data = grub_disk_cache_fetch (disk->dev->id, disk->id, start_sector);
453 if (data)
454 {
455 /* Just copy it! */
456 if (buf) {
457 if (pos + real_offset + len >= size) {
458 // prevent read overflow
459 grub_errno = GRUB_ERR_BAD_FS;
460 goto finish;
461 }
462 grub_memcpy (buf, data + pos + real_offset, len);
463 }
464 grub_disk_cache_unlock (disk->dev->id, disk->id, start_sector);
465 }
466 else
467 {
468 /* Otherwise read data from the disk actually. */
469 if (start_sector + GRUB_DISK_CACHE_SIZE > disk->total_sectors
470 || (disk->dev->read) (disk, start_sector,
471 GRUB_DISK_CACHE_SIZE, tmp_buf)
472 != GRUB_ERR_NONE)
473 {
474 /* Uggh... Failed. Instead, just read necessary data. */
475 unsigned num;
476 char *p;
477
478 grub_errno = GRUB_ERR_NONE;
479
480 num = ((size + real_offset + GRUB_DISK_SECTOR_SIZE - 1)
481 >> GRUB_DISK_SECTOR_BITS);
482
483 p = grub_realloc (tmp_buf, num << GRUB_DISK_SECTOR_BITS);
484 if (!p)
485 goto finish;
486
487 tmp_buf = p;
488
489 if ((disk->dev->read) (disk, sector, num, tmp_buf))
490 {
491 grub_error_push ();
492 grub_dprintf ("disk", "%s read failed\n", disk->name);
493 grub_error_pop ();
494 goto finish;
495 }
496
497 if (buf)
498 grub_memcpy (buf, tmp_buf + real_offset, size);
499
500 /* Call the read hook, if any. */
501 if (disk->read_hook)
502 while (size)
503 {
504 grub_size_t to_read;
505
506 to_read = size;
507 if (real_offset + to_read > GRUB_DISK_SECTOR_SIZE)
508 to_read = GRUB_DISK_SECTOR_SIZE - real_offset;
509 (disk->read_hook) (sector, real_offset,
510 to_read, disk->closure);
511 if (grub_errno != GRUB_ERR_NONE)
512 goto finish;
513
514 sector++;
515 size -= to_read;
516 real_offset = 0;
517 }
518
519 /* This must be the end. */
520 goto finish;
521 }
522
523 /* Copy it and store it in the disk cache. */
524 if (buf)
525 grub_memcpy (buf, tmp_buf + pos + real_offset, len);
526 grub_disk_cache_store (disk->dev->id, disk->id,
527 start_sector, tmp_buf);
528 }
529
530 /* Call the read hook, if any. */
531 if (disk->read_hook)
532 {
533 grub_disk_addr_t s = sector;
534 grub_size_t l = len;
535
536 while (l)
537 {
538 (disk->read_hook) (s, real_offset,
539 ((l > GRUB_DISK_SECTOR_SIZE)
540 ? GRUB_DISK_SECTOR_SIZE
541 : l), disk->closure);
542
543 if (l < GRUB_DISK_SECTOR_SIZE - real_offset)
544 break;
545
546 s++;
547 l -= GRUB_DISK_SECTOR_SIZE - real_offset;
548 real_offset = 0;
549 }
550 }
551
552 sector = start_sector + GRUB_DISK_CACHE_SIZE;
553 if (buf)
554 buf = (char *) buf + len;
555 size -= len;
556 real_offset = 0;
557 }
558
559 finish:
560
561 grub_free (tmp_buf);
562
563 return grub_errno;
564 }
565
566 grub_err_t
grub_disk_read_ex(grub_disk_t disk,grub_disk_addr_t sector,grub_off_t offset,grub_size_t size,void * buf,int flags)567 grub_disk_read_ex (grub_disk_t disk, grub_disk_addr_t sector,
568 grub_off_t offset, grub_size_t size, void *buf, int flags)
569 {
570 unsigned real_offset;
571
572 if (! flags)
573 return grub_disk_read (disk, sector, offset, size, buf);
574
575 if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE)
576 return grub_errno;
577
578 real_offset = offset;
579 while (size)
580 {
581 char tmp_buf[GRUB_DISK_SECTOR_SIZE];
582 grub_size_t len;
583
584 if ((real_offset != 0) || (size < GRUB_DISK_SECTOR_SIZE))
585 {
586 len = GRUB_DISK_SECTOR_SIZE - real_offset;
587 if (len > size)
588 len = size;
589
590 if (buf)
591 {
592 if ((disk->dev->read) (disk, sector, 1, tmp_buf) != GRUB_ERR_NONE)
593 break;
594 grub_memcpy (buf, tmp_buf + real_offset, len);
595 }
596
597 if (disk->read_hook)
598 (disk->read_hook) (sector, real_offset, len, disk->closure);
599
600 sector++;
601 real_offset = 0;
602 }
603 else
604 {
605 grub_size_t n;
606
607 len = size & ~(GRUB_DISK_SECTOR_SIZE - 1);
608 n = size >> GRUB_DISK_SECTOR_BITS;
609
610 if ((buf) &&
611 ((disk->dev->read) (disk, sector, n, buf) != GRUB_ERR_NONE))
612 break;
613
614 if (disk->read_hook)
615 {
616 while (n)
617 {
618 (disk->read_hook) (sector++, 0, GRUB_DISK_SECTOR_SIZE,
619 disk->closure);
620 n--;
621 }
622 }
623 else
624 sector += n;
625 }
626
627 if (buf)
628 buf = (char *) buf + len;
629 size -= len;
630 }
631
632 return grub_errno;
633 }
634
635 grub_err_t
grub_disk_write(grub_disk_t disk,grub_disk_addr_t sector,grub_off_t offset,grub_size_t size,const void * buf)636 grub_disk_write (grub_disk_t disk, grub_disk_addr_t sector,
637 grub_off_t offset, grub_size_t size, const void *buf)
638 {
639 unsigned real_offset;
640
641 grub_dprintf ("disk", "Writing `%s'...\n", disk->name);
642
643 if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE)
644 return grub_errno;
645
646 real_offset = offset;
647
648 while (size)
649 {
650 if (real_offset != 0 || (size < GRUB_DISK_SECTOR_SIZE && size != 0))
651 {
652 char tmp_buf[GRUB_DISK_SECTOR_SIZE];
653 grub_size_t len;
654 grub_partition_t part;
655
656 part = disk->partition;
657 disk->partition = 0;
658 if (grub_disk_read (disk, sector, 0, GRUB_DISK_SECTOR_SIZE, tmp_buf)
659 != GRUB_ERR_NONE)
660 {
661 disk->partition = part;
662 goto finish;
663 }
664 disk->partition = part;
665
666 len = GRUB_DISK_SECTOR_SIZE - real_offset;
667 if (len > size)
668 len = size;
669
670 grub_memcpy (tmp_buf + real_offset, buf, len);
671
672 grub_disk_cache_invalidate (disk->dev->id, disk->id, sector);
673
674 if ((disk->dev->write) (disk, sector, 1, tmp_buf) != GRUB_ERR_NONE)
675 goto finish;
676
677 sector++;
678 buf = (char *) buf + len;
679 size -= len;
680 real_offset = 0;
681 }
682 else
683 {
684 grub_size_t len;
685 grub_size_t n;
686
687 len = size & ~(GRUB_DISK_SECTOR_SIZE - 1);
688 n = size >> GRUB_DISK_SECTOR_BITS;
689
690 if ((disk->dev->write) (disk, sector, n, buf) != GRUB_ERR_NONE)
691 goto finish;
692
693 while (n--)
694 grub_disk_cache_invalidate (disk->dev->id, disk->id, sector++);
695
696 buf = (char *) buf + len;
697 size -= len;
698 }
699 }
700
701 finish:
702
703 return grub_errno;
704 }
705
706 grub_uint64_t
grub_disk_get_size(grub_disk_t disk)707 grub_disk_get_size (grub_disk_t disk)
708 {
709 if (disk->partition)
710 return grub_partition_get_len (disk->partition);
711 else
712 return disk->total_sectors;
713 }
714