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, &sector, &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, &sector, &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, &sector, &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