file_media.c revision 1.2 1 1.1 christos /*
2 1.1 christos * file_media.c -
3 1.1 christos *
4 1.1 christos * Written by Eryk Vershen
5 1.1 christos */
6 1.1 christos
7 1.1 christos /*
8 1.1 christos * Copyright 1997,1998 by Apple Computer, Inc.
9 1.1 christos * All Rights Reserved
10 1.1 christos *
11 1.1 christos * Permission to use, copy, modify, and distribute this software and
12 1.1 christos * its documentation for any purpose and without fee is hereby granted,
13 1.1 christos * provided that the above copyright notice appears in all copies and
14 1.1 christos * that both the copyright notice and this permission notice appear in
15 1.1 christos * supporting documentation.
16 1.1 christos *
17 1.1 christos * APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
18 1.1 christos * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 1.1 christos * FOR A PARTICULAR PURPOSE.
20 1.1 christos *
21 1.1 christos * IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
22 1.1 christos * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
23 1.1 christos * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
24 1.1 christos * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
25 1.1 christos * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 1.1 christos */
27 1.1 christos
28 1.1 christos // for printf()
29 1.1 christos #include <stdio.h>
30 1.1 christos // for malloc() & free()
31 1.1 christos #include <stdlib.h>
32 1.1 christos // for lseek(), read(), write(), close()
33 1.1 christos #include <unistd.h>
34 1.1 christos // for open()
35 1.1 christos #include <fcntl.h>
36 1.1 christos // for LONG_MAX
37 1.1 christos #include <limits.h>
38 1.1 christos // for errno
39 1.1 christos #include <errno.h>
40 1.1 christos
41 1.1 christos #ifdef __linux__
42 1.1 christos #include <sys/ioctl.h>
43 1.1 christos #include <linux/fs.h>
44 1.1 christos #include <linux/hdreg.h>
45 1.1 christos #include <sys/stat.h>
46 1.1 christos #else
47 1.1 christos #ifdef __unix__
48 1.1 christos #include <sys/ioctl.h>
49 1.1 christos #include <sys/stat.h>
50 1.1 christos #endif
51 1.1 christos #endif
52 1.1 christos
53 1.1 christos #include "file_media.h"
54 1.1 christos #include "errors.h"
55 1.1 christos
56 1.1 christos
57 1.1 christos /*
58 1.1 christos * Defines
59 1.1 christos */
60 1.1 christos #ifdef __linux__
61 1.1 christos #define LOFF_MAX 9223372036854775807LL
62 1.1 christos extern __loff_t llseek __P ((int __fd, __loff_t __offset, int __whence));
63 1.2 christos #elif defined(__NetBSD__) || defined(__APPLE__)
64 1.2 christos #define loff_t off_t
65 1.2 christos #define llseek lseek
66 1.2 christos #define LOFF_MAX LLONG_MAX
67 1.1 christos #else
68 1.1 christos #define loff_t long
69 1.1 christos #define llseek lseek
70 1.1 christos #define LOFF_MAX LONG_MAX
71 1.1 christos #endif
72 1.1 christos
73 1.1 christos
74 1.1 christos /*
75 1.1 christos * Types
76 1.1 christos */
77 1.1 christos typedef struct file_media *FILE_MEDIA;
78 1.1 christos
79 1.1 christos struct file_media {
80 1.1 christos struct media m;
81 1.1 christos int fd;
82 1.1 christos int regular_file;
83 1.1 christos };
84 1.1 christos
85 1.1 christos struct file_media_globals {
86 1.1 christos long exists;
87 1.1 christos long kind;
88 1.1 christos };
89 1.1 christos
90 1.1 christos typedef struct file_media_iterator *FILE_MEDIA_ITERATOR;
91 1.1 christos
92 1.1 christos struct file_media_iterator {
93 1.1 christos struct media_iterator m;
94 1.1 christos long style;
95 1.1 christos long index;
96 1.1 christos };
97 1.1 christos
98 1.1 christos
99 1.1 christos /*
100 1.1 christos * Global Constants
101 1.1 christos */
102 1.1 christos int potential_block_sizes[] = {
103 1.2 christos 1, 512, 1024, 2048, 4096, 8192, 16834,
104 1.1 christos 0
105 1.1 christos };
106 1.1 christos
107 1.1 christos enum {
108 1.1 christos kSCSI_Disks = 0,
109 1.1 christos kATA_Devices = 1,
110 1.1 christos kSCSI_CDs = 2,
111 1.1 christos kMaxStyle = 2
112 1.1 christos };
113 1.1 christos
114 1.1 christos
115 1.1 christos /*
116 1.1 christos * Global Variables
117 1.1 christos */
118 1.1 christos static long file_inited = 0;
119 1.1 christos static struct file_media_globals file_info;
120 1.1 christos
121 1.1 christos /*
122 1.1 christos * Forward declarations
123 1.1 christos */
124 1.1 christos int compute_block_size(int fd);
125 1.1 christos void file_init(void);
126 1.1 christos FILE_MEDIA new_file_media(void);
127 1.2 christos long read_file_media(MEDIA m, long long offset, uint32_t count, void *address);
128 1.2 christos long write_file_media(MEDIA m, long long offset, uint32_t count, void *address);
129 1.1 christos long close_file_media(MEDIA m);
130 1.1 christos long os_reload_file_media(MEDIA m);
131 1.1 christos FILE_MEDIA_ITERATOR new_file_iterator(void);
132 1.1 christos void reset_file_iterator(MEDIA_ITERATOR m);
133 1.1 christos char *step_file_iterator(MEDIA_ITERATOR m);
134 1.1 christos void delete_file_iterator(MEDIA_ITERATOR m);
135 1.1 christos
136 1.1 christos
137 1.1 christos /*
138 1.1 christos * Routines
139 1.1 christos */
140 1.1 christos void
141 1.1 christos file_init(void)
142 1.1 christos {
143 1.1 christos if (file_inited != 0) {
144 1.1 christos return;
145 1.1 christos }
146 1.1 christos file_inited = 1;
147 1.1 christos
148 1.1 christos file_info.kind = allocate_media_kind();
149 1.1 christos }
150 1.1 christos
151 1.1 christos
152 1.1 christos FILE_MEDIA
153 1.1 christos new_file_media(void)
154 1.1 christos {
155 1.1 christos return (FILE_MEDIA) new_media(sizeof(struct file_media));
156 1.1 christos }
157 1.1 christos
158 1.1 christos
159 1.1 christos int
160 1.1 christos compute_block_size(int fd)
161 1.1 christos {
162 1.1 christos int size;
163 1.1 christos int max_size;
164 1.1 christos loff_t x;
165 1.1 christos long t;
166 1.1 christos int i;
167 1.1 christos char *buffer;
168 1.1 christos
169 1.1 christos max_size = 0;
170 1.1 christos for (i = 0; ; i++) {
171 1.1 christos size = potential_block_sizes[i];
172 1.1 christos if (size == 0) {
173 1.1 christos break;
174 1.1 christos }
175 1.1 christos if (max_size < size) {
176 1.1 christos max_size = size;
177 1.1 christos }
178 1.1 christos }
179 1.1 christos
180 1.1 christos buffer = malloc(max_size);
181 1.1 christos if (buffer != 0) {
182 1.1 christos for (i = 0; ; i++) {
183 1.1 christos size = potential_block_sizes[i];
184 1.1 christos if (size == 0) {
185 1.1 christos break;
186 1.1 christos }
187 1.1 christos if ((x = llseek(fd, (loff_t)0, 0)) < 0) {
188 1.1 christos error(errno, "Can't seek on file");
189 1.1 christos break;
190 1.1 christos }
191 1.1 christos if ((t = read(fd, buffer, size)) == size) {
192 1.1 christos free(buffer);
193 1.1 christos return size;
194 1.1 christos }
195 1.1 christos }
196 1.1 christos }
197 1.1 christos return 0;
198 1.1 christos }
199 1.1 christos
200 1.1 christos
201 1.1 christos MEDIA
202 1.1 christos open_file_as_media(char *file, int oflag)
203 1.1 christos {
204 1.1 christos FILE_MEDIA a;
205 1.1 christos int fd;
206 1.1 christos loff_t off;
207 1.1 christos #if defined(__linux__) || defined(__unix__)
208 1.1 christos struct stat info;
209 1.1 christos #endif
210 1.1 christos
211 1.1 christos if (file_inited == 0) {
212 1.1 christos file_init();
213 1.1 christos }
214 1.1 christos
215 1.1 christos a = 0;
216 1.1 christos fd = open(file, oflag);
217 1.1 christos if (fd >= 0) {
218 1.1 christos a = new_file_media();
219 1.1 christos if (a != 0) {
220 1.1 christos a->m.kind = file_info.kind;
221 1.1 christos a->m.grain = compute_block_size(fd);
222 1.1 christos off = llseek(fd, (loff_t)0, 2); /* seek to end of media */
223 1.1 christos #if !defined(__linux__) && !defined(__unix__)
224 1.1 christos if (off <= 0) {
225 1.1 christos off = 1; /* XXX not right? */
226 1.1 christos }
227 1.1 christos #endif
228 1.1 christos //printf("file size = %Ld\n", off);
229 1.1 christos a->m.size_in_bytes = (long long) off;
230 1.1 christos a->m.do_read = read_file_media;
231 1.1 christos a->m.do_write = write_file_media;
232 1.1 christos a->m.do_close = close_file_media;
233 1.1 christos a->m.do_os_reload = os_reload_file_media;
234 1.1 christos a->fd = fd;
235 1.1 christos a->regular_file = 0;
236 1.1 christos #if defined(__linux__) || defined(__unix__)
237 1.1 christos if (fstat(fd, &info) < 0) {
238 1.1 christos error(errno, "can't stat file '%s'", file);
239 1.1 christos } else {
240 1.1 christos a->regular_file = S_ISREG(info.st_mode);
241 1.1 christos }
242 1.1 christos #endif
243 1.1 christos } else {
244 1.1 christos close(fd);
245 1.1 christos }
246 1.1 christos }
247 1.1 christos return (MEDIA) a;
248 1.1 christos }
249 1.1 christos
250 1.1 christos
251 1.1 christos long
252 1.2 christos read_file_media(MEDIA m, long long offset, uint32_t count, void *address)
253 1.1 christos {
254 1.1 christos FILE_MEDIA a;
255 1.1 christos long rtn_value;
256 1.1 christos loff_t off;
257 1.1 christos int t;
258 1.1 christos
259 1.1 christos a = (FILE_MEDIA) m;
260 1.1 christos rtn_value = 0;
261 1.1 christos if (a == 0) {
262 1.1 christos /* no media */
263 1.2 christos fprintf(stderr,"no media\n");
264 1.1 christos } else if (a->m.kind != file_info.kind) {
265 1.1 christos /* wrong kind - XXX need to error here - this is an internal problem */
266 1.2 christos fprintf(stderr,"wrong kind\n");
267 1.1 christos } else if (count <= 0 || count % a->m.grain != 0) {
268 1.1 christos /* can't handle size */
269 1.2 christos fprintf(stderr,"bad size\n");
270 1.1 christos } else if (offset < 0 || offset % a->m.grain != 0) {
271 1.1 christos /* can't handle offset */
272 1.2 christos fprintf(stderr,"bad offset\n");
273 1.2 christos } else if (offset + (long long) count > a->m.size_in_bytes && a->m.size_in_bytes != (long long) 0) {
274 1.1 christos /* check for offset (and offset+count) too large */
275 1.2 christos fprintf(stderr,"offset+count too large\n");
276 1.1 christos } else if (offset + count > (long long) LOFF_MAX) {
277 1.1 christos /* check for offset (and offset+count) too large */
278 1.2 christos fprintf(stderr,"offset+count too large 2\n");
279 1.1 christos } else {
280 1.1 christos /* do the read */
281 1.1 christos off = offset;
282 1.1 christos if ((off = llseek(a->fd, off, 0)) >= 0) {
283 1.2 christos if ((t = read(a->fd, address, count)) == (ssize_t)count) {
284 1.1 christos rtn_value = 1;
285 1.1 christos } else {
286 1.2 christos fprintf(stderr,"read failed\n");
287 1.1 christos }
288 1.1 christos } else {
289 1.2 christos fprintf(stderr,"lseek failed\n");
290 1.1 christos }
291 1.1 christos }
292 1.1 christos return rtn_value;
293 1.1 christos }
294 1.1 christos
295 1.1 christos
296 1.1 christos long
297 1.2 christos write_file_media(MEDIA m, long long offset, uint32_t count, void *address)
298 1.1 christos {
299 1.1 christos FILE_MEDIA a;
300 1.1 christos long rtn_value;
301 1.1 christos loff_t off;
302 1.1 christos int t;
303 1.1 christos
304 1.1 christos a = (FILE_MEDIA) m;
305 1.1 christos rtn_value = 0;
306 1.1 christos if (a == 0) {
307 1.1 christos /* no media */
308 1.1 christos } else if (a->m.kind != file_info.kind) {
309 1.1 christos /* wrong kind - XXX need to error here - this is an internal problem */
310 1.1 christos } else if (count <= 0 || count % a->m.grain != 0) {
311 1.1 christos /* can't handle size */
312 1.1 christos } else if (offset < 0 || offset % a->m.grain != 0) {
313 1.1 christos /* can't handle offset */
314 1.1 christos } else if (offset + count > (long long) LOFF_MAX) {
315 1.1 christos /* check for offset (and offset+count) too large */
316 1.1 christos } else {
317 1.1 christos /* do the write */
318 1.1 christos off = offset;
319 1.1 christos if ((off = llseek(a->fd, off, 0)) >= 0) {
320 1.2 christos if ((t = write(a->fd, address, count)) == (ssize_t)count) {
321 1.2 christos if (off + (long long) count > a->m.size_in_bytes) {
322 1.1 christos a->m.size_in_bytes = off + count;
323 1.1 christos }
324 1.1 christos rtn_value = 1;
325 1.1 christos }
326 1.1 christos }
327 1.1 christos }
328 1.1 christos return rtn_value;
329 1.1 christos }
330 1.1 christos
331 1.1 christos
332 1.1 christos long
333 1.1 christos close_file_media(MEDIA m)
334 1.1 christos {
335 1.1 christos FILE_MEDIA a;
336 1.1 christos
337 1.1 christos a = (FILE_MEDIA) m;
338 1.1 christos if (a == 0) {
339 1.1 christos return 0;
340 1.1 christos } else if (a->m.kind != file_info.kind) {
341 1.1 christos /* XXX need to error here - this is an internal problem */
342 1.1 christos return 0;
343 1.1 christos }
344 1.1 christos
345 1.1 christos close(a->fd);
346 1.1 christos return 1;
347 1.1 christos }
348 1.1 christos
349 1.1 christos
350 1.1 christos long
351 1.1 christos os_reload_file_media(MEDIA m)
352 1.1 christos {
353 1.1 christos FILE_MEDIA a;
354 1.1 christos long rtn_value;
355 1.1 christos #if defined(__linux__)
356 1.1 christos int i;
357 1.1 christos int saved_errno;
358 1.1 christos #endif
359 1.1 christos
360 1.1 christos a = (FILE_MEDIA) m;
361 1.1 christos rtn_value = 0;
362 1.1 christos if (a == 0) {
363 1.1 christos /* no media */
364 1.1 christos } else if (a->m.kind != file_info.kind) {
365 1.1 christos /* wrong kind - XXX need to error here - this is an internal problem */
366 1.1 christos } else if (a->regular_file) {
367 1.1 christos /* okay - nothing to do */
368 1.1 christos rtn_value = 1;
369 1.1 christos } else {
370 1.1 christos #ifdef __linux__
371 1.1 christos sync();
372 1.1 christos sleep(2);
373 1.1 christos if ((i = ioctl(a->fd, BLKRRPART)) != 0) {
374 1.1 christos saved_errno = errno;
375 1.1 christos } else {
376 1.1 christos // some kernel versions (1.2.x) seem to have trouble
377 1.1 christos // rereading the partition table, but if asked to do it
378 1.1 christos // twice, the second time works. - biro (at) yggdrasil.com */
379 1.1 christos sync();
380 1.1 christos sleep(2);
381 1.1 christos if ((i = ioctl(a->fd, BLKRRPART)) != 0) {
382 1.1 christos saved_errno = errno;
383 1.1 christos }
384 1.1 christos }
385 1.1 christos
386 1.1 christos // printf("Syncing disks.\n");
387 1.1 christos sync();
388 1.1 christos sleep(4); /* for sync() */
389 1.1 christos
390 1.1 christos if (i < 0) {
391 1.1 christos error(saved_errno, "Re-read of partition table failed");
392 1.1 christos printf("Reboot your system to ensure the "
393 1.1 christos "partition table is updated.\n");
394 1.1 christos }
395 1.1 christos #endif
396 1.1 christos rtn_value = 1;
397 1.1 christos }
398 1.1 christos return rtn_value;
399 1.1 christos }
400 1.1 christos
401 1.1 christos
402 1.1 christos #if !defined(__linux__) && !defined(__unix__)
403 1.1 christos #pragma mark -
404 1.1 christos #endif
405 1.1 christos
406 1.1 christos
407 1.1 christos FILE_MEDIA_ITERATOR
408 1.1 christos new_file_iterator(void)
409 1.1 christos {
410 1.1 christos return (FILE_MEDIA_ITERATOR) new_media_iterator(sizeof(struct file_media_iterator));
411 1.1 christos }
412 1.1 christos
413 1.1 christos
414 1.1 christos MEDIA_ITERATOR
415 1.1 christos create_file_iterator(void)
416 1.1 christos {
417 1.1 christos FILE_MEDIA_ITERATOR a;
418 1.1 christos
419 1.1 christos if (file_inited == 0) {
420 1.1 christos file_init();
421 1.1 christos }
422 1.1 christos
423 1.1 christos a = new_file_iterator();
424 1.1 christos if (a != 0) {
425 1.1 christos a->m.kind = file_info.kind;
426 1.1 christos a->m.state = kInit;
427 1.1 christos a->m.do_reset = reset_file_iterator;
428 1.1 christos a->m.do_step = step_file_iterator;
429 1.1 christos a->m.do_delete = delete_file_iterator;
430 1.1 christos a->style = 0;
431 1.1 christos a->index = 0;
432 1.1 christos }
433 1.1 christos
434 1.1 christos return (MEDIA_ITERATOR) a;
435 1.1 christos }
436 1.1 christos
437 1.1 christos
438 1.1 christos void
439 1.1 christos reset_file_iterator(MEDIA_ITERATOR m)
440 1.1 christos {
441 1.1 christos FILE_MEDIA_ITERATOR a;
442 1.1 christos
443 1.1 christos a = (FILE_MEDIA_ITERATOR) m;
444 1.1 christos if (a == 0) {
445 1.1 christos /* no media */
446 1.1 christos } else if (a->m.kind != file_info.kind) {
447 1.1 christos /* wrong kind - XXX need to error here - this is an internal problem */
448 1.1 christos } else if (a->m.state != kInit) {
449 1.1 christos a->m.state = kReset;
450 1.1 christos }
451 1.1 christos }
452 1.1 christos
453 1.1 christos
454 1.1 christos char *
455 1.1 christos step_file_iterator(MEDIA_ITERATOR m)
456 1.1 christos {
457 1.1 christos FILE_MEDIA_ITERATOR a;
458 1.1 christos char *result;
459 1.1 christos struct stat info;
460 1.1 christos int fd;
461 1.1 christos int bump;
462 1.1 christos int value;
463 1.1 christos
464 1.1 christos a = (FILE_MEDIA_ITERATOR) m;
465 1.1 christos if (a == 0) {
466 1.1 christos /* no media */
467 1.1 christos } else if (a->m.kind != file_info.kind) {
468 1.1 christos /* wrong kind - XXX need to error here - this is an internal problem */
469 1.1 christos } else {
470 1.1 christos switch (a->m.state) {
471 1.1 christos case kInit:
472 1.1 christos a->m.state = kReset;
473 1.1 christos /* fall through to reset */
474 1.1 christos case kReset:
475 1.1 christos a->style = 0 /* first style */;
476 1.1 christos a->index = 0 /* first index */;
477 1.1 christos a->m.state = kIterating;
478 1.1 christos /* fall through to iterate */
479 1.1 christos case kIterating:
480 1.1 christos while (1) {
481 1.1 christos if (a->style > kMaxStyle) {
482 1.1 christos break;
483 1.1 christos }
484 1.1 christos #ifndef notdef
485 1.1 christos /* if old version of mklinux then skip CD drive */
486 1.1 christos if (a->style == kSCSI_Disks && a->index == 3) {
487 1.1 christos a->index += 1;
488 1.1 christos }
489 1.1 christos #endif
490 1.1 christos /* generate result */
491 1.1 christos result = (char *) malloc(20);
492 1.1 christos if (result != NULL) {
493 1.1 christos /*
494 1.1 christos * for DR3 we should actually iterate through:
495 1.1 christos *
496 1.1 christos * /dev/sd[a...] # first missing is end of list
497 1.1 christos * /dev/hd[a...] # may be holes in sequence
498 1.1 christos * /dev/scd[0...] # first missing is end of list
499 1.1 christos *
500 1.1 christos * and stop in each group when either a stat of
501 1.1 christos * the name fails or if an open fails for
502 1.1 christos * particular reasons.
503 1.1 christos */
504 1.1 christos bump = 0;
505 1.1 christos value = (int) a->index;
506 1.1 christos switch (a->style) {
507 1.1 christos case kSCSI_Disks:
508 1.1 christos if (value < 26) {
509 1.2 christos snprintf(result, 20, "/dev/sd%c", 'a'+value);
510 1.1 christos } else if (value < 676) {
511 1.2 christos snprintf(result, 20, "/dev/sd%c%c",
512 1.1 christos 'a' + value / 26,
513 1.1 christos 'a' + value % 26);
514 1.1 christos } else {
515 1.1 christos bump = -1;
516 1.1 christos }
517 1.1 christos break;
518 1.1 christos case kATA_Devices:
519 1.1 christos if (value < 26) {
520 1.2 christos snprintf(result, 20, "/dev/hd%c", 'a'+value);
521 1.1 christos } else {
522 1.1 christos bump = -1;
523 1.1 christos }
524 1.1 christos break;
525 1.1 christos case kSCSI_CDs:
526 1.1 christos if (value < 10) {
527 1.2 christos snprintf(result, 20, "/dev/scd%c", '0'+value);
528 1.1 christos } else {
529 1.1 christos bump = -1;
530 1.1 christos }
531 1.1 christos break;
532 1.1 christos }
533 1.1 christos if (bump != 0) {
534 1.1 christos // already set don't even check
535 1.1 christos } else if (stat(result, &info) < 0) {
536 1.1 christos bump = 1;
537 1.1 christos } else if ((fd = open(result, O_RDONLY)) >= 0) {
538 1.1 christos close(fd);
539 1.1 christos #if defined(__linux__) || defined(__unix__)
540 1.1 christos } else if (errno == ENXIO || errno == ENODEV) {
541 1.1 christos if (a->style == kATA_Devices) {
542 1.1 christos bump = -1;
543 1.1 christos } else {
544 1.1 christos bump = 1;
545 1.1 christos }
546 1.1 christos #endif
547 1.1 christos }
548 1.1 christos if (bump) {
549 1.1 christos if (bump > 0) {
550 1.1 christos a->style += 1; /* next style */
551 1.1 christos a->index = 0; /* first index again */
552 1.1 christos } else {
553 1.1 christos a->index += 1; /* next index */
554 1.1 christos }
555 1.1 christos free(result);
556 1.1 christos continue;
557 1.1 christos }
558 1.1 christos }
559 1.1 christos
560 1.1 christos a->index += 1; /* next index */
561 1.1 christos return result;
562 1.1 christos }
563 1.1 christos a->m.state = kEnd;
564 1.1 christos /* fall through to end */
565 1.1 christos case kEnd:
566 1.1 christos default:
567 1.1 christos break;
568 1.1 christos }
569 1.1 christos }
570 1.1 christos return 0 /* no entry */;
571 1.1 christos }
572 1.1 christos
573 1.1 christos
574 1.1 christos void
575 1.1 christos delete_file_iterator(MEDIA_ITERATOR m)
576 1.1 christos {
577 1.1 christos return;
578 1.1 christos }
579