a52b664e8900c6e789764c696f5d4e61ab9fab0d
[yaboot.git] / second / file.c
1 /*
2  *  file.c - Filesystem related interfaces
3  *
4  *  Copyright (C) 2001, 2002 Ethan Benson
5  *
6  *  parse_device_path()
7  *
8  *  Copyright (C) 2001 Colin Walters
9  *
10  *  Copyright (C) 1999 Benjamin Herrenschmidt
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25  */
26
27 #include "ctype.h"
28 #include "types.h"
29 #include "stddef.h"
30 #include "stdlib.h"
31 #include "file.h"
32 #include "prom.h"
33 #include "string.h"
34 #include "partition.h"
35 #include "fs.h"
36 #include "errors.h"
37 #include "debug.h"
38
39 extern char bootdevice[];
40
41 /* Convert __u32 into std, dotted quad string, leaks like a sive :( */
42 static char *
43 ipv4_to_str(__u32 ip)
44 {
45      char *buf = malloc(sizeof("000.000.000.000"));
46
47      sprintf(buf,"%u.%u.%u.%u",
48              (ip & 0xff000000) >> 24, (ip & 0x00ff0000) >> 16,
49              (ip & 0x0000ff00) >>  8, (ip & 0x000000ff));
50
51      return buf;
52 }
53
54 /* Ensure the string arg is a plausible IPv4 address */
55 static char * is_valid_ipv4_str(char *str)
56 {
57      int i;
58      long tmp;
59      __u32 ip = 0;
60      char *ptr=str, *endptr;
61
62      if (str == NULL)
63           return NULL;
64
65      for (i=0; i<4; i++, ptr = ++endptr) {
66           tmp = strtol(ptr, &endptr, 10);
67           if ((tmp & 0xff) != tmp)
68                return NULL;
69
70           /* If we reach the end of the string but we're not in the 4th octet
71            * we have an invalid IP */
72           if (*endptr == '\x0' && i!=3)
73                return NULL;
74
75           /* If we have anything other than a NULL or '.' we have an invlaid
76            * IP */
77           if (*endptr != '\x0' && *endptr != '.')
78                return NULL;
79
80           ip += (tmp << (24-(i*8)));
81      }
82
83      if (ip == 0 || ip == ~0u)
84           return NULL;
85
86      return str;
87 }
88
89
90 /*
91  * Copy the string from source to dest till newline or comma(,) is seen
92  * in the source.
93  * Move source and dest pointers respectively.
94  * Returns pointer to the start of the string that has just been copied.
95  */
96 static char *
97 scopy(char **dest, char **source)
98 {
99      char *ret = *dest;
100
101      if (!**source)
102           return NULL;
103
104      while (**source != ',' && **source != '\0')
105           *(*dest)++ = *(*source)++;
106      if (**source != '\0')
107           *(*source)++;
108      **dest = '\0';
109      *(*dest)++;
110      return ret;
111 }
112
113 /*
114  * Extract all the ipv4 arguments from the bootpath provided and fill result
115  * Returns 1 on success, 0 on failure.
116  */
117 static int
118 extract_ipv4_args(char *imagepath, struct boot_fspec_t *result)
119 {
120      char *tmp, *args, *str, *start;
121
122      args = strrchr(imagepath, ':');
123      if (!args)
124           return 1;
125
126      start = args; /* used to see if we read any optional parameters */
127
128      /* The obp-tftp device arguments should be at the end of
129       * the argument list.  Skip over any extra arguments (promiscuous,
130       * speed, duplex, bootp, rarp).
131       */
132
133      tmp = strstr(args, "promiscuous");
134      if (tmp && tmp > args)
135           args = tmp + strlen("promiscuous");
136
137      tmp = strstr(args, "speed=");
138      if (tmp && tmp > args)
139           args = tmp + strlen("speed=");
140
141      tmp = strstr(args, "duplex=");
142      if (tmp && tmp > args)
143           args = tmp + strlen("duplex=");
144
145      tmp = strstr(args, "bootp");
146      if (tmp && tmp > args)
147           args = tmp + strlen("bootp");
148
149      tmp = strstr(args, "rarp");
150      if (tmp && tmp > args)
151           args = tmp + strlen("rarp");
152
153      if (args != start) /* we read some parameters, so go past the next comma(,) */
154           args = strchr(args, ',');
155      if (!args)
156           return 1;
157
158      str = malloc(strlen(args) + 1); /*long enough to hold all strings */
159      if (!str)
160           return 0;
161
162      if (args[-1] != ':')
163           args++; /* If comma(,) is not immediately followed by ':' then go past the , */
164
165      /*
166       * read the arguments in order: siaddr,filename,ciaddr,giaddr,
167       * bootp-retries,tftp-retries,addl_prameters
168       */
169      result->siaddr = is_valid_ipv4_str(scopy(&str, &args));
170      result->file = scopy(&str, &args);
171      result->ciaddr = is_valid_ipv4_str(scopy(&str, &args));
172      result->giaddr = is_valid_ipv4_str(scopy(&str, &args));
173      result->bootp_retries = scopy(&str, &args);
174      result->tftp_retries = scopy(&str, &args);
175      result->subnetmask = is_valid_ipv4_str(scopy(&str, &args));
176      if (*args) {
177           result->addl_params = strdup(args);
178           if (!result->addl_params)
179                 return 0;
180      }
181      return 1;
182 }
183
184 /* DHCP options */
185 enum dhcp_options {
186      DHCP_PAD = 0,
187      DHCP_NETMASK = 1,
188      DHCP_ROUTERS = 3,
189      DHCP_END = 255,
190 };
191
192 #define DHCP_COOKIE        0x63825363
193 #define DHCP_COOKIE_SIZE   4
194
195 /*
196  * Process the bootp reply packet's vendor extensions.
197  * Vendor extensions are detailed in: http://www.faqs.org/rfcs/rfc1084.html
198  */
199 static void
200 extract_vendor_options(struct bootp_packet *packet, struct boot_fspec_t *result)
201 {
202      int i = 0;
203      __u32 cookie;
204      __u8 *options = &packet->options[0];
205
206      memcpy(&cookie, &options[i], DHCP_COOKIE_SIZE);
207
208      if (cookie != DHCP_COOKIE) {
209           prom_printf("EEEK! cookie is fubar got %08x expected %08x\n",
210                       cookie, DHCP_COOKIE);
211           return;
212      }
213
214      i += DHCP_COOKIE_SIZE;
215
216      /* FIXME: It may be possible to run off the end of a packet here /if/
217       *         it's malformed. :( */
218      while (options[i] != DHCP_END) {
219           __u8 tag = options[i++], len;
220           __u32 value = 0;
221
222           if (tag == DHCP_PAD)
223                continue;
224
225           len = options[i++];
226           /* Clamp the maxium length of the memcpy() to the right size for
227            * value. */
228           if (len > sizeof(value))
229                memcpy(&value, &options[i], sizeof(value));
230           else
231                memcpy(&value, &options[i], len);
232
233 #if DEBUG
234 {
235      DEBUG_F("tag=%2d, len=%2d, data=", tag, len);
236      int j;
237      for (j=0; j<len; j++)
238           prom_printf("%02x", options[i+j]);
239      prom_printf("\n");
240 }
241 #endif
242
243           switch (tag) {
244                case DHCP_NETMASK:
245                     if ((result->subnetmask == NULL ||
246                          *(result->subnetmask) == '\x0') && value != 0) {
247                          result->subnetmask = ipv4_to_str(value);
248                          DEBUG_F("Storing %s as subnetmask from options\n",
249                                  result->subnetmask);
250                     }
251                     break;
252                case DHCP_ROUTERS:
253                     if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
254                         && value != 0) {
255                          result->giaddr = ipv4_to_str(value);
256                          DEBUG_F("Storing %s as gateway from options\n",
257                                  result->giaddr);
258                     }
259                     break;
260                }
261           i += len;
262      }
263 }
264
265 /*
266  * Check netinfo for ipv4 parameters and add them to the fspec iff the
267  * fspec has no existing value.
268  */
269 static void
270 extract_netinfo_args(struct boot_fspec_t *result)
271 {
272      struct bootp_packet *packet;
273
274      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
275      packet = prom_get_netinfo();
276      if (!packet)
277           return;
278
279      DEBUG_F("We have a boot packet\n");
280      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
281      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
282      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
283      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
284
285      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
286      if (packet->ciaddr == 0 && packet->yiaddr != 0)
287           packet->ciaddr = packet->yiaddr;
288
289      if ((result->siaddr == NULL || *(result->siaddr) == '\x0')
290          && packet->siaddr != 0)
291           result->siaddr = ipv4_to_str(packet->siaddr);
292      if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0')
293          && packet->ciaddr != 0)
294           result->ciaddr = ipv4_to_str(packet->ciaddr);
295      if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
296          && packet->giaddr != 0)
297           result->giaddr = ipv4_to_str(packet->giaddr);
298
299      extract_vendor_options(packet, result);
300
301      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
302       *        the server.  This will be okay if the client and server are on
303       *        the same IP network, if not then lets hope the server does ICMP
304       *        redirections */
305      if (result->giaddr == NULL) {
306           result->giaddr = ipv4_to_str(packet->siaddr);
307           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
308      }
309 }
310
311 /*
312  * Extract all the ipv6 arguments from the bootpath provided and fill result
313  * Syntax: ipv6,[dhcpv6[=diaddr,]]ciaddr=c_iaddr,giaddr=g_iaddr,siaddr=s_iaddr,
314  *      filename=file_name,tftp-retries=tftp_retries,blksize=block_size
315  * Returns 1 on success, 0 on failure.
316  */
317 static int
318 extract_ipv6_args(char *imagepath, struct boot_fspec_t *result)
319 {
320      char *str, *tmp;
321      int total_len;
322
323      result->is_ipv6 = 1;
324
325      /* Just allocate the max required size */
326      total_len = strlen(imagepath) + 1;
327      str = malloc(total_len);
328      if (!str)
329         return 0;
330
331      if ((tmp = strstr(imagepath, "dhcpv6=")) != NULL)
332         result->dhcpv6 = scopy(&str, &tmp);
333
334      if ((tmp = strstr(imagepath, "ciaddr=")) != NULL)
335         result->ciaddr = scopy(&str, &tmp);
336
337      if ((tmp = strstr(imagepath, "giaddr=")) != NULL)
338         result->giaddr = scopy(&str, &tmp);
339
340      if ((tmp = strstr(imagepath, "siaddr=")) != NULL)
341         result->siaddr = scopy(&str, &tmp);
342
343      if ((tmp = strstr(imagepath, "filename=")) != NULL)
344         result->file = scopy(&str, &tmp);
345
346      if ((tmp = strstr(imagepath, "tftp-retries=")) != NULL)
347         result->tftp_retries = scopy(&str, &tmp);
348
349      if ((tmp = strstr(imagepath, "blksize=")) != NULL)
350         result->blksize = scopy(&str, &tmp);
351
352      return 1;
353 }
354
355 /*
356  * Extract all the arguments provided in the imagepath and fill it in result.
357  * Returns 1 on success, 0 on failure.
358  */
359 static int
360 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
361 {
362      int ret;
363
364      DEBUG_F("imagepath = %s\n", imagepath);
365
366      if (!imagepath)
367           return 1;
368
369      if (strstr(imagepath, TOK_IPV6))
370           ret = extract_ipv6_args(imagepath, result);
371      else
372           ret = extract_ipv4_args(imagepath, result);
373      extract_netinfo_args(result);
374
375      DEBUG_F("ipv6 = <%d>\n", result->is_ipv6);
376      DEBUG_F("siaddr = <%s>\n", result->siaddr);
377      DEBUG_F("file = <%s>\n", result->file);
378      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
379      DEBUG_F("giaddr = <%s>\n", result->giaddr);
380      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
381      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
382      DEBUG_F("addl_params = <%s>\n", result->addl_params);
383      DEBUG_F("dhcpv6 = <%s>\n", result->dhcpv6);
384      DEBUG_F("blksize = <%s>\n", result->blksize);
385
386      return ret;
387 }
388
389 static char *netdev_path_to_dev(const char *path)
390 {
391      char *dev, *tmp;
392      size_t len;
393
394      DEBUG_F("path = %s\n", path);
395
396      if (!path)
397           return NULL;
398
399      tmp = strchr(path, ':');
400      if (!tmp)
401           return strdup(path);
402      tmp++;
403
404      len = tmp - path + 1;
405
406      dev = malloc(len);
407      if (dev) {
408           strncpy(dev, path, len);
409           dev[len - 1] = '\0';
410      }
411      return dev;
412 }
413
414 /* This function follows the device path in the devtree and separates
415    the device name, partition number, and other datas (mostly file name)
416    the string passed in parameters is changed since 0 are put in place
417    of some separators to terminate the various strings.
418
419    when a default device is supplied imagepath will be assumed to be a
420    plain filename unless it contains a : otherwise if defaultdev is
421    NULL imagepath will be assumed to be a device path.
422
423    returns 1 on success 0 on failure.
424
425    Supported examples:
426     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
427     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
428     - hd:3,/boot/vmlinux
429     - enet:10.0.0.1,/tftpboot/vmlinux
430     - enet:,/tftpboot/vmlinux
431     - enet:bootp
432     - enet:0
433     - arguments for obp-tftp open as specified in section 4.1 of
434       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
435       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
436       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
437    Supported only if defdevice == NULL
438     - disc
439     - any other device path lacking a :
440    Unsupported examples:
441     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
442     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
443
444 int
445 parse_device_path(char *imagepath, char *defdevice, int defpart,
446                   char *deffile, struct boot_fspec_t *result)
447 {
448      char *ptr;
449      char *ipath = NULL;
450      char *defdev = NULL;
451      int device_kind = -1;
452
453      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
454                 imagepath, defdevice, defpart, deffile);
455
456      result->dev = NULL;
457      result->part = -1;
458      result->file = NULL;
459
460      if (!imagepath)
461           return 0;
462
463       /*
464        * Do preliminary checking for an iscsi device; it may appear as
465        * pure a network device (device_type == "network") if this is
466        * ISWI.  This is the case on IBM systems doing an iscsi OFW
467        * boot.
468        */
469      if (strstr(imagepath, TOK_ISCSI)) {
470           /*
471            * get the virtual device information from the
472            * "nas-bootdevice" property.
473            */
474           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
475                DEBUG_F("reset boot-device to"
476                        " /chosen/nas-bootdevice = %s\n", bootdevice);
477                device_kind = FILE_DEVICE_ISCSI;
478                ipath = strdup(bootdevice);
479                if (!ipath)
480                     return 0;
481           }
482           else
483                return 0;
484      }
485      else if (!(ipath = strdup(imagepath)))
486           return 0;
487
488      if (defdevice) {
489           defdev = strdup(defdevice);
490           device_kind = prom_get_devtype(defdev);
491      } else if (device_kind == -1)
492           device_kind = prom_get_devtype(ipath);
493
494      /*
495       * When an iscsi iqn is present, it may have embedded colons, so
496       * don't parse off anything.
497       */
498      if (device_kind != FILE_DEVICE_NET &&
499          device_kind != FILE_DEVICE_ISCSI &&
500          strchr(defdev, ':') != NULL) {
501            if ((ptr = strrchr(defdev, ':')) != NULL)
502                 *ptr = 0; /* remove trailing : from defdevice if necessary */
503      }
504
505      /* This will not properly handle an obp-tftp argument list
506       * with elements after the filename; that is handled below.
507       */
508      if (device_kind != FILE_DEVICE_NET &&
509          device_kind != FILE_DEVICE_ISCSI &&
510          strchr(ipath, ':') != NULL) {
511           if ((ptr = strrchr(ipath, ',')) != NULL) {
512                char *colon = strrchr(ipath, ':');
513                /* If a ':' occurs *after* a ',', then we assume that there is
514                   no filename */
515                if (!colon || colon < ptr) {
516                     result->file = strdup(ptr+1);
517                     /* Trim the filename off */
518                     *ptr = 0;
519                }
520           }
521      }
522
523      if (device_kind == FILE_DEVICE_NET) {
524           if (strchr(ipath, ':')) {
525                if (extract_netboot_args(ipath, result) == 0)
526                    return 0;
527           } else {
528                /* If we didn't get a ':' then look only in netinfo */
529                extract_netinfo_args(result);
530                result->file = strdup(ipath);
531           }
532
533           if (!defdev)
534                result->dev = netdev_path_to_dev(ipath);
535      } else if (device_kind != FILE_DEVICE_ISCSI &&
536                 (ptr = strrchr(ipath, ':')) != NULL) {
537           *ptr = 0;
538           result->dev = strdup(ipath);
539           if (*(ptr+1))
540                result->part = simple_strtol(ptr+1, NULL, 10);
541      } else if (!defdev) {
542           result->dev = strdup(ipath);
543      } else if (strlen(ipath)) {
544           result->file = strdup(ipath);
545      } else {
546           free(defdev);
547           return 0;
548      }
549
550      if (!result->dev && defdev)
551           result->dev = strdup(defdev);
552
553      if (result->part < 0)
554           result->part = defpart;
555
556      if (!result->file)
557           result->file = strdup(deffile);
558
559      free(ipath);
560      if (defdev)
561           free(defdev);
562      return 1;
563 }
564
565
566 static int
567 file_block_open(        struct boot_file_t*     file,
568                         struct boot_fspec_t*    fspec,
569                         int                     partition)
570 {
571      struct partition_t*        parts;
572      struct partition_t*        p;
573      struct partition_t*        found;
574
575      parts = partitions_lookup(fspec->dev);
576      found = NULL;
577
578 #if DEBUG
579      if (parts)
580           prom_printf("partitions:\n");
581      else
582           prom_printf("no partitions found.\n");
583 #endif
584      for (p = parts; p && !found; p=p->next) {
585           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
586                   p->part_number, p->part_start, p->part_size );
587           if (partition == -1) {
588                file->fs = fs_open( file, p, fspec );
589                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
590                     continue;
591                else {
592                     partition = p->part_number;
593                     goto done;
594                }
595           }
596           if ((partition >= 0) && (partition == p->part_number))
597                found = p;
598 #if DEBUG
599           if (found)
600                prom_printf(" (match)\n");
601 #endif
602      }
603
604      /* Note: we don't skip when found is NULL since we can, in some
605       * cases, let OF figure out a default partition.
606       */
607      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
608      file->fs = fs_open( file, found, fspec );
609
610 done:
611      if (parts)
612           partitions_free(parts);
613
614      return fserrorno;
615 }
616
617 static int
618 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
619 {
620      file->fs = fs_of_netboot;
621      return fs_of_netboot->open(file, NULL, fspec);
622 }
623
624 static int
625 default_read(   struct boot_file_t*     file,
626                 unsigned int            size,
627                 void*                   buffer)
628 {
629      prom_printf("WARNING ! default_read called !\n");
630      return FILE_ERR_EOF;
631 }
632
633 static int
634 default_seek(   struct boot_file_t*     file,
635                 unsigned int            newpos)
636 {
637      prom_printf("WARNING ! default_seek called !\n");
638      return FILE_ERR_EOF;
639 }
640
641 static int
642 default_close(  struct boot_file_t*     file)
643 {
644      prom_printf("WARNING ! default_close called !\n");
645      return FILE_ERR_OK;
646 }
647
648 static struct fs_t fs_default =
649 {
650      "defaults",
651      NULL,
652      default_read,
653      default_seek,
654      default_close
655 };
656
657
658 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
659 {
660      int result;
661
662      memset(file, 0, sizeof(struct boot_file_t*));
663      file->fs        = &fs_default;
664
665      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
666              spec->dev, spec->file, spec->part);
667
668      result = prom_get_devtype(spec->dev);
669      if (result > 0)
670           file->device_kind = result;
671      else
672           return result;
673
674      switch(file->device_kind) {
675      case FILE_DEVICE_BLOCK:
676           DEBUG_F("device is a block device\n");
677           return file_block_open(file, spec, spec->part);
678      case FILE_DEVICE_ISCSI:
679           DEBUG_F("device is a iSCSI device\n");
680           return file_block_open(file, spec, spec->part);
681      case FILE_DEVICE_NET:
682           DEBUG_F("device is a network device\n");
683           return file_net_open(file, spec);
684      }
685      return 0;
686 }
687
688 /*
689  * Local variables:
690  * c-file-style: "k&r"
691  * c-basic-offset: 5
692  * End:
693  */