Enhance the parseing of IPv4 information.
[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 /*
148  * Check netinfo for ipv4 parameters and add them to the fspec iff the
149  * fspec has no existing value.
150  *
151  * Returns 1 on success, 0 on failure.
152  */
153 static int
154 extract_netinfo_args(struct boot_fspec_t *result)
155 {
156      struct bootp_packet *packet;
157
158      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
159      packet = prom_get_netinfo();
160      if (!packet)
161           return 0;
162
163      DEBUG_F("We have a boot packet\n");
164      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
165      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
166      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
167      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
168
169      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
170      if (packet->ciaddr == 0 && packet->yiaddr != 0)
171           packet->ciaddr = packet->yiaddr;
172
173      if ((result->siaddr == NULL || *(result->siaddr) == NULL)
174          && packet->siaddr != 0)
175           result->siaddr = ipv4_to_str(packet->siaddr);
176      if ((result->ciaddr == NULL || *(result->ciaddr) == NULL)
177          && packet->ciaddr != 0)
178           result->ciaddr = ipv4_to_str(packet->ciaddr);
179      if ((result->giaddr == NULL || *(result->giaddr) == NULL)
180          && packet->giaddr != 0)
181           result->giaddr = ipv4_to_str(packet->giaddr);
182
183      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
184       *        the server.  This will be okay if the client and server are on
185       *        the same IP network, if not then lets hope the server does ICMP
186       *        redirections */
187      if (result->giaddr == NULL) {
188           result->giaddr = ipv4_to_str(packet->siaddr);
189           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
190      }
191
192      return 1;
193 }
194
195 /*
196  * Extract all the arguments provided in the imagepath and fill it in result.
197  * Returns 1 on success, 0 on failure.
198  */
199 static int
200 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
201 {
202      int ret;
203
204      DEBUG_F("imagepath = %s\n", imagepath);
205
206      if (!imagepath)
207           return 1;
208
209      ret = extract_ipv4_args(imagepath, result);
210      ret |= extract_netinfo_args(result);
211
212      DEBUG_F("siaddr = <%s>\n", result->siaddr);
213      DEBUG_F("file = <%s>\n", result->file);
214      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
215      DEBUG_F("giaddr = <%s>\n", result->giaddr);
216      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
217      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
218      DEBUG_F("addl_params = <%s>\n", result->addl_params);
219    
220      return ret;
221 }
222
223 static char *netdev_path_to_dev(const char *path)
224 {
225      char *dev, *tmp;
226      size_t len;
227
228      DEBUG_F("path = %s\n", path);
229
230      if (!path)
231           return NULL;
232
233      tmp = strchr(path, ':');
234      if (!tmp)
235           return strdup(path);
236      tmp++;
237
238      len = tmp - path + 1;
239
240      dev = malloc(len);
241      if (dev) {
242           strncpy(dev, path, len);
243           dev[len - 1] = '\0';
244      }
245      return dev;
246 }
247
248 /* This function follows the device path in the devtree and separates
249    the device name, partition number, and other datas (mostly file name)
250    the string passed in parameters is changed since 0 are put in place
251    of some separators to terminate the various strings.
252
253    when a default device is supplied imagepath will be assumed to be a
254    plain filename unless it contains a : otherwise if defaultdev is
255    NULL imagepath will be assumed to be a device path.
256
257    returns 1 on success 0 on failure.
258
259    Supported examples:
260     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
261     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
262     - hd:3,/boot/vmlinux
263     - enet:10.0.0.1,/tftpboot/vmlinux
264     - enet:,/tftpboot/vmlinux
265     - enet:bootp
266     - enet:0
267     - arguments for obp-tftp open as specified in section 4.1 of
268       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
269       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
270       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
271    Supported only if defdevice == NULL
272     - disc
273     - any other device path lacking a :
274    Unsupported examples:
275     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
276     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
277
278 int
279 parse_device_path(char *imagepath, char *defdevice, int defpart,
280                   char *deffile, struct boot_fspec_t *result)
281 {
282      char *ptr;
283      char *ipath = NULL;
284      char *defdev = NULL;
285      int device_kind = -1;
286
287      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
288                 imagepath, defdevice, defpart, deffile);
289
290      result->dev = NULL;
291      result->part = -1;
292      result->file = NULL;
293
294      if (!imagepath)
295           return 0;
296
297       /*
298        * Do preliminary checking for an iscsi device; it may appear as
299        * pure a network device (device_type == "network") if this is
300        * ISWI.  This is the case on IBM systems doing an iscsi OFW
301        * boot.
302        */
303      if (strstr(imagepath, TOK_ISCSI)) {
304           /*
305            * get the virtual device information from the
306            * "nas-bootdevice" property.
307            */
308           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
309                DEBUG_F("reset boot-device to"
310                        " /chosen/nas-bootdevice = %s\n", bootdevice);
311                device_kind = FILE_DEVICE_ISCSI;
312                ipath = strdup(bootdevice);
313                if (!ipath)
314                     return 0;
315           }
316           else
317                return 0;
318      }
319      else if (!(ipath = strdup(imagepath)))
320           return 0;
321
322      if (defdevice) {
323           defdev = strdup(defdevice);
324           device_kind = prom_get_devtype(defdev);
325      } else if (device_kind == -1)
326           device_kind = prom_get_devtype(ipath);
327
328      /*
329       * When an iscsi iqn is present, it may have embedded colons, so
330       * don't parse off anything.
331       */
332      if (device_kind != FILE_DEVICE_NET &&
333          device_kind != FILE_DEVICE_ISCSI &&
334          strchr(defdev, ':') != NULL) {
335            if ((ptr = strrchr(defdev, ':')) != NULL)
336                 *ptr = 0; /* remove trailing : from defdevice if necessary */
337      }
338
339      /* This will not properly handle an obp-tftp argument list
340       * with elements after the filename; that is handled below.
341       */
342      if (device_kind != FILE_DEVICE_NET &&
343          device_kind != FILE_DEVICE_ISCSI &&
344          strchr(ipath, ':') != NULL) {
345           if ((ptr = strrchr(ipath, ',')) != NULL) {
346                char *colon = strrchr(ipath, ':');
347                /* If a ':' occurs *after* a ',', then we assume that there is
348                   no filename */
349                if (!colon || colon < ptr) {
350                     result->file = strdup(ptr+1);
351                     /* Trim the filename off */
352                     *ptr = 0;
353                }
354           }
355      }
356
357      if (device_kind == FILE_DEVICE_NET) {
358           if (strchr(ipath, ':')) {
359                if (extract_netboot_args(ipath, result) == 0)
360                    return 0;
361           } else {
362                /* If we didn't get a ':' then look only in netinfo */
363                extract_netinfo_args(result);
364                result->file = strdup(ipath);
365           }
366
367           if (!defdev)
368                result->dev = netdev_path_to_dev(ipath);
369      } else if (device_kind != FILE_DEVICE_ISCSI &&
370                 (ptr = strrchr(ipath, ':')) != NULL) {
371           *ptr = 0;
372           result->dev = strdup(ipath);
373           if (*(ptr+1))
374                result->part = simple_strtol(ptr+1, NULL, 10);
375      } else if (!defdev) {
376           result->dev = strdup(ipath);
377      } else if (strlen(ipath)) {
378           result->file = strdup(ipath);
379      } else {
380           free(defdev);
381           return 0;
382      }
383
384      if (!result->dev && defdev)
385           result->dev = strdup(defdev);
386
387      if (result->part < 0)
388           result->part = defpart;
389
390      if (!result->file)
391           result->file = strdup(deffile);
392
393      free(ipath);
394      if (defdev)
395           free(defdev);
396      return 1;
397 }
398
399
400 static int
401 file_block_open(        struct boot_file_t*     file,
402                         struct boot_fspec_t*    fspec,
403                         int                     partition)
404 {
405      struct partition_t*        parts;
406      struct partition_t*        p;
407      struct partition_t*        found;
408
409      parts = partitions_lookup(fspec->dev);
410      found = NULL;
411
412 #if DEBUG
413      if (parts)
414           prom_printf("partitions:\n");
415      else
416           prom_printf("no partitions found.\n");
417 #endif
418      for (p = parts; p && !found; p=p->next) {
419           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
420                   p->part_number, p->part_start, p->part_size );
421           if (partition == -1) {
422                file->fs = fs_open( file, p, fspec );
423                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
424                     continue;
425                else {
426                     partition = p->part_number;
427                     goto done;
428                }
429           }
430           if ((partition >= 0) && (partition == p->part_number))
431                found = p;
432 #if DEBUG
433           if (found)
434                prom_printf(" (match)\n");
435 #endif
436      }
437
438      /* Note: we don't skip when found is NULL since we can, in some
439       * cases, let OF figure out a default partition.
440       */
441      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
442      file->fs = fs_open( file, found, fspec );
443
444 done:
445      if (parts)
446           partitions_free(parts);
447
448      return fserrorno;
449 }
450
451 static int
452 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
453 {
454      file->fs = fs_of_netboot;
455      return fs_of_netboot->open(file, NULL, fspec);
456 }
457
458 static int
459 default_read(   struct boot_file_t*     file,
460                 unsigned int            size,
461                 void*                   buffer)
462 {
463      prom_printf("WARNING ! default_read called !\n");
464      return FILE_ERR_EOF;
465 }
466
467 static int
468 default_seek(   struct boot_file_t*     file,
469                 unsigned int            newpos)
470 {
471      prom_printf("WARNING ! default_seek called !\n");
472      return FILE_ERR_EOF;
473 }
474
475 static int
476 default_close(  struct boot_file_t*     file)
477 {
478      prom_printf("WARNING ! default_close called !\n");
479      return FILE_ERR_OK;
480 }
481
482 static struct fs_t fs_default =
483 {
484      "defaults",
485      NULL,
486      default_read,
487      default_seek,
488      default_close
489 };
490
491
492 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
493 {
494      int result;
495
496      memset(file, 0, sizeof(struct boot_file_t*));
497      file->fs        = &fs_default;
498
499      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
500              spec->dev, spec->file, spec->part);
501
502      result = prom_get_devtype(spec->dev);
503      if (result > 0)
504           file->device_kind = result;
505      else
506           return result;
507
508      switch(file->device_kind) {
509      case FILE_DEVICE_BLOCK:
510           DEBUG_F("device is a block device\n");
511           return file_block_open(file, spec, spec->part);
512      case FILE_DEVICE_NET:
513           DEBUG_F("device is a network device\n");
514           return file_net_open(file, spec);
515      }
516      return 0;
517 }
518
519 /*
520  * Local variables:
521  * c-file-style: "k&r"
522  * c-basic-offset: 5
523  * End:
524  */