~darkmuggle-deactivatedaccount/ubuntu/quantal/grub2/fix-872244

« back to all changes in this revision

Viewing changes to kern/disk.c

  • Committer: Bazaar Package Importer
  • Author(s): Otavio Salvador
  • Date: 2006-01-05 15:20:40 UTC
  • mto: (17.3.1 squeeze) (1.9.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: james.westby@ubuntu.com-20060105152040-b72i5pq1a82z22yi
Tags: upstream-1.92
ImportĀ upstreamĀ versionĀ 1.92

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  GRUB  --  GRand Unified Bootloader
 
3
 *  Copyright (C) 2002,2003,2004  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 2 of the License, or
 
8
 *  (at your option) any later version.
 
9
 *
 
10
 *  This program 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, write to the Free Software
 
17
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
18
 */
 
19
 
 
20
#include <grub/disk.h>
 
21
#include <grub/err.h>
 
22
#include <grub/mm.h>
 
23
#include <grub/types.h>
 
24
#include <grub/partition.h>
 
25
#include <grub/misc.h>
 
26
#include <grub/machine/time.h>
 
27
#include <grub/file.h>
 
28
 
 
29
#define GRUB_CACHE_TIMEOUT      2
 
30
 
 
31
/* The last time the disk was used.  */
 
32
static unsigned long grub_last_time = 0;
 
33
 
 
34
 
 
35
/* Disk cache.  */
 
36
struct grub_disk_cache
 
37
{
 
38
  unsigned long dev_id;
 
39
  unsigned long disk_id;
 
40
  unsigned long sector;
 
41
  char *data;
 
42
  int lock;
 
43
};
 
44
 
 
45
static struct grub_disk_cache grub_disk_cache_table[GRUB_DISK_CACHE_NUM];
 
46
 
 
47
#if 0
 
48
static unsigned long grub_disk_cache_hits;
 
49
static unsigned long grub_disk_cache_misses;
 
50
 
 
51
void
 
52
grub_disk_cache_get_performance (unsigned long *hits, unsigned long *misses)
 
53
{
 
54
  *hits = grub_disk_cache_hits;
 
55
  *misses = grub_disk_cache_misses;
 
56
}
 
57
#endif
 
58
 
 
59
static unsigned
 
60
grub_disk_cache_get_index (unsigned long dev_id, unsigned long disk_id,
 
61
                           unsigned long sector)
 
62
{
 
63
  return ((dev_id * 524287UL + disk_id * 2606459UL
 
64
           + (sector >> GRUB_DISK_CACHE_BITS))
 
65
          % GRUB_DISK_CACHE_NUM);
 
66
}
 
67
 
 
68
static void
 
69
grub_disk_cache_invalidate (unsigned long dev_id, unsigned long disk_id,
 
70
                            unsigned long sector)
 
71
{
 
72
  unsigned index;
 
73
  struct grub_disk_cache *cache;
 
74
 
 
75
  sector &= ~(GRUB_DISK_CACHE_SIZE - 1);
 
76
  index = grub_disk_cache_get_index (dev_id, disk_id, sector);
 
77
  cache = grub_disk_cache_table + index;
 
78
 
 
79
  if (cache->dev_id == dev_id && cache->disk_id == disk_id
 
80
      && cache->sector == sector && cache->data)
 
81
    {
 
82
      cache->lock = 1;
 
83
      grub_free (cache->data);
 
84
      cache->data = 0;
 
85
      cache->lock = 0;
 
86
    }
 
87
}
 
88
 
 
89
void
 
90
grub_disk_cache_invalidate_all (void)
 
91
{
 
92
  unsigned i;
 
93
 
 
94
  for (i = 0; i < GRUB_DISK_CACHE_NUM; i++)
 
95
    {
 
96
      struct grub_disk_cache *cache = grub_disk_cache_table + i;
 
97
 
 
98
      if (cache->data && ! cache->lock)
 
99
        {
 
100
          grub_free (cache->data);
 
101
          cache->data = 0;
 
102
        }
 
103
    }
 
104
}
 
105
 
 
106
static char *
 
107
grub_disk_cache_fetch (unsigned long dev_id, unsigned long disk_id,
 
108
                       unsigned long sector)
 
109
{
 
110
  struct grub_disk_cache *cache;
 
111
  unsigned index;
 
112
 
 
113
  index = grub_disk_cache_get_index (dev_id, disk_id, sector);
 
114
  cache = grub_disk_cache_table + index;
 
115
 
 
116
  if (cache->dev_id == dev_id && cache->disk_id == disk_id
 
117
      && cache->sector == sector)
 
118
    {
 
119
      cache->lock = 1;
 
120
#if 0
 
121
      grub_disk_cache_hits++;
 
122
#endif
 
123
      return cache->data;
 
124
    }
 
125
 
 
126
#if 0
 
127
  grub_disk_cache_misses++;
 
128
#endif
 
129
  
 
130
  return 0;
 
131
}
 
132
 
 
133
static void
 
134
grub_disk_cache_unlock (unsigned long dev_id, unsigned long disk_id,
 
135
                        unsigned long sector)
 
136
{
 
137
  struct grub_disk_cache *cache;
 
138
  unsigned index;
 
139
 
 
140
  index = grub_disk_cache_get_index (dev_id, disk_id, sector);
 
141
  cache = grub_disk_cache_table + index;
 
142
 
 
143
  if (cache->dev_id == dev_id && cache->disk_id == disk_id
 
144
      && cache->sector == sector)
 
145
    cache->lock = 0;
 
146
}
 
147
 
 
148
static grub_err_t
 
149
grub_disk_cache_store (unsigned long dev_id, unsigned long disk_id,
 
150
                       unsigned long sector, const char *data)
 
151
{
 
152
  unsigned index;
 
153
  struct grub_disk_cache *cache;
 
154
  
 
155
  grub_disk_cache_invalidate (dev_id, disk_id, sector);
 
156
  
 
157
  index = grub_disk_cache_get_index (dev_id, disk_id, sector);
 
158
  cache = grub_disk_cache_table + index;
 
159
  
 
160
  cache->data = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
 
161
  if (! cache->data)
 
162
    return grub_errno;
 
163
  
 
164
  grub_memcpy (cache->data, data,
 
165
               GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
 
166
  cache->dev_id = dev_id;
 
167
  cache->disk_id = disk_id;
 
168
  cache->sector = sector;
 
169
 
 
170
  return GRUB_ERR_NONE;
 
171
}
 
172
 
 
173
 
 
174
 
 
175
static grub_disk_dev_t grub_disk_dev_list;
 
176
 
 
177
void
 
178
grub_disk_dev_register (grub_disk_dev_t dev)
 
179
{
 
180
  dev->next = grub_disk_dev_list;
 
181
  grub_disk_dev_list = dev;
 
182
}
 
183
 
 
184
void
 
185
grub_disk_dev_unregister (grub_disk_dev_t dev)
 
186
{
 
187
  grub_disk_dev_t *p, q;
 
188
  
 
189
  for (p = &grub_disk_dev_list, q = *p; q; p = &(q->next), q = q->next)
 
190
    if (q == dev)
 
191
      {
 
192
        *p = q->next;
 
193
        break;
 
194
      }
 
195
}
 
196
 
 
197
int
 
198
grub_disk_dev_iterate (int (*hook) (const char *name))
 
199
{
 
200
  grub_disk_dev_t p;
 
201
 
 
202
  for (p = grub_disk_dev_list; p; p = p->next)
 
203
    if ((p->iterate) (hook))
 
204
      return 1;
 
205
 
 
206
  return 0;
 
207
}
 
208
 
 
209
grub_disk_t
 
210
grub_disk_open (const char *name)
 
211
{
 
212
  char *p;
 
213
  grub_disk_t disk;
 
214
  grub_disk_dev_t dev;
 
215
  char *raw = (char *) name;
 
216
  unsigned long current_time;
 
217
  
 
218
  disk = (grub_disk_t) grub_malloc (sizeof (*disk));
 
219
  if (! disk)
 
220
    return 0;
 
221
 
 
222
  disk->dev = 0;
 
223
  disk->read_hook = 0;
 
224
  disk->partition = 0;
 
225
  disk->data = 0;
 
226
  disk->name = grub_strdup (name);
 
227
  if (! disk->name)
 
228
    goto fail;
 
229
  
 
230
  p = grub_strchr (name, ',');
 
231
  if (p)
 
232
    {
 
233
      grub_size_t len = p - name;
 
234
      
 
235
      raw = grub_malloc (len + 1);
 
236
      if (! raw)
 
237
        goto fail;
 
238
 
 
239
      grub_memcpy (raw, name, len);
 
240
      raw[len] = '\0';
 
241
    }
 
242
 
 
243
  for (dev = grub_disk_dev_list; dev; dev = dev->next)
 
244
    {
 
245
      if ((dev->open) (raw, disk) == GRUB_ERR_NONE)
 
246
        break;
 
247
      else if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
 
248
        grub_errno = GRUB_ERR_NONE;
 
249
      else
 
250
        goto fail;
 
251
    }
 
252
 
 
253
  if (! dev)
 
254
    {
 
255
      grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such disk");
 
256
      goto fail;
 
257
    }
 
258
 
 
259
  if (p && ! disk->has_partitions)
 
260
    {
 
261
      grub_error (GRUB_ERR_BAD_DEVICE, "no partition on this disk");
 
262
      goto fail;
 
263
    }
 
264
  
 
265
  disk->dev = dev;
 
266
 
 
267
  if (p)
 
268
    disk->partition = grub_partition_probe (disk, p + 1);
 
269
 
 
270
  /* The cache will be invalidated about 2 seconds after a device was
 
271
     closed.  */
 
272
  current_time = grub_get_rtc ();
 
273
 
 
274
  if (current_time > (grub_last_time
 
275
                      + GRUB_CACHE_TIMEOUT * GRUB_TICKS_PER_SECOND))
 
276
    grub_disk_cache_invalidate_all ();
 
277
  
 
278
  grub_last_time = current_time;
 
279
  
 
280
 fail:
 
281
  
 
282
  if (raw && raw != name)
 
283
    grub_free (raw);
 
284
 
 
285
  if (grub_errno != GRUB_ERR_NONE)
 
286
    {
 
287
      grub_disk_close (disk);
 
288
      return 0;
 
289
    }
 
290
 
 
291
  return disk;
 
292
}
 
293
 
 
294
void
 
295
grub_disk_close (grub_disk_t disk)
 
296
{
 
297
  if (disk->dev && disk->dev->close)
 
298
    (disk->dev->close) (disk);
 
299
 
 
300
  /* Reset the timer.  */
 
301
  grub_last_time = grub_get_rtc ();
 
302
 
 
303
  grub_free (disk->partition);
 
304
  grub_free ((void *) disk->name);
 
305
  grub_free (disk);
 
306
}
 
307
 
 
308
static grub_err_t
 
309
grub_disk_check_range (grub_disk_t disk, unsigned long *sector,
 
310
                       unsigned long *offset, grub_ssize_t size)
 
311
{
 
312
  *sector += *offset >> GRUB_DISK_SECTOR_BITS;
 
313
  *offset &= GRUB_DISK_SECTOR_SIZE - 1;
 
314
  
 
315
  if (disk->partition)
 
316
    {
 
317
      unsigned long start, len;
 
318
 
 
319
      start = grub_partition_get_start (disk->partition);
 
320
      len = grub_partition_get_len (disk->partition);
 
321
 
 
322
      if (*sector >= len
 
323
          || len - *sector < ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1)
 
324
                              >> GRUB_DISK_SECTOR_BITS))
 
325
        return grub_error (GRUB_ERR_OUT_OF_RANGE, "out of partition");
 
326
 
 
327
      *sector += start;
 
328
    }
 
329
 
 
330
  if (disk->total_sectors <= *sector
 
331
      || ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1)
 
332
          >> GRUB_DISK_SECTOR_BITS) > disk->total_sectors - *sector)
 
