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