Support for VLAN tags
[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 until the end of string 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           (void)*(*source)++;
108      **dest = '\0';
109      (void)*(*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: vtag,siaddr,filename,ciaddr,giaddr,
167       * bootp-retries,tftp-retries,addl_prameters
168       */
169      if ((tmp = strstr(imagepath, "vtag=")) != NULL) {
170         result->vtag = scopy(&str, &tmp);
171         args = tmp;
172      }
173
174      result->siaddr = is_valid_ipv4_str(scopy(&str, &args));
175      result->file = scopy(&str, &args);
176      result->ciaddr = is_valid_ipv4_str(scopy(&str, &args));
177      result->giaddr = is_valid_ipv4_str(scopy(&str, &args));
178      result->bootp_retries = scopy(&str, &args);
179      result->tftp_retries = scopy(&str, &args);
180      result->subnetmask = is_valid_ipv4_str(scopy(&str, &args));
181      if (*args) {
182           result->addl_params = strdup(args);
183           if (!result->addl_params)
184                 return 0;
185      }
186      return 1;
187 }
188
189 /* DHCP options */
190 enum dhcp_options {
191      DHCP_PAD = 0,
192      DHCP_NETMASK = 1,
193      DHCP_ROUTERS = 3,
194      DHCP_END = 255,
195 };
196
197 #define DHCP_COOKIE        0x63825363
198 #define DHCP_COOKIE_SIZE   4
199
200 /*
201  * Process the bootp reply packet's vendor extensions.
202  * Vendor extensions are detailed in: http://www.faqs.org/rfcs/rfc1084.html
203  */
204 static void
205 extract_vendor_options(struct bootp_packet *packet, struct boot_fspec_t *result)
206 {
207      int i = 0;
208      __u32 cookie;
209      __u8 *options = &packet->options[0];
210
211      memcpy(&cookie, &options[i], DHCP_COOKIE_SIZE);
212
213      if (cookie != DHCP_COOKIE) {
214           prom_printf("EEEK! cookie is fubar got %08x expected %08x\n",
215                       cookie, DHCP_COOKIE);
216           return;
217      }
218
219      i += DHCP_COOKIE_SIZE;
220
221      /* FIXME: It may be possible to run off the end of a packet here /if/
222       *         it's malformed. :( */
223      while (options[i] != DHCP_END) {
224           __u8 tag = options[i++], len;
225           __u32 value = 0;
226
227           if (tag == DHCP_PAD)
228                continue;
229
230           len = options[i++];
231           /* Clamp the maxium length of the memcpy() to the right size for
232            * value. */
233           if (len > sizeof(value))
234                memcpy(&value, &options[i], sizeof(value));
235           else
236                memcpy(&value, &options[i], len);
237
238 #if DEBUG
239 {
240      DEBUG_F("tag=%2d, len=%2d, data=", tag, len);
241      int j;
242      for (j=0; j<len; j++)
243           prom_printf("%02x", options[i+j]);
244      prom_printf("\n");
245 }
246 #endif
247
248           switch (tag) {
249                case DHCP_NETMASK:
250                     if ((result->subnetmask == NULL ||
251                          *(result->subnetmask) == '\x0') && value != 0) {
252                          result->subnetmask = ipv4_to_str(value);
253                          DEBUG_F("Storing %s as subnetmask from options\n",
254                                  result->subnetmask);
255                     }
256                     break;
257                case DHCP_ROUTERS:
258                     if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
259                         && value != 0) {
260                          result->giaddr = ipv4_to_str(value);
261                          DEBUG_F("Storing %s as gateway from options\n",
262                                  result->giaddr);
263                     }
264                     break;
265                }
266           i += len;
267      }
268 }
269
270 /*
271  * Check netinfo for ipv4 parameters and add them to the fspec iff the
272  * fspec has no existing value.
273  */
274 static void
275 extract_netinfo_args(struct boot_fspec_t *result)
276 {
277      struct bootp_packet *packet;
278
279      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
280      packet = prom_get_netinfo();
281      if (!packet)
282           return;
283
284      DEBUG_F("We have a boot packet\n");
285      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
286      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
287      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
288      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
289
290      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
291      if (packet->ciaddr == 0 && packet->yiaddr != 0)
292           packet->ciaddr = packet->yiaddr;
293
294      if ((result->siaddr == NULL || *(result->siaddr) == '\x0')
295          && packet->siaddr != 0)
296           result->siaddr = ipv4_to_str(packet->siaddr);
297      if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0')
298          && packet->ciaddr != 0)
299           result->ciaddr = ipv4_to_str(packet->ciaddr);
300      if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
301          && packet->giaddr != 0)
302           result->giaddr = ipv4_to_str(packet->giaddr);
303
304      extract_vendor_options(packet, result);
305
306      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
307       *        the server.  This will be okay if the client and server are on
308       *        the same IP network, if not then lets hope the server does ICMP
309       *        redirections */
310      if (result->giaddr == NULL) {
311           result->giaddr = ipv4_to_str(packet->siaddr);
312           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
313      }
314 }
315
316 /*
317  * Extract all the ipv6 arguments from the bootpath provided and fill result
318  * Syntax: ipv6,[dhcpv6[=diaddr,]]ciaddr=c_iaddr,giaddr=g_iaddr,siaddr=s_iaddr,
319  *      filename=file_name,tftp-retries=tftp_retries,blksize=block_size
320  * Returns 1 on success, 0 on failure.
321  */
322 static int
323 extract_ipv6_args(char *imagepath, struct boot_fspec_t *result)
324 {
325      char *str, *tmp;
326      int total_len;
327
328      result->is_ipv6 = 1;
329
330      /* Just allocate the max required size */
331      total_len = strlen(imagepath) + 1;
332      str = malloc(total_len);
333      if (!str)
334         return 0;
335
336      if ((tmp = strstr(imagepath, "dhcpv6=")) != NULL)
337         result->dhcpv6 = scopy(&str, &tmp);
338
339      if ((tmp = strstr(imagepath, "vtag=")) != NULL)
340         result->vtag = scopy(&str, &tmp);
341
342      if ((tmp = strstr(imagepath, "ciaddr=")) != NULL)
343         result->ciaddr = scopy(&str, &tmp);
344
345      if ((tmp = strstr(imagepath, "giaddr=")) != NULL)
346         result->giaddr = scopy(&str, &tmp);
347
348      if ((tmp = strstr(imagepath, "siaddr=")) != NULL)
349         result->siaddr = scopy(&str, &tmp);
350
351      if ((tmp = strstr(imagepath, "filename=")) != NULL)
352         result->file = scopy(&str, &tmp);
353
354      if ((tmp = strstr(imagepath, "tftp-retries=")) != NULL)
355         result->tftp_retries = scopy(&str, &tmp);
356
357      if ((tmp = strstr(imagepath, "blksize=")) != NULL)
358         result->blksize = scopy(&str, &tmp);
359
360      return 1;
361 }
362
363 /*
364  * Extract all the arguments provided in the imagepath and fill it in result.
365  * Returns 1 on success, 0 on failure.
366  */
367 static int
368 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
369 {
370      int ret;
371
372      DEBUG_F("imagepath = %s\n", imagepath);
373
374      if (!imagepath)
375           return 1;
376
377      if (strstr(imagepath, TOK_IPV6))
378           ret = extract_ipv6_args(imagepath, result);
379      else
380           ret = extract_ipv4_args(imagepath, result);
381      extract_netinfo_args(result);
382
383      DEBUG_F("ipv6 = <%d>\n", result->is_ipv6);
384      DEBUG_F("siaddr = <%s>\n", result->siaddr);
385      DEBUG_F("file = <%s>\n", result->file);
386      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
387      DEBUG_F("giaddr = <%s>\n", result->giaddr);
388      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
389      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
390      DEBUG_F("addl_params = <%s>\n", result->addl_params);
391      DEBUG_F("dhcpv6 = <%s>\n", result->dhcpv6);
392      DEBUG_F("blksize = <%s>\n", result->blksize);
393
394      return ret;
395 }
396
397 static char *netdev_path_to_dev(const char *path)
398 {
399      char *dev, *tmp;
400      size_t len;
401
402      DEBUG_F("path = %s\n", path);
403
404      if (!path)
405           return NULL;
406
407      tmp = strchr(path, ':');
408      if (!tmp)
409           return strdup(path);
410      tmp++;
411
412      len = tmp - path + 1;
413
414      dev = malloc(len);
415      if (dev) {
416           strncpy(dev, path, len);
417           dev[len - 1] = '\0';
418      }
419      return dev;
420 }
421
422 /* This function follows the device path in the devtree and separates
423    the device name, partition number, and other datas (mostly file name)
424    the string passed in parameters is changed since 0 are put in place
425    of some separators to terminate the various strings.
426
427    when a default device is supplied imagepath will be assumed to be a
428    plain filename unless it contains a : otherwise if defaultdev is
429    NULL imagepath will be assumed to be a device path.
430
431    returns 1 on success 0 on failure.
432
433    Supported examples:
434     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
435     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
436     - hd:3,/boot/vmlinux
437     - enet:10.0.0.1,/tftpboot/vmlinux
438     - enet:,/tftpboot/vmlinux
439     - enet:bootp
440     - enet:0
441     - arguments for obp-tftp open as specified in section 4.1 of
442       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
443       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
444       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
445    Supported only if defdevice == NULL
446     - disc
447     - any other device path lacking a :
448    Unsupported examples:
449     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
450     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
451
452 int
453 parse_device_path(char *imagepath, char *defdevice, int defpart,
454                   char *deffile, struct boot_fspec_t *result)
455 {
456      char *ptr;
457      char *ipath = NULL;
458      char *defdev = NULL;
459      int device_kind = -1;
460
461      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
462                 imagepath, defdevice, defpart, deffile);
463
464      result->dev = NULL;
465      result->part = -1;
466      result->file = NULL;
467
468      if (!imagepath)
469           return 0;
470
471       /*
472        * Do preliminary checking for an iscsi device; it may appear as
473        * pure a network device (device_type == "network") if this is
474        * ISWI.  This is the case on IBM systems doing an iscsi OFW
475        * boot.
476        */
477      if (strstr(imagepath, TOK_ISCSI)) {
478           /*
479            * get the virtual device information from the
480            * "nas-bootdevice" property.
481            */
482           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
483                DEBUG_F("reset boot-device to"
484                        " /chosen/nas-bootdevice = %s\n", bootdevice);
485                device_kind = FILE_DEVICE_ISCSI;
486                ipath = strdup(bootdevice);
487                if (!ipath)
488                     return 0;
489           }
490           else
491                return 0;
492      }
493      else if (!(ipath = strdup(imagepath)))
494           return 0;
495
496      if (defdevice) {
497           defdev = strdup(defdevice);
498           device_kind = prom_get_devtype(defdev);
499      } else if (device_kind == -1)
500           device_kind = prom_get_devtype(ipath);
501
502      /*
503       * When an iscsi iqn is present, it may have embedded colons, so
504       * don't parse off anything.
505       */
506      if (device_kind != FILE_DEVICE_NET &&
507          device_kind != FILE_DEVICE_ISCSI &&
508          strchr(defdev, ':') != NULL) {
509            if ((ptr = strrchr(defdev, ':')) != NULL)
510                 *ptr = 0; /* remove trailing : from defdevice if necessary */
511      }
512
513      /* This will not properly handle an obp-tftp argument list
514       * with elements after the filename; that is handled below.
515       */
516      if (device_kind != FILE_DEVICE_NET &&
517          device_kind != FILE_DEVICE_ISCSI &&
518          strchr(ipath, ':') != NULL) {
519           if ((ptr = strrchr(ipath, ',')) != NULL) {
520                char *colon = strrchr(ipath, ':');
521                /* If a ':' occurs *after* a ',', then we assume that there is
522                   no filename */
523                if (!colon || colon < ptr) {
524                     result->file = strdup(ptr+1);
525                     /* Trim the filename off */
526                     *ptr = 0;
527                }
528           }
529      }
530
531      if (device_kind == FILE_DEVICE_NET) {
532           if (strchr(ipath, ':')) {
533                if (extract_netboot_args(ipath, result) == 0)
534                    return 0;
535           } else {
536                /* If we didn't get a ':' then look only in netinfo */
537                extract_netinfo_args(result);
538                result->file = strdup(ipath);
539           }
540
541           if (!defdev)
542                result->dev = netdev_path_to_dev(ipath);
543      } else if (device_kind != FILE_DEVICE_ISCSI &&
544                 (ptr = strrchr(ipath, ':')) != NULL) {
545           *ptr = 0;
546           result->dev = strdup(ipath);
547           if (*(ptr+1))
548                result->part = simple_strtol(ptr+1, NULL, 10);
549      } else if (!defdev) {
550           result->dev = strdup(ipath);
551      } else if (strlen(ipath)) {
552           result->file = strdup(ipath);
553      } else {
554           free(defdev);
555           return 0;
556      }
557
558      if (!result->dev && defdev)
559           result->dev = strdup(defdev);
560
561      if (result->part < 0)
562           result->part = defpart;
563
564      if (!result->file)
565           result->file = strdup(deffile);
566
567      free(ipath);
568      if (defdev)
569           free(defdev);
570      return 1;
571 }
572
573
574 static int
575 file_block_open(        struct boot_file_t*     file,
576                         struct boot_fspec_t*    fspec,
577                         int                     partition)
578 {
579      struct partition_t*        parts;
580      struct partition_t*        p;
581      struct partition_t*        found;
582
583      parts = partitions_lookup(fspec->dev);
584      found = NULL;
585
586 #if DEBUG
587      if (parts)
588           prom_printf("partitions:\n");
589      else
590           prom_printf("no partitions found.\n");
591 #endif
592      for (p = parts; p && !found; p=p->next) {
593           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
594                   p->part_number, p->part_start, p->part_size );
595           if (partition == -1) {
596                file->fs = fs_open( file, p, fspec );
597                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
598                     continue;
599                else {
600                     partition = p->part_number;
601                     goto done;
602                }
603           }
604           if ((partition >= 0) && (partition == p->part_number))
605                found = p;
606 #if DEBUG
607           if (found)
608                prom_printf(" (match)\n");
609 #endif
610      }
611
612      /* Note: we don't skip when found is NULL since we can, in some
613       * cases, let OF figure out a default partition.
614       */
615      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
616      file->fs = fs_open( file, found, fspec );
617
618 done:
619      if (parts)
620           partitions_free(parts);
621
622      return fserrorno;
623 }
624
625 static int
626 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
627 {
628      file->fs = fs_of_netboot;
629      return fs_of_netboot->open(file, NULL, fspec);
630 }
631
632 static int
633 default_read(   struct boot_file_t*     file,
634                 unsigned int            size,
635                 void*                   buffer)
636 {
637      prom_printf("WARNING ! default_read called !\n");
638      return FILE_ERR_EOF;
639 }
640
641 static int
642 default_seek(   struct boot_file_t*     file,
643                 unsigned int            newpos)
644 {
645      prom_printf("WARNING ! default_seek called !\n");
646      return FILE_ERR_EOF;
647 }
648
649 static int
650 default_close(  struct boot_file_t*     file)
651 {
652      prom_printf("WARNING ! default_close called !\n");
653      return FILE_ERR_OK;
654 }
655
656 static struct fs_t fs_default =
657 {
658      "defaults",
659      NULL,
660      default_read,
661      default_seek,
662      default_close
663 };
664
665
666 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
667 {
668      int result;
669
670      memset(file, 0, sizeof(struct boot_file_t*));
671      file->fs        = &fs_default;
672
673      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
674              spec->dev, spec->file, spec->part);
675
676      result = prom_get_devtype(spec->dev);
677      if (result > 0)
678           file->device_kind = result;
679      else
680           return result;
681
682      switch(file->device_kind) {
683      case FILE_DEVICE_BLOCK:
684           DEBUG_F("device is a block device\n");
685           return file_block_open(file, spec, spec->part);
686      case FILE_DEVICE_ISCSI:
687           DEBUG_F("device is a iSCSI device\n");
688           return file_block_open(file, spec, spec->part);
689      case FILE_DEVICE_NET:
690           DEBUG_F("device is a network device\n");
691           return file_net_open(file, spec);
692      }
693      return 0;
694 }
695
696 /*
697  * Local variables:
698  * c-file-style: "k&r"
699  * c-basic-offset: 5
700  * End:
701  */