333
    return grub_error (GRUB_ERR_OUT_OF_RANGE, "out of disk");
 
334
 
 
335
  return GRUB_ERR_NONE;
 
336
}
 
337
 
 
338
/* Read data from the disk.  */
 
339
grub_err_t
 
340
grub_disk_read (grub_disk_t disk, unsigned long sector,
 
341
                unsigned long offset, unsigned long size, char *buf)
 
342
{
 
343
  char *tmp_buf;
 
344
 
 
345
  /* First of all, check if the region is within the disk.  */
 
346
  if (grub_disk_check_range (disk, &sector, &offset, size) != GRUB_ERR_NONE)
 
347
    return grub_errno;
 
348
 
 
349
  /* Allocate a temporary buffer.  */
 
350
  tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
 
351
  if (! tmp_buf)
 
352
    return grub_errno;
 
353
 
 
354
  /* Until SIZE is zero...  */
 
355
  while (size)
 
356
    {
 
357
      char *data;
 
358
      unsigned long start_sector;
 
359
      unsigned long len;
 
360
      unsigned long pos;
 
361
 
 
362
      /* For reading bulk data.  */
 
363
      start_sector = sector & ~(GRUB_DISK_CACHE_SIZE - 1);
 
364
      pos = (sector - start_sector) << GRUB_DISK_SECTOR_BITS;
 
365
      len = (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS) - pos - offset;
 
366
      if (len > size)
 
367
        len = size;
 
368
 
 
369
      /* Fetch the cache.  */
 
370
      data = grub_disk_cache_fetch (disk->dev->id, disk->id, start_sector);
 
371
      if (data)
 
372
        {
 
373
          /* Just copy it!  */
 
374
          grub_memcpy (buf, data + pos + offset, len);
 
375
          grub_disk_cache_unlock (disk->dev->id, disk->id, start_sector);
 
376
        }
 
377
      else
 
378
        {
 
379
          /* Otherwise read data from the disk actually.  */
 
380
          if ((disk->dev->read) (disk, start_sector,
 
381
                                 GRUB_DISK_CACHE_SIZE, tmp_buf)
 
382
              != GRUB_ERR_NONE)
 
383
            {
 
384
              /* Uggh... Failed. Instead, just read necessary data.  */
 
385
              unsigned num;
 
386
 
 
387
              grub_errno = GRUB_ERR_NONE;
 
388
 
 
389
              /* If more data is required, no way.  */
 
390
              if (pos + size
 
391
                  >= (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS))
 
392
                goto finish;
 
393
 
 
394
              num = ((size + GRUB_DISK_SECTOR_SIZE - 1)
 
395
                     >> GRUB_DISK_SECTOR_BITS);
 
396
              if ((disk->dev->read) (disk, sector, num, tmp_buf))
 
397
                goto finish;
 
398
 
 
399
              grub_memcpy (buf, tmp_buf + offset, size);
 
400
 
 
401
              /* Call the read hook, if any.  */
 
402
              if (disk->read_hook)
 
403
                while (size)
 
404
                  {
 
405
                    (disk->read_hook) (sector, offset,
 
406
                                       ((size > GRUB_DISK_SECTOR_SIZE)
 
407
                                        ? GRUB_DISK_SECTOR_SIZE
 
408
                                        : size));
 
409
                    sector++;
 
410
                    size -= GRUB_DISK_SECTOR_SIZE - offset;
 
411
                    offset = 0;
 
412
                  }
 
413
 
 
414
              /* This must be the end.  */
 
415
              goto finish;
 
416
            }
 
417
 
 
418
          /* Copy it and store it in the disk cache.  */
 
419
          grub_memcpy (buf, tmp_buf + pos + offset, len);
 
420
          grub_disk_cache_store (disk->dev->id, disk->id,
 
421
                                 start_sector, tmp_buf);
 
422
        }
 
423
 
 
424
      /* Call the read hook, if any.  */
 
425
      if (disk->read_hook)
 
426
        {
 
427
          unsigned long s = sector;
 
428
          unsigned long l = len;
 
429
          
 
430
          while (l)
 
431
            {
 
432
              (disk->read_hook) (s, offset,
 
433
                                 ((l > GRUB_DISK_SECTOR_SIZE)
 
434
                                  ? GRUB_DISK_SECTOR_SIZE
 
435
                                  : l));
 
436
              
 
437
              if (l < GRUB_DISK_SECTOR_SIZE - offset)
 
438
                break;
 
439
              
 
440
              s++;
 
441
              l -= GRUB_DISK_SECTOR_SIZE - offset;
 
442
              offset = 0;
 
443
            }
 
444
        }
 
445
      
 
446
      sector = start_sector + GRUB_DISK_CACHE_SIZE;
 
447
      buf += len;
 
448
      size -= len;
 
449
      offset = 0;
 
450
    }
 
451
  
 
452
 finish:
 
453
  
 
454
  grub_free (tmp_buf);
 
455
  
 
456
  return grub_errno;
 
457
}
 
