Parse the subnetmask from bootpath and 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 /* 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  * Returns 1 on success, 0 on failure.
266  */
267 static int
268 extract_netinfo_args(struct boot_fspec_t *result)
269 {
270      struct bootp_packet *packet;
271
272      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
273      packet = prom_get_netinfo();
274      if (!packet)
275           return 0;
276
277      DEBUG_F("We have a boot packet\n");
278      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
279      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
280      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
281      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
282
283      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
284      if (packet->ciaddr == 0 && packet->yiaddr != 0)
285           packet->ciaddr = packet->yiaddr;
286
287      if ((result->siaddr == NULL || *(result->siaddr) == '\x0')
288          && packet->siaddr != 0)
289           result->siaddr = ipv4_to_str(packet->siaddr);
290      if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0')
291          && packet->ciaddr != 0)
292           result->ciaddr = ipv4_to_str(packet->ciaddr);
293      if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
294          && packet->giaddr != 0)
295           result->giaddr = ipv4_to_str(packet->giaddr);
296
297      extract_vendor_options(packet, result);
298
299      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
300       *        the server.  This will be okay if the client and server are on
301       *        the same IP network, if not then lets hope the server does ICMP
302       *        redirections */
303      if (result->giaddr == NULL) {
304           result->giaddr = ipv4_to_str(packet->siaddr);
305           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
306      }
307
308      return 1;
309 }
310
311 /*
312  * Extract all the arguments provided in the imagepath and fill it in result.
313  * Returns 1 on success, 0 on failure.
314  */
315 static int
316 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
317 {
318      int ret;
319
320      DEBUG_F("imagepath = %s\n", imagepath);
321
322      if (!imagepath)
323           return 1;
324
325      ret = extract_ipv4_args(imagepath, result);
326      ret |= extract_netinfo_args(result);
327
328      DEBUG_F("siaddr = <%s>\n", result->siaddr);
329      DEBUG_F("file = <%s>\n", result->file);
330      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
331      DEBUG_F("giaddr = <%s>\n", result->giaddr);
332      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
333      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
334      DEBUG_F("addl_params = <%s>\n", result->addl_params);
335    
336      return ret;
337 }
338
339 static char *netdev_path_to_dev(const char *path)
340 {
341      char *dev, *tmp;
342      size_t len;
343
344      DEBUG_F("path = %s\n", path);
345
346      if (!path)
347           return NULL;
348
349      tmp = strchr(path, ':');
350      if (!tmp)
351           return strdup(path);
352      tmp++;
353
354      len = tmp - path + 1;
355
356      dev = malloc(len);
357      if (dev) {
358           strncpy(dev, path, len);
359           dev[len - 1] = '\0';
360      }
361      return dev;
362 }
363
364 /* This function follows the device path in the devtree and separates
365    the device name, partition number, and other datas (mostly file name)
366    the string passed in parameters is changed since 0 are put in place
367    of some separators to terminate the various strings.
368
369    when a default device is supplied imagepath will be assumed to be a
370    plain filename unless it contains a : otherwise if defaultdev is
371    NULL imagepath will be assumed to be a device path.
372
373    returns 1 on success 0 on failure.
374
375    Supported examples:
376     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
377     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
378     - hd:3,/boot/vmlinux
379     - enet:10.0.0.1,/tftpboot/vmlinux
380     - enet:,/tftpboot/vmlinux
381     - enet:bootp
382     - enet:0
383     - arguments for obp-tftp open as specified in section 4.1 of
384       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
385       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
386       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
387    Supported only if defdevice == NULL
388     - disc
389     - any other device path lacking a :
390    Unsupported examples:
391     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
392     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
393
394 int
395 parse_device_path(char *imagepath, char *defdevice, int defpart,
396                   char *deffile, struct boot_fspec_t *result)
397 {
398      char *ptr;
399      char *ipath = NULL;
400      char *defdev = NULL;
401      int device_kind = -1;
402
403      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
404                 imagepath, defdevice, defpart, deffile);
405
406      result->dev = NULL;
407      result->part = -1;
408      result->file = NULL;
409
410      if (!imagepath)
411           return 0;
412
413       /*
414        * Do preliminary checking for an iscsi device; it may appear as
415        * pure a network device (device_type == "network") if this is
416        * ISWI.  This is the case on IBM systems doing an iscsi OFW
417        * boot.
418        */
419      if (strstr(imagepath, TOK_ISCSI)) {
420           /*
421            * get the virtual device information from the
422            * "nas-bootdevice" property.
423            */
424           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
425                DEBUG_F("reset boot-device to"
426                        " /chosen/nas-bootdevice = %s\n", bootdevice);
427                device_kind = FILE_DEVICE_ISCSI;
428                ipath = strdup(bootdevice);
429                if (!ipath)
430                     return 0;
431           }
432           else
433                return 0;
434      }
435      else if (!(ipath = strdup(imagepath)))
436           return 0;
437
438      if (defdevice) {
439           defdev = strdup(defdevice);
440           device_kind = prom_get_devtype(defdev);
441      } else if (device_kind == -1)
442           device_kind = prom_get_devtype(ipath);
443
444      /*
445       * When an iscsi iqn is present, it may have embedded colons, so
446       * don't parse off anything.
447       */
448      if (device_kind != FILE_DEVICE_NET &&
449          device_kind != FILE_DEVICE_ISCSI &&
450          strchr(defdev, ':') != NULL) {
451            if ((ptr = strrchr(defdev, ':')) != NULL)
452                 *ptr = 0; /* remove trailing : from defdevice if necessary */
453      }
454
455      /* This will not properly handle an obp-tftp argument list
456       * with elements after the filename; that is handled below.
457       */
458      if (device_kind != FILE_DEVICE_NET &&
459          device_kind != FILE_DEVICE_ISCSI &&
460          strchr(ipath, ':') != NULL) {
461           if ((ptr = strrchr(ipath, ',')) != NULL) {
462                char *colon = strrchr(ipath, ':');
463                /* If a ':' occurs *after* a ',', then we assume that there is
464                   no filename */
465                if (!colon || colon < ptr) {
466                     result->file = strdup(ptr+1);
467                     /* Trim the filename off */
468                     *ptr = 0;
469                }
470           }
471      }
472
473      if (device_kind == FILE_DEVICE_NET) {
474           if (strchr(ipath, ':')) {
475                if (extract_netboot_args(ipath, result) == 0)
476                    return 0;
477           } else {
478                /* If we didn't get a ':' then look only in netinfo */
479                extract_netinfo_args(result);
480                result->file = strdup(ipath);
481           }
482
483           if (!defdev)
484                result->dev = netdev_path_to_dev(ipath);
485      } else if (device_kind != FILE_DEVICE_ISCSI &&
486                 (ptr = strrchr(ipath, ':')) != NULL) {
487           *ptr = 0;
488           result->dev = strdup(ipath);
489           if (*(ptr+1))
490                result->part = simple_strtol(ptr+1, NULL, 10);
491      } else if (!defdev) {
492           result->dev = strdup(ipath);
493      } else if (strlen(ipath)) {
494           result->file = strdup(ipath);
495      } else {
496           free(defdev);
497           return 0;
498      }
499
500      if (!result->dev && defdev)
501           result->dev = strdup(defdev);
502
503      if (result->part < 0)
504           result->part = defpart;
505
506      if (!result->file)
507           result->file = strdup(deffile);
508
509      free(ipath);
510      if (defdev)
511           free(defdev);
512      return 1;
513 }
514
515
516 static int
517 file_block_open(        struct boot_file_t*     file,
518                         struct boot_fspec_t*    fspec,
519                         int                     partition)
520 {
521      struct partition_t*        parts;
522      struct partition_t*        p;
523      struct partition_t*        found;
524
525      parts = partitions_lookup(fspec->dev);
526      found = NULL;
527
528 #if DEBUG
529      if (parts)
530           prom_printf("partitions:\n");
531      else
532           prom_printf("no partitions found.\n");
533 #endif
534      for (p = parts; p && !found; p=p->next) {
535           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
536                   p->part_number, p->part_start, p->part_size );
537           if (partition == -1) {
538                file->fs = fs_open( file, p, fspec );
539                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
540                     continue;
541                else {
542                     partition = p->part_number;
543                     goto done;
544                }
545           }
546           if ((partition >= 0) && (partition == p->part_number))
547                found = p;
548 #if DEBUG
549           if (found)
550                prom_printf(" (match)\n");
551 #endif
552      }
553
554      /* Note: we don't skip when found is NULL since we can, in some
555       * cases, let OF figure out a default partition.
556       */
557      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
558      file->fs = fs_open( file, found, fspec );
559
560 done:
561      if (parts)
562           partitions_free(parts);
563
564      return fserrorno;
565 }
566
567 static int
568 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
569 {
570      file->fs = fs_of_netboot;
571      return fs_of_netboot->open(file, NULL, fspec);
572 }
573
574 static int
575 default_read(   struct boot_file_t*     file,
576                 unsigned int            size,
577                 void*                   buffer)
578 {
579      prom_printf("WARNING ! default_read called !\n");
580      return FILE_ERR_EOF;
581 }
582
583 static int
584 default_seek(   struct boot_file_t*     file,
585                 unsigned int            newpos)
586 {
587      prom_printf("WARNING ! default_seek called !\n");
588      return FILE_ERR_EOF;
589 }
590
591 static int
592 default_close(  struct boot_file_t*     file)
593 {
594      prom_printf("WARNING ! default_close called !\n");
595      return FILE_ERR_OK;
596 }
597
598 static struct fs_t fs_default =
599 {
600      "defaults",
601      NULL,
602      default_read,
603      default_seek,
604      default_close
605 };
606
607
608 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
609 {
610      int result;
611
612      memset(file, 0, sizeof(struct boot_file_t*));
613      file->fs        = &fs_default;
614
615      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
616              spec->dev, spec->file, spec->part);
617
618      result = prom_get_devtype(spec->dev);
619      if (result > 0)
620           file->device_kind = result;
621      else
622           return result;
623
624      switch(file->device_kind) {
625      case FILE_DEVICE_BLOCK:
626           DEBUG_F("device is a block device\n");
627           return file_block_open(file, spec, spec->part);
628      case FILE_DEVICE_NET:
629           DEBUG_F("device is a network device\n");
630           return file_net_open(file, spec);
631      }
632      return 0;
633 }
634
635 /*
636  * Local variables:
637  * c-file-style: "k&r"
638  * c-basic-offset: 5
639  * End:
640  */