Add parseing for Vendor options aka DHCP options.
[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 /*
55  * Copy the string from source to dest till newline or comma(,) is seen
56  * in the source.
57  * Move source and dest pointers respectively.
58  * Returns pointer to the start of the string that has just been copied.
59  */
60 static char *
61 scopy(char **dest, char **source)
62 {
63      char *ret = *dest;
64
65      if (!**source)
66           return NULL;
67
68      while (**source != ',' && **source != '\0')
69           *(*dest)++ = *(*source)++;
70      if (**source != '\0')
71           *(*source)++;
72      **dest = '\0';
73      *(*dest)++;
74      return ret;
75 }
76
77 /*
78  * Extract all the ipv4 arguments from the bootpath provided and fill result
79  * Returns 1 on success, 0 on failure.
80  */
81 static int
82 extract_ipv4_args(char *imagepath, struct boot_fspec_t *result)
83 {
84      char *tmp, *args, *str, *start;
85
86      args = strrchr(imagepath, ':');
87      if (!args)
88           return 1;
89
90      start = args; /* used to see if we read any optional parameters */
91
92      /* The obp-tftp device arguments should be at the end of
93       * the argument list.  Skip over any extra arguments (promiscuous,
94       * speed, duplex, bootp, rarp).
95       */
96
97      tmp = strstr(args, "promiscuous");
98      if (tmp && tmp > args)
99           args = tmp + strlen("promiscuous");
100
101      tmp = strstr(args, "speed=");
102      if (tmp && tmp > args)
103           args = tmp + strlen("speed=");
104
105      tmp = strstr(args, "duplex=");
106      if (tmp && tmp > args)
107           args = tmp + strlen("duplex=");
108
109      tmp = strstr(args, "bootp");
110      if (tmp && tmp > args)
111           args = tmp + strlen("bootp");
112
113      tmp = strstr(args, "rarp");
114      if (tmp && tmp > args)
115           args = tmp + strlen("rarp");
116
117      if (args != start) /* we read some parameters, so go past the next comma(,) */
118           args = strchr(args, ',');
119      if (!args)
120           return 1;
121
122      str = malloc(strlen(args) + 1); /*long enough to hold all strings */
123      if (!str)
124           return 0;
125
126      if (args[-1] != ':')
127           args++; /* If comma(,) is not immediately followed by ':' then go past the , */
128
129      /*
130       * read the arguments in order: siaddr,filename,ciaddr,giaddr,
131       * bootp-retries,tftp-retries,addl_prameters
132       */
133      result->siaddr = scopy(&str, &args);
134      result->file = scopy(&str, &args);
135      result->ciaddr = scopy(&str, &args);
136      result->giaddr = scopy(&str, &args);
137      result->bootp_retries = scopy(&str, &args);
138      result->tftp_retries = scopy(&str, &args);
139      if (*args) {
140           result->addl_params = strdup(args);
141           if (!result->addl_params)
142                 return 0;
143      }
144      return 1;
145 }
146
147 /* DHCP options */
148 enum dhcp_options {
149      DHCP_PAD = 0,
150      DHCP_NETMASK = 1,
151      DHCP_ROUTERS = 3,
152      DHCP_DNS = 6,
153      DHCP_END = 255,
154 };
155
156 #define DHCP_COOKIE        0x63825363
157 #define DHCP_COOKIE_SIZE   4
158
159 /*
160  * Process the bootp reply packet's vendor extensions.
161  * Vendor extensions are detailed in: http://www.faqs.org/rfcs/rfc1084.html
162  */
163 static void
164 extract_vendor_options(struct bootp_packet *packet, struct boot_fspec_t *result)
165 {
166      int i = 0;
167      __u32 cookie;
168      __u8 *options = &packet->options[0];
169
170      memcpy(&cookie, &options[i], DHCP_COOKIE_SIZE);
171
172      if (cookie != DHCP_COOKIE) {
173           prom_printf("EEEK! cookie is fubar got %08x expected %08x\n",
174                       cookie, DHCP_COOKIE);
175           return;
176      }
177
178      i += DHCP_COOKIE_SIZE;
179
180      /* FIXME: It may be possible to run off the end of a packet here /if/
181       *         it's malformed. :( */
182      while (options[i] != DHCP_END) {
183           __u8 tag = options[i++], len;
184           __u32 value;
185
186           if (tag == DHCP_PAD)
187                continue;
188
189           len = options[i++];
190           memcpy(&value, &options[i], len);
191
192 #if DEBUG
193 {
194      DEBUG_F("tag=%2d, len=%2d, data=", tag, len);
195      int j;
196      for (j=0; j<len; j++)
197           prom_printf("%02x", options[i+j]);
198      prom_printf("\n");
199 }
200 #endif
201
202           switch (tag) {
203                case DHCP_NETMASK:
204                     /* FIXME: do we need to grok the subnet mask? */
205                     break;
206                case DHCP_ROUTERS:
207                     if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
208                         && value != 0) {
209                          result->giaddr = ipv4_to_str(value);
210                          DEBUG_F("Storing %s as gateway from options\n",
211                                  result->giaddr);
212                     }
213                     break;
214                }
215           i += len;
216      }
217 }
218
219 /*
220  * Check netinfo for ipv4 parameters and add them to the fspec iff the
221  * fspec has no existing value.
222  *
223  * Returns 1 on success, 0 on failure.
224  */
225 static int
226 extract_netinfo_args(struct boot_fspec_t *result)
227 {
228      struct bootp_packet *packet;
229
230      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
231      packet = prom_get_netinfo();
232      if (!packet)
233           return 0;
234
235      DEBUG_F("We have a boot packet\n");
236      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
237      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
238      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
239      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
240
241      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
242      if (packet->ciaddr == 0 && packet->yiaddr != 0)
243           packet->ciaddr = packet->yiaddr;
244
245      if ((result->siaddr == NULL || *(result->siaddr) == '\x0')
246          && packet->siaddr != 0)
247           result->siaddr = ipv4_to_str(packet->siaddr);
248      if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0')
249          && packet->ciaddr != 0)
250           result->ciaddr = ipv4_to_str(packet->ciaddr);
251      if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
252          && packet->giaddr != 0)
253           result->giaddr = ipv4_to_str(packet->giaddr);
254
255      extract_vendor_options(packet, result);
256
257      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
258       *        the server.  This will be okay if the client and server are on
259       *        the same IP network, if not then lets hope the server does ICMP
260       *        redirections */
261      if (result->giaddr == NULL) {
262           result->giaddr = ipv4_to_str(packet->siaddr);
263           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
264      }
265
266      return 1;
267 }
268
269 /*
270  * Extract all the arguments provided in the imagepath and fill it in result.
271  * Returns 1 on success, 0 on failure.
272  */
273 static int
274 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
275 {
276      int ret;
277
278      DEBUG_F("imagepath = %s\n", imagepath);
279
280      if (!imagepath)
281           return 1;
282
283      ret = extract_ipv4_args(imagepath, result);
284      ret |= extract_netinfo_args(result);
285
286      DEBUG_F("siaddr = <%s>\n", result->siaddr);
287      DEBUG_F("file = <%s>\n", result->file);
288      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
289      DEBUG_F("giaddr = <%s>\n", result->giaddr);
290      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
291      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
292      DEBUG_F("addl_params = <%s>\n", result->addl_params);
293    
294      return ret;
295 }
296
297 static char *netdev_path_to_dev(const char *path)
298 {
299      char *dev, *tmp;
300      size_t len;
301
302      DEBUG_F("path = %s\n", path);
303
304      if (!path)
305           return NULL;
306
307      tmp = strchr(path, ':');
308      if (!tmp)
309           return strdup(path);
310      tmp++;
311
312      len = tmp - path + 1;
313
314      dev = malloc(len);
315      if (dev) {
316           strncpy(dev, path, len);
317           dev[len - 1] = '\0';
318      }
319      return dev;
320 }
321
322 /* This function follows the device path in the devtree and separates
323    the device name, partition number, and other datas (mostly file name)
324    the string passed in parameters is changed since 0 are put in place
325    of some separators to terminate the various strings.
326
327    when a default device is supplied imagepath will be assumed to be a
328    plain filename unless it contains a : otherwise if defaultdev is
329    NULL imagepath will be assumed to be a device path.
330
331    returns 1 on success 0 on failure.
332
333    Supported examples:
334     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
335     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
336     - hd:3,/boot/vmlinux
337     - enet:10.0.0.1,/tftpboot/vmlinux
338     - enet:,/tftpboot/vmlinux
339     - enet:bootp
340     - enet:0
341     - arguments for obp-tftp open as specified in section 4.1 of
342       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
343       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
344       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
345    Supported only if defdevice == NULL
346     - disc
347     - any other device path lacking a :
348    Unsupported examples:
349     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
350     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
351
352 int
353 parse_device_path(char *imagepath, char *defdevice, int defpart,
354                   char *deffile, struct boot_fspec_t *result)
355 {
356      char *ptr;
357      char *ipath = NULL;
358      char *defdev = NULL;
359      int device_kind = -1;
360
361      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
362                 imagepath, defdevice, defpart, deffile);
363
364      result->dev = NULL;
365      result->part = -1;
366      result->file = NULL;
367
368      if (!imagepath)
369           return 0;
370
371       /*
372        * Do preliminary checking for an iscsi device; it may appear as
373        * pure a network device (device_type == "network") if this is
374        * ISWI.  This is the case on IBM systems doing an iscsi OFW
375        * boot.
376        */
377      if (strstr(imagepath, TOK_ISCSI)) {
378           /*
379            * get the virtual device information from the
380            * "nas-bootdevice" property.
381            */
382           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
383                DEBUG_F("reset boot-device to"
384                        " /chosen/nas-bootdevice = %s\n", bootdevice);
385                device_kind = FILE_DEVICE_ISCSI;
386                ipath = strdup(bootdevice);
387                if (!ipath)
388                     return 0;
389           }
390           else
391                return 0;
392      }
393      else if (!(ipath = strdup(imagepath)))
394           return 0;
395
396      if (defdevice) {
397           defdev = strdup(defdevice);
398           device_kind = prom_get_devtype(defdev);
399      } else if (device_kind == -1)
400           device_kind = prom_get_devtype(ipath);
401
402      /*
403       * When an iscsi iqn is present, it may have embedded colons, so
404       * don't parse off anything.
405       */
406      if (device_kind != FILE_DEVICE_NET &&
407          device_kind != FILE_DEVICE_ISCSI &&
408          strchr(defdev, ':') != NULL) {
409            if ((ptr = strrchr(defdev, ':')) != NULL)
410                 *ptr = 0; /* remove trailing : from defdevice if necessary */
411      }
412
413      /* This will not properly handle an obp-tftp argument list
414       * with elements after the filename; that is handled below.
415       */
416      if (device_kind != FILE_DEVICE_NET &&
417          device_kind != FILE_DEVICE_ISCSI &&
418          strchr(ipath, ':') != NULL) {
419           if ((ptr = strrchr(ipath, ',')) != NULL) {
420                char *colon = strrchr(ipath, ':');
421                /* If a ':' occurs *after* a ',', then we assume that there is
422                   no filename */
423                if (!colon || colon < ptr) {
424                     result->file = strdup(ptr+1);
425                     /* Trim the filename off */
426                     *ptr = 0;
427                }
428           }
429      }
430
431      if (device_kind == FILE_DEVICE_NET) {
432           if (strchr(ipath, ':')) {
433                if (extract_netboot_args(ipath, result) == 0)
434                    return 0;
435           } else {
436                /* If we didn't get a ':' then look only in netinfo */
437                extract_netinfo_args(result);
438                result->file = strdup(ipath);
439           }
440
441           if (!defdev)
442                result->dev = netdev_path_to_dev(ipath);
443      } else if (device_kind != FILE_DEVICE_ISCSI &&
444                 (ptr = strrchr(ipath, ':')) != NULL) {
445           *ptr = 0;
446           result->dev = strdup(ipath);
447           if (*(ptr+1))
448                result->part = simple_strtol(ptr+1, NULL, 10);
449      } else if (!defdev) {
450           result->dev = strdup(ipath);
451      } else if (strlen(ipath)) {
452           result->file = strdup(ipath);
453      } else {
454           free(defdev);
455           return 0;
456      }
457
458      if (!result->dev && defdev)
459           result->dev = strdup(defdev);
460
461      if (result->part < 0)
462           result->part = defpart;
463
464      if (!result->file)
465           result->file = strdup(deffile);
466
467      free(ipath);
468      if (defdev)
469           free(defdev);
470      return 1;
471 }
472
473
474 static int
475 file_block_open(        struct boot_file_t*     file,
476                         struct boot_fspec_t*    fspec,
477                         int                     partition)
478 {
479      struct partition_t*        parts;
480      struct partition_t*        p;
481      struct partition_t*        found;
482
483      parts = partitions_lookup(fspec->dev);
484      found = NULL;
485
486 #if DEBUG
487      if (parts)
488           prom_printf("partitions:\n");
489      else
490           prom_printf("no partitions found.\n");
491 #endif
492      for (p = parts; p && !found; p=p->next) {
493           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
494                   p->part_number, p->part_start, p->part_size );
495           if (partition == -1) {
496                file->fs = fs_open( file, p, fspec );
497                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
498                     continue;
499                else {
500                     partition = p->part_number;
501                     goto done;
502                }
503           }
504           if ((partition >= 0) && (partition == p->part_number))
505                found = p;
506 #if DEBUG
507           if (found)
508                prom_printf(" (match)\n");
509 #endif
510      }
511
512      /* Note: we don't skip when found is NULL since we can, in some
513       * cases, let OF figure out a default partition.
514       */
515      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
516      file->fs = fs_open( file, found, fspec );
517
518 done:
519      if (parts)
520           partitions_free(parts);
521
522      return fserrorno;
523 }
524
525 static int
526 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
527 {
528      file->fs = fs_of_netboot;
529      return fs_of_netboot->open(file, NULL, fspec);
530 }
531
532 static int
533 default_read(   struct boot_file_t*     file,
534                 unsigned int            size,
535                 void*                   buffer)
536 {
537      prom_printf("WARNING ! default_read called !\n");
538      return FILE_ERR_EOF;
539 }
540
541 static int
542 default_seek(   struct boot_file_t*     file,
543                 unsigned int            newpos)
544 {
545      prom_printf("WARNING ! default_seek called !\n");
546      return FILE_ERR_EOF;
547 }
548
549 static int
550 default_close(  struct boot_file_t*     file)
551 {
552      prom_printf("WARNING ! default_close called !\n");
553      return FILE_ERR_OK;
554 }
555
556 static struct fs_t fs_default =
557 {
558      "defaults",
559      NULL,
560      default_read,
561      default_seek,
562      default_close
563 };
564
565
566 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
567 {
568      int result;
569
570      memset(file, 0, sizeof(struct boot_file_t*));
571      file->fs        = &fs_default;
572
573      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
574              spec->dev, spec->file, spec->part);
575
576      result = prom_get_devtype(spec->dev);
577      if (result > 0)
578           file->device_kind = result;
579      else
580           return result;
581
582      switch(file->device_kind) {
583      case FILE_DEVICE_BLOCK:
584           DEBUG_F("device is a block device\n");
585           return file_block_open(file, spec, spec->part);
586      case FILE_DEVICE_NET:
587           DEBUG_F("device is a network device\n");
588           return file_net_open(file, spec);
589      }
590      return 0;
591 }
592
593 /*
594  * Local variables:
595  * c-file-style: "k&r"
596  * c-basic-offset: 5
597  * End:
598  */