Move ipv4 arguments handling to a separate function. No fucntional change.
[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 /*
42  * Copy the string from source to dest till newline or comma(,) is seen
43  * in the source.
44  * Move source and dest pointers respectively.
45  * Returns pointer to the start of the string that has just been copied.
46  */
47 static char *
48 scopy(char **dest, char **source)
49 {
50      char *ret = *dest;
51
52      if (!**source)
53           return NULL;
54
55      while (**source != ',' && **source != '\0')
56           *(*dest)++ = *(*source)++;
57      if (**source != '\0')
58           *(*source)++;
59      **dest = '\0';
60      *(*dest)++;
61      return ret;
62 }
63
64 /*
65  * Extract all the ipv4 arguments from the bootpath provided and fill result
66  * Returns 1 on success, 0 on failure.
67  */
68 static int
69 extract_ipv4_args(char *imagepath, struct boot_fspec_t *result)
70 {
71      char *tmp, *args, *str, *start;
72
73      args = strrchr(imagepath, ':');
74      if (!args)
75           return 1;
76
77      start = args; /* used to see if we read any optional parameters */
78
79      /* The obp-tftp device arguments should be at the end of
80       * the argument list.  Skip over any extra arguments (promiscuous,
81       * speed, duplex, bootp, rarp).
82       */
83
84      tmp = strstr(args, "promiscuous");
85      if (tmp && tmp > args)
86           args = tmp + strlen("promiscuous");
87
88      tmp = strstr(args, "speed=");
89      if (tmp && tmp > args)
90           args = tmp + strlen("speed=");
91
92      tmp = strstr(args, "duplex=");
93      if (tmp && tmp > args)
94           args = tmp + strlen("duplex=");
95
96      tmp = strstr(args, "bootp");
97      if (tmp && tmp > args)
98           args = tmp + strlen("bootp");
99
100      tmp = strstr(args, "rarp");
101      if (tmp && tmp > args)
102           args = tmp + strlen("rarp");
103
104      if (args != start) /* we read some parameters, so go past the next comma(,) */
105           args = strchr(args, ',');
106      if (!args)
107           return 1;
108
109      str = malloc(strlen(args) + 1); /*long enough to hold all strings */
110      if (!str)
111           return 0;
112
113      if (args[-1] != ':')
114           args++; /* If comma(,) is not immediately followed by ':' then go past the , */
115
116      /*
117       * read the arguments in order: siaddr,filename,ciaddr,giaddr,
118       * bootp-retries,tftp-retries,addl_prameters
119       */
120      result->siaddr = scopy(&str, &args);
121      result->file = scopy(&str, &args);
122      result->ciaddr = scopy(&str, &args);
123      result->giaddr = scopy(&str, &args);
124      result->bootp_retries = scopy(&str, &args);
125      result->tftp_retries = scopy(&str, &args);
126      if (*args) {
127           result->addl_params = strdup(args);
128           if (!result->addl_params)
129                 return 0;
130      }
131      return 1;
132 }
133
134 /*
135  * Extract all the arguments provided in the imagepath and fill it in result.
136  * Returns 1 on success, 0 on failure.
137  */
138 static int
139 extract_args_from_netdev_path(char *imagepath, struct boot_fspec_t *result)
140 {
141      int ret;
142
143      DEBUG_F("imagepath = %s\n", imagepath);
144
145      if (!imagepath)
146           return 1;
147
148      ret = extract_ipv4_args(imagepath, result);
149
150      DEBUG_F("siaddr = <%s>\n", result->siaddr);
151      DEBUG_F("file = <%s>\n", result->file);
152      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
153      DEBUG_F("giaddr = <%s>\n", result->giaddr);
154      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
155      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
156      DEBUG_F("addl_params = <%s>\n", result->addl_params);
157      return ret;
158 }
159
160 static char *netdev_path_to_dev(const char *path)
161 {
162      char *dev, *tmp;
163      size_t len;
164
165      DEBUG_F("path = %s\n", path);
166
167      if (!path)
168           return NULL;
169
170      tmp = strchr(path, ':');
171      if (!tmp)
172           return strdup(path);
173      tmp++;
174
175      len = tmp - path + 1;
176
177      dev = malloc(len);
178      if (dev) {
179           strncpy(dev, path, len);
180           dev[len - 1] = '\0';
181      }
182      return dev;
183 }
184
185 /* This function follows the device path in the devtree and separates
186    the device name, partition number, and other datas (mostly file name)
187    the string passed in parameters is changed since 0 are put in place
188    of some separators to terminate the various strings.
189
190    when a default device is supplied imagepath will be assumed to be a
191    plain filename unless it contains a : otherwise if defaultdev is
192    NULL imagepath will be assumed to be a device path.
193
194    returns 1 on success 0 on failure.
195
196    Supported examples:
197     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
198     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
199     - hd:3,/boot/vmlinux
200     - enet:10.0.0.1,/tftpboot/vmlinux
201     - enet:,/tftpboot/vmlinux
202     - enet:bootp
203     - enet:0
204     - arguments for obp-tftp open as specified in section 4.1 of
205       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
206       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
207       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
208    Supported only if defdevice == NULL
209     - disc
210     - any other device path lacking a :
211    Unsupported examples:
212     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
213     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
214
215 int
216 parse_device_path(char *imagepath, char *defdevice, int defpart,
217                   char *deffile, struct boot_fspec_t *result)
218 {
219      char *ptr;
220      char *ipath = NULL;
221      char *defdev = NULL;
222      int device_kind = -1;
223
224      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
225                 imagepath, defdevice, defpart, deffile);
226
227      result->dev = NULL;
228      result->part = -1;
229      result->file = NULL;
230
231      if (!imagepath)
232           return 0;
233
234       /*
235        * Do preliminary checking for an iscsi device; it may appear as
236        * pure a network device (device_type == "network") if this is
237        * ISWI.  This is the case on IBM systems doing an iscsi OFW
238        * boot.
239        */
240      if (strstr(imagepath, TOK_ISCSI)) {
241           /*
242            * get the virtual device information from the
243            * "nas-bootdevice" property.
244            */
245           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
246                DEBUG_F("reset boot-device to"
247                        " /chosen/nas-bootdevice = %s\n", bootdevice);
248                device_kind = FILE_DEVICE_ISCSI;
249                ipath = strdup(bootdevice);
250                if (!ipath)
251                     return 0;
252           }
253           else
254                return 0;
255      }
256      else if (!(ipath = strdup(imagepath)))
257           return 0;
258
259      if (defdevice) {
260           defdev = strdup(defdevice);
261           device_kind = prom_get_devtype(defdev);
262      } else if (device_kind == -1)
263           device_kind = prom_get_devtype(ipath);
264
265      /*
266       * When an iscsi iqn is present, it may have embedded colons, so
267       * don't parse off anything.
268       */
269      if (device_kind != FILE_DEVICE_NET &&
270          device_kind != FILE_DEVICE_ISCSI &&
271          strchr(defdev, ':') != NULL) {
272            if ((ptr = strrchr(defdev, ':')) != NULL)
273                 *ptr = 0; /* remove trailing : from defdevice if necessary */
274      }
275
276      /* This will not properly handle an obp-tftp argument list
277       * with elements after the filename; that is handled below.
278       */
279      if (device_kind != FILE_DEVICE_NET &&
280          device_kind != FILE_DEVICE_ISCSI &&
281          strchr(ipath, ':') != NULL) {
282           if ((ptr = strrchr(ipath, ',')) != NULL) {
283                char *colon = strrchr(ipath, ':');
284                /* If a ':' occurs *after* a ',', then we assume that there is
285                   no filename */
286                if (!colon || colon < ptr) {
287                     result->file = strdup(ptr+1);
288                     /* Trim the filename off */
289                     *ptr = 0;
290                }
291           }
292      }
293
294      if (device_kind == FILE_DEVICE_NET) {
295           if (strchr(ipath, ':')) {
296                if (extract_args_from_netdev_path(ipath, result) == 0)
297                    return 0;
298           } else
299                result->file = strdup(ipath);
300
301           if (!defdev)
302                result->dev = netdev_path_to_dev(ipath);
303      } else if (device_kind != FILE_DEVICE_ISCSI &&
304                 (ptr = strrchr(ipath, ':')) != NULL) {
305           *ptr = 0;
306           result->dev = strdup(ipath);
307           if (*(ptr+1))
308                result->part = simple_strtol(ptr+1, NULL, 10);
309      } else if (!defdev) {
310           result->dev = strdup(ipath);
311      } else if (strlen(ipath)) {
312           result->file = strdup(ipath);
313      } else {
314           free(defdev);
315           return 0;
316      }
317
318      if (!result->dev && defdev)
319           result->dev = strdup(defdev);
320
321      if (result->part < 0)
322           result->part = defpart;
323
324      if (!result->file)
325           result->file = strdup(deffile);
326
327      free(ipath);
328      if (defdev)
329           free(defdev);
330      return 1;
331 }
332
333
334 static int
335 file_block_open(        struct boot_file_t*     file,
336                         struct boot_fspec_t*    fspec,
337                         int                     partition)
338 {
339      struct partition_t*        parts;
340      struct partition_t*        p;
341      struct partition_t*        found;
342
343      parts = partitions_lookup(fspec->dev);
344      found = NULL;
345
346 #if DEBUG
347      if (parts)
348           prom_printf("partitions:\n");
349      else
350           prom_printf("no partitions found.\n");
351 #endif
352      for (p = parts; p && !found; p=p->next) {
353           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
354                   p->part_number, p->part_start, p->part_size );
355           if (partition == -1) {
356                file->fs = fs_open( file, p, fspec );
357                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
358                     continue;
359                else {
360                     partition = p->part_number;
361                     goto done;
362                }
363           }
364           if ((partition >= 0) && (partition == p->part_number))
365                found = p;
366 #if DEBUG
367           if (found)
368                prom_printf(" (match)\n");
369 #endif
370      }
371
372      /* Note: we don't skip when found is NULL since we can, in some
373       * cases, let OF figure out a default partition.
374       */
375      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
376      file->fs = fs_open( file, found, fspec );
377
378 done:
379      if (parts)
380           partitions_free(parts);
381
382      return fserrorno;
383 }
384
385 static int
386 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
387 {
388      file->fs = fs_of_netboot;
389      return fs_of_netboot->open(file, NULL, fspec);
390 }
391
392 static int
393 default_read(   struct boot_file_t*     file,
394                 unsigned int            size,
395                 void*                   buffer)
396 {
397      prom_printf("WARNING ! default_read called !\n");
398      return FILE_ERR_EOF;
399 }
400
401 static int
402 default_seek(   struct boot_file_t*     file,
403                 unsigned int            newpos)
404 {
405      prom_printf("WARNING ! default_seek called !\n");
406      return FILE_ERR_EOF;
407 }
408
409 static int
410 default_close(  struct boot_file_t*     file)
411 {
412      prom_printf("WARNING ! default_close called !\n");
413      return FILE_ERR_OK;
414 }
415
416 static struct fs_t fs_default =
417 {
418      "defaults",
419      NULL,
420      default_read,
421      default_seek,
422      default_close
423 };
424
425
426 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
427 {
428      int result;
429
430      memset(file, 0, sizeof(struct boot_file_t*));
431      file->fs        = &fs_default;
432
433      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
434              spec->dev, spec->file, spec->part);
435
436      result = prom_get_devtype(spec->dev);
437      if (result > 0)
438           file->device_kind = result;
439      else
440           return result;
441
442      switch(file->device_kind) {
443      case FILE_DEVICE_BLOCK:
444           DEBUG_F("device is a block device\n");
445           return file_block_open(file, spec, spec->part);
446      case FILE_DEVICE_NET:
447           DEBUG_F("device is a network device\n");
448           return file_net_open(file, spec);
449      }
450      return 0;
451 }
452
453 /*
454  * Local variables:
455  * c-file-style: "k&r"
456  * c-basic-offset: 5
457  * End:
458  */