458
 
 
459
grub_err_t
 
460
grub_disk_write (grub_disk_t disk, unsigned long sector,
 
461
                 unsigned long offset, unsigned long size, const char *buf)
 
462
{
 
463
  if (grub_disk_check_range (disk, &sector, &offset, size) != GRUB_ERR_NONE)
 
464
    return -1;
 
465
 
 
466
  while (size)
 
467
    {
 
468
      if (offset != 0 || (size < GRUB_DISK_SECTOR_SIZE && size != 0))
 
469
        {
 
470
          char tmp_buf[GRUB_DISK_SECTOR_SIZE];
 
471
          unsigned long len;
 
472
          
 
473
          if (grub_disk_read (disk, sector, 0, GRUB_DISK_SECTOR_SIZE, tmp_buf)
 
474
              != GRUB_ERR_NONE)
 
475
            goto finish;
 
476
 
 
477
          len = GRUB_DISK_SECTOR_SIZE - offset;
 
478
          if (len > size)
 
479
            len = size;
 
480
          
 
481
          grub_memcpy (tmp_buf + offset, buf, len);
 
482
 
 
483
          grub_disk_cache_invalidate (disk->dev->id, disk->id, sector);
 
484
 
 
485
          if ((disk->dev->write) (disk, sector, 1, tmp_buf) != GRUB_ERR_NONE)
 
486
            goto finish;
 
487
 
 
488
          sector++;
 
489
          buf += len;
 
490
          size -= len;
 
491
          offset = 0;
 
492
        }
 
493
      else
 
494
        {
 
495
          unsigned long len;
 
496
          unsigned long n;
 
497
 
 
498
          len = size & ~(GRUB_DISK_SECTOR_SIZE - 1);
 
499
          n = size >> GRUB_DISK_SECTOR_BITS;
 
500
          
 
501
          if ((disk->dev->write) (disk, sector, n, buf) != GRUB_ERR_NONE)
 
502
            goto finish;
 
503
 
 
504
          while (n--)
 
505
            grub_disk_cache_invalidate (disk->dev->id, disk->id, sector++);
 
506
 
 
507
          buf += len;
 
508
          size -= len;
 
509
        }
 
510
    }
 
511
 
 
512
 finish:
 
513
 
 
514
  return grub_errno;
 
515
}