xref: /aosp_15_r20/external/vboot_reference/futility/updater_archive.c (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1 /* Copyright 2018 The ChromiumOS Authors
2  * Use of this source code is governed by a BSD-style license that can be
3  * found in the LICENSE file.
4  *
5  * Accessing updater resources from an archive.
6  */
7 
8 #include <assert.h>
9 #include <errno.h>
10 #if defined(__OpenBSD__)
11 #include <sys/types.h>
12 #endif
13 #include <fts.h>
14 #include <sys/stat.h>
15 #include <sys/time.h>
16 #include <unistd.h>
17 
18 #ifdef HAVE_LIBARCHIVE
19 #include <archive.h>
20 #include <archive_entry.h>
21 #endif
22 
23 #ifdef HAVE_LIBZIP
24 #ifndef __clang__
25 /* If libzip headers were built for Clang but later get included with GCC you
26    need this. This check should really be in libzip but apparently they think
27    it's fine to ship compiler-specific system headers or something... */
28 #define _Nullable
29 #define _Nonnull
30 #endif
31 #include <zip.h>
32 #endif
33 
34 #include "host_misc.h"
35 #include "updater.h"
36 
37 /*
38  * A firmware update package (archive) is a file packed by either shar(1) or
39  * zip(1). See https://chromium.googlesource.com/chromiumos/platform/firmware/
40  * for more information.
41  */
42 
43 struct u_archive {
44 	void *handle;
45 
46 	void * (*open)(const char *name);
47 	int (*close)(void *handle);
48 
49 	int (*walk)(void *handle, void *arg,
50 		    int (*callback)(const char *path, void *arg));
51 	int (*has_entry)(void *handle, const char *name);
52 	int (*read_file)(void *handle, const char *fname,
53 			 uint8_t **data, uint32_t *size, int64_t *mtime);
54 	int (*write_file)(void *handle, const char *fname,
55 			  uint8_t *data, uint32_t size, int64_t mtime);
56 };
57 
58 /*
59  * -- The fallback driver (using general file system). --
60  */
61 
62 /* Callback for archive_open on a general file system. */
archive_fallback_open(const char * name)63 static void *archive_fallback_open(const char *name)
64 {
65 	assert(name && *name);
66 	return strdup(name);
67 }
68 
69 /* Callback for archive_close on a general file system. */
archive_fallback_close(void * handle)70 static int archive_fallback_close(void *handle)
71 {
72 	free(handle);
73 	return 0;
74 }
75 
76 /* Callback for archive_walk on a general file system. */
archive_fallback_walk(void * handle,void * arg,int (* callback)(const char * path,void * arg))77 static int archive_fallback_walk(
78 		void *handle, void *arg,
79 		int (*callback)(const char *path, void *arg))
80 {
81 	FTS *fts_handle;
82 	FTSENT *ent;
83 	char *fts_argv[2] = {};
84 	char default_path[] = ".";
85 	char *root = default_path;
86 	size_t root_len;
87 
88 	if (handle)
89 		root = (char *)handle;
90 	root_len = strlen(root);
91 	fts_argv[0] = root;
92 
93 	fts_handle = fts_open(fts_argv, FTS_NOCHDIR, NULL);
94 	if (!fts_handle)
95 		return -1;
96 
97 	while ((ent = fts_read(fts_handle)) != NULL) {
98 		char *path = ent->fts_path + root_len;
99 		if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
100 			continue;
101 		while (*path == '/')
102 			path++;
103 		if (!*path)
104 			continue;
105 		if (callback(path, arg))
106 			break;
107 	}
108 	return 0;
109 }
110 
111 /* Callback for fallback drivers to get full path easily. */
archive_fallback_get_path(void * handle,const char * fname,char ** temp_path)112 static const char *archive_fallback_get_path(void *handle, const char *fname,
113 					     char **temp_path)
114 {
115 	if (handle && *fname != '/') {
116 		ASPRINTF(temp_path, "%s/%s", (char *)handle, fname);
117 		return *temp_path;
118 	}
119 	return fname;
120 }
121 
122 /* Callback for archive_has_entry on a general file system. */
archive_fallback_has_entry(void * handle,const char * fname)123 static int archive_fallback_has_entry(void *handle, const char *fname)
124 {
125 	int r;
126 	char *temp_path = NULL;
127 	const char *path = archive_fallback_get_path(handle, fname, &temp_path);
128 
129 	VB2_DEBUG("Checking %s\n", path);
130 	r = access(path, R_OK);
131 	free(temp_path);
132 	return r == 0;
133 }
134 
135 /* Callback for archive_read_file on a general file system. */
archive_fallback_read_file(void * handle,const char * fname,uint8_t ** data,uint32_t * size,int64_t * mtime)136 static int archive_fallback_read_file(void *handle, const char *fname,
137 				      uint8_t **data, uint32_t *size, int64_t *mtime)
138 {
139 	int r;
140 	char *temp_path = NULL;
141 	const char *path = archive_fallback_get_path(handle, fname, &temp_path);
142 	struct stat st;
143 
144 	VB2_DEBUG("Reading %s\n", path);
145 	*data = NULL;
146 	*size = 0;
147 	/* vb2_read_file already has an extra '\0' in the end. */
148 	r = vb2_read_file(path, data, size) != VB2_SUCCESS;
149 	if (mtime) {
150 		if (stat(path, &st) == 0)
151 			*mtime = st.st_mtime;
152 		else
153 			WARN("Unable to stat %s: %s\n", path, strerror(errno));
154 	}
155 	free(temp_path);
156 	return r;
157 }
158 
159 /* Callback for archive_write_file on a general file system. */
archive_fallback_write_file(void * handle,const char * fname,uint8_t * data,uint32_t size,int64_t mtime)160 static int archive_fallback_write_file(void *handle, const char *fname,
161 				       uint8_t *data, uint32_t size, int64_t mtime)
162 {
163 	int r;
164 	char *temp_path = NULL;
165 	const char *path = archive_fallback_get_path(handle, fname, &temp_path);
166 
167 	VB2_DEBUG("Writing %s\n", path);
168 	if (strchr(path, '/')) {
169 		char *dirname = strdup(path);
170 		*strrchr(dirname, '/') = '\0';
171 		/* TODO(hungte): call mkdir(2) instead of shell invocation. */
172 		if (access(dirname, W_OK) != 0) {
173 			char *command;
174 			ASPRINTF(&command, "mkdir -p %s", dirname);
175 			free(host_shell(command));
176 			free(command);
177 		}
178 		free(dirname);
179 	}
180 	r = vb2_write_file(path, data, size) != VB2_SUCCESS;
181 	if (mtime) {
182 		struct timeval times[2] = {
183 			{.tv_sec = mtime, .tv_usec = 0},
184 			{.tv_sec = mtime, .tv_usec = 0},
185 		};
186 		if (utimes(path, times) != 0)
187 			WARN("Unable to set times on %s: %s\n", path, strerror(errno));
188 	}
189 	free(temp_path);
190 	return r;
191 }
192 
193 /*
194  * -- The cache driver (used by other drivers). --
195  */
196 
197 #ifdef HAVE_LIBARCHIVE
198 
199 /*
200  * For stream-based archives (e.g., tar+gz) we want to create a cache for
201  * storing the names and contents for later processing.
202  */
203 struct archive_cache {
204 	char *name;
205 	uint8_t *data;
206 	int64_t mtime;
207 	size_t size;
208 	int has_data;
209 	struct archive_cache *next;
210 };
211 
212 /* Add a new cache node to an existing cache list and return the new head. */
archive_cache_new(struct archive_cache * cache,const char * name)213 static struct archive_cache *archive_cache_new(struct archive_cache *cache,
214 					       const char *name)
215 {
216 	struct archive_cache *c;
217 
218 	c = (struct archive_cache *)calloc(sizeof(*c), 1);
219 	if (!c)
220 		return NULL;
221 
222 	c->name = strdup(name);
223 	if (!c->name) {
224 		free(c);
225 		return NULL;
226 	}
227 
228 	c->next = cache;
229 	return c;
230 }
231 
232 /* Find and return an entry (by name) from the cache. */
archive_cache_find(struct archive_cache * c,const char * name)233 static struct archive_cache *archive_cache_find(struct archive_cache *c,
234 						const char *name)
235 {
236 	for (; c; c = c->next) {
237 		assert(c->name);
238 		if (!strcmp(c->name, name))
239 			return c;
240 	}
241 	return NULL;
242 }
243 
244 /* Callback for archive_walk to process all entries in the cache. */
archive_cache_walk(struct archive_cache * c,void * arg,int (* callback)(const char * name,void * arg))245 static int archive_cache_walk(
246 		struct archive_cache *c, void *arg,
247 		int (*callback)(const char *name, void *arg))
248 {
249 	for (; c; c = c->next) {
250 		assert(c->name);
251 		if (callback(c->name, arg))
252 			break;
253 	}
254 	return 0;
255 }
256 
257 /* Delete all entries in the cache. */
archive_cache_free(struct archive_cache * c)258 static void *archive_cache_free(struct archive_cache *c)
259 {
260 	struct archive_cache *next;
261 
262 	while (c) {
263 		next = c->next;
264 		free(c->name);
265 		free(c->data);
266 		free(c);
267 		c = next;
268 	}
269 	return NULL;
270 }
271 
272 /*
273  * -- The libarchive driver (multiple formats but very slow). --
274  */
275 
276 enum {
277 	FILTER_IGNORE,
278 	FILTER_ABORT,
279 	FILTER_NAME_ONLY,
280 	FILTER_READ_ALL,
281 };
282 
libarchive_read_file_entries(const char * fpath,int (* filter)(struct archive_entry * entry))283 static struct archive_cache *libarchive_read_file_entries(
284 		const char *fpath, int (*filter)(struct archive_entry *entry))
285 {
286 	struct archive *a = archive_read_new();
287 	struct archive_entry *entry;
288 	struct archive_cache *c, *cache = NULL;
289 	int r;
290 
291 	assert(a);
292 	archive_read_support_filter_all(a);
293 	archive_read_support_format_all(a);
294 	r = archive_read_open_filename(a, fpath, 10240);
295 	if (r != ARCHIVE_OK) {
296 		ERROR("Failed parsing archive using libarchive: %s\n", fpath);
297 		archive_read_free(a);
298 		return NULL;
299 	}
300 
301 	WARN("Loading data from archive: %s ", fpath);
302 	while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
303 		fputc('.', stderr);
304 		if (archive_entry_filetype(entry) != AE_IFREG)
305 			continue;
306 		if (filter)
307 			r = filter(entry);
308 		else
309 			r = FILTER_READ_ALL;
310 
311 		if (r == FILTER_ABORT)
312 			break;
313 		if (r == FILTER_IGNORE)
314 			continue;
315 
316 		c = archive_cache_new(cache, archive_entry_pathname(entry));
317 		if (!c) {
318 			ERROR("Internal error: out of memory.\n");
319 			archive_cache_free(cache);
320 			archive_read_free(a);
321 			return NULL;
322 		}
323 		cache = c;
324 
325 		if (r == FILTER_NAME_ONLY)
326 			continue;
327 
328 		assert(r == FILTER_READ_ALL);
329 		c->size = archive_entry_size(entry);
330 		c->mtime = archive_entry_mtime(entry);
331 		c->data = (uint8_t *)calloc(1, c->size + 1);
332 		if (!c->data) {
333 			WARN("Out of memory when loading: %s\n", c->name);
334 			continue;
335 		}
336 		if (archive_read_data(a, c->data, c->size) != c->size) {
337 			WARN("Failed reading from archive: %s\n", c->name);
338 			continue;
339 		}
340 		c->has_data = 1;
341 	}
342 	fputs("\r\n", stderr);  /* Flush the '.' */
343 	VB2_DEBUG("Finished loading from archive: %s.\n", fpath);
344 
345 	archive_read_free(a);
346 	return cache;
347 }
348 
349 /* Callback for archive_open on an ARCHIVE file. */
archive_libarchive_open(const char * name)350 static void *archive_libarchive_open(const char *name)
351 {
352 	/*
353 	 * The firmware archives today can usually all load into memory
354 	 * so we are using a NULL filter. Change that to a specific list in
355 	 * future if the /build/$BOARD/firmware archive becomes too large.
356 	 */
357 	return libarchive_read_file_entries(name, NULL);
358 }
359 
360 /* Callback for archive_close on an ARCHIVE file. */
archive_libarchive_close(void * handle)361 static int archive_libarchive_close(void *handle)
362 {
363 	archive_cache_free(handle);
364 	return 0;
365 }
366 
367 /* Callback for archive_has_entry on an ARCHIVE file. */
archive_libarchive_has_entry(void * handle,const char * fname)368 static int archive_libarchive_has_entry(void *handle, const char *fname)
369 {
370 	return archive_cache_find(handle, fname) != NULL;
371 }
372 
373 /* Callback for archive_walk on an ARCHIVE file. */
archive_libarchive_walk(void * handle,void * arg,int (* callback)(const char * name,void * arg))374 static int archive_libarchive_walk(
375 		void *handle, void *arg,
376 		int (*callback)(const char *name, void *arg))
377 {
378 	return archive_cache_walk(handle, arg, callback);
379 }
380 
381 /* Callback for archive_read_file on an ARCHIVE file. */
archive_libarchive_read_file(void * handle,const char * fname,uint8_t ** data,uint32_t * size,int64_t * mtime)382 static int archive_libarchive_read_file(
383 		void *handle, const char *fname, uint8_t **data,
384 		uint32_t *size, int64_t *mtime)
385 {
386 	struct archive_cache *c = archive_cache_find(handle, fname);
387 
388 	if (!c)
389 		return 1;
390 
391 	if (!c->has_data) {
392 		/* TODO(hungte) Re-read. */
393 		ERROR("Not in the cache: %s\n", fname);
394 		return 1;
395 	}
396 
397 	if (mtime)
398 		*mtime = c->mtime;
399 	if (size)
400 		*size = c->size;
401 	*data = (uint8_t *)malloc(c->size + 1);
402 	if (!*data) {
403 		ERROR("Out of memory when reading: %s\n", c->name);
404 		return 1;
405 	}
406 	memcpy(*data, c->data, c->size);
407 	(*data)[c->size] = '\0';
408 	return 0;
409 }
410 
411 /* Callback for archive_write_file on an ARCHIVE file. */
archive_libarchive_write_file(void * handle,const char * fname,uint8_t * data,uint32_t size,int64_t mtime)412 static int archive_libarchive_write_file(
413 		void *handle, const char *fname, uint8_t *data, uint32_t size,
414 		int64_t mtime)
415 {
416 	ERROR("Not implemented!\n");
417 	return 1;
418 }
419 #endif
420 
421 /*
422  * -- The libzip driver (for ZIP, the official format for CrOS fw updater). --
423  */
424 
425 #ifdef HAVE_LIBZIP
426 
427 /* Callback for archive_open on a ZIP file. */
archive_zip_open(const char * name)428 static void *archive_zip_open(const char *name)
429 {
430 	return zip_open(name, 0, NULL);
431 }
432 
433 /* Callback for archive_close on a ZIP file. */
archive_zip_close(void * handle)434 static int archive_zip_close(void *handle)
435 {
436 	struct zip *zip = (struct zip *)handle;
437 
438 	if (zip)
439 		return zip_close(zip);
440 	return 0;
441 }
442 
443 /* Callback for archive_has_entry on a ZIP file. */
archive_zip_has_entry(void * handle,const char * fname)444 static int archive_zip_has_entry(void *handle, const char *fname)
445 {
446 	struct zip *zip = (struct zip *)handle;
447 	assert(zip);
448 	return zip_name_locate(zip, fname, 0) != -1;
449 }
450 
451 /* Callback for archive_walk on a ZIP file. */
archive_zip_walk(void * handle,void * arg,int (* callback)(const char * name,void * arg))452 static int archive_zip_walk(
453 		void *handle, void *arg,
454 		int (*callback)(const char *name, void *arg))
455 {
456 	zip_int64_t num, i;
457 	struct zip *zip = (struct zip *)handle;
458 	assert(zip);
459 
460 	num = zip_get_num_entries(zip, 0);
461 	if (num < 0)
462 		return 1;
463 	for (i = 0; i < num; i++) {
464 		const char *name = zip_get_name(zip, i, 0);
465 		if (*name && name[strlen(name) - 1] == '/')
466 			continue;
467 		if (callback(name, arg))
468 			break;
469 	}
470 	return 0;
471 }
472 
473 /* Callback for archive_zip_read_file on a ZIP file. */
archive_zip_read_file(void * handle,const char * fname,uint8_t ** data,uint32_t * size,int64_t * mtime)474 static int archive_zip_read_file(void *handle, const char *fname,
475 			     uint8_t **data, uint32_t *size, int64_t *mtime)
476 {
477 	struct zip *zip = (struct zip *)handle;
478 	struct zip_file *fp;
479 	struct zip_stat stat;
480 
481 	assert(zip);
482 	*data = NULL;
483 	*size = 0;
484 	zip_stat_init(&stat);
485 	if (zip_stat(zip, fname, 0, &stat)) {
486 		ERROR("Fail to stat entry in ZIP: %s\n", fname);
487 		return 1;
488 	}
489 	fp = zip_fopen(zip, fname, 0);
490 	if (!fp) {
491 		ERROR("Failed to open entry in ZIP: %s\n", fname);
492 		return 1;
493 	}
494 	*data = (uint8_t *)malloc(stat.size + 1);
495 	if (*data) {
496 		if (zip_fread(fp, *data, stat.size) == stat.size) {
497 			if (mtime)
498 				*mtime = stat.mtime;
499 			*size = stat.size;
500 			(*data)[stat.size] = '\0';
501 		} else {
502 			ERROR("Failed to read entry in zip: %s\n", fname);
503 			free(*data);
504 			*data = NULL;
505 		}
506 	}
507 	zip_fclose(fp);
508 	return *data == NULL;
509 }
510 
511 /* Callback for archive_zip_write_file on a ZIP file. */
archive_zip_write_file(void * handle,const char * fname,uint8_t * data,uint32_t size,int64_t mtime)512 static int archive_zip_write_file(void *handle, const char *fname,
513 				  uint8_t *data, uint32_t size, int64_t mtime)
514 {
515 	struct zip *zip = (struct zip *)handle;
516 	struct zip_source *src;
517 
518 	VB2_DEBUG("Writing %s\n", fname);
519 	assert(zip);
520 	src = zip_source_buffer(zip, data, size, 0);
521 	if (!src) {
522 		ERROR("Internal error: cannot allocate buffer: %s\n", fname);
523 		return 1;
524 	}
525 
526 	if (zip_file_add(zip, fname, src, ZIP_FL_OVERWRITE) < 0) {
527 		zip_source_free(src);
528 		ERROR("Internal error: failed to add: %s\n", fname);
529 		return 1;
530 	}
531 	/* zip_source_free is not needed if zip_file_add success. */
532 #if LIBZIP_VERSION_MAJOR >= 1
533 	zip_file_set_mtime(zip, zip_name_locate(zip, fname, 0), mtime, 0);
534 #endif
535 	return 0;
536 }
537 #endif
538 
539 /*
540  * -- The public functions for using u_archive. --
541  */
542 
archive_open(const char * path)543 struct u_archive *archive_open(const char *path)
544 {
545 	struct stat path_stat;
546 	struct u_archive *ar;
547 
548 	if (stat(path, &path_stat) != 0) {
549 		ERROR("Cannot identify type of path: %s\n", path);
550 		return NULL;
551 	}
552 
553 	ar = (struct u_archive *)calloc(sizeof(*ar), 1);
554 	if (!ar) {
555 		ERROR("Internal error: allocation failure.\n");
556 		return NULL;
557 	}
558 
559 	if (S_ISDIR(path_stat.st_mode)) {
560 		VB2_DEBUG("Found directory, use fallback (fs) driver: %s\n",
561 			  path);
562 		/* Regular file system. */
563 		ar->open = archive_fallback_open;
564 		ar->close = archive_fallback_close;
565 		ar->walk = archive_fallback_walk;
566 		ar->has_entry = archive_fallback_has_entry;
567 		ar->read_file = archive_fallback_read_file;
568 		ar->write_file = archive_fallback_write_file;
569 	}
570 
571 	/* Format detection must try ZIP (the official format) first. */
572 #ifdef HAVE_LIBZIP
573 	if (!ar->open) {
574 		ar->handle = archive_zip_open(path);
575 
576 		if (ar->handle) {
577 			VB2_DEBUG("Found a ZIP file: %s\n", path);
578 			ar->open = archive_zip_open;
579 			ar->close = archive_zip_close;
580 			ar->walk = archive_zip_walk;
581 			ar->has_entry = archive_zip_has_entry;
582 			ar->read_file = archive_zip_read_file;
583 			ar->write_file = archive_zip_write_file;
584 		}
585 	}
586 #endif
587 
588 	/* LIBARCHIVE must be the last driver. */
589 #ifdef HAVE_LIBARCHIVE
590 	if (!ar->open) {
591 		VB2_DEBUG("Found a file, use libarchive: %s\n", path);
592 		ar->open = archive_libarchive_open;
593 		ar->close = archive_libarchive_close;
594 		ar->walk = archive_libarchive_walk;
595 		ar->has_entry = archive_libarchive_has_entry;
596 		ar->read_file = archive_libarchive_read_file;
597 		ar->write_file = archive_libarchive_write_file;
598 	}
599 #endif
600 
601 	if (!ar->open) {
602 		ERROR("Found a file, but no drivers were selected: %s\n", path);
603 		free(ar);
604 		return NULL;
605 	}
606 
607 	/* Some drivers may have already opened the archive. */
608 	if (!ar->handle)
609 		ar->handle = ar->open(path);
610 
611 	if (!ar->handle) {
612 		ERROR("Failed to open archive: %s\n", path);
613 		free(ar);
614 		return NULL;
615 	}
616 	return ar;
617 }
618 
archive_close(struct u_archive * ar)619 int archive_close(struct u_archive *ar)
620 {
621 	int r = ar->close(ar->handle);
622 	free(ar);
623 	return r;
624 }
625 
archive_has_entry(struct u_archive * ar,const char * name)626 int archive_has_entry(struct u_archive *ar, const char *name)
627 {
628 	if (!ar || *name == '/')
629 		return archive_fallback_has_entry(NULL, name);
630 	return ar->has_entry(ar->handle, name);
631 }
632 
archive_walk(struct u_archive * ar,void * arg,int (* callback)(const char * path,void * arg))633 int archive_walk(struct u_archive *ar, void *arg,
634 		 int (*callback)(const char *path, void *arg))
635 {
636 	if (!ar)
637 		return archive_fallback_walk(NULL, arg, callback);
638 	return ar->walk(ar->handle, arg, callback);
639 }
640 
archive_read_file(struct u_archive * ar,const char * fname,uint8_t ** data,uint32_t * size,int64_t * mtime)641 int archive_read_file(struct u_archive *ar, const char *fname,
642 		      uint8_t **data, uint32_t *size, int64_t *mtime)
643 {
644 	if (!ar || *fname == '/')
645 		return archive_fallback_read_file(NULL, fname, data, size, mtime);
646 	return ar->read_file(ar->handle, fname, data, size, mtime);
647 }
648 
archive_write_file(struct u_archive * ar,const char * fname,uint8_t * data,uint32_t size,int64_t mtime)649 int archive_write_file(struct u_archive *ar, const char *fname,
650 		       uint8_t *data, uint32_t size, int64_t mtime)
651 {
652 	if (!ar || *fname == '/')
653 		return archive_fallback_write_file(NULL, fname, data, size, mtime);
654 	return ar->write_file(ar->handle, fname, data, size, mtime);
655 }
656 
657 struct _copy_arg {
658 	struct u_archive *from, *to;
659 };
660 
661 /* Callback for archive_copy. */
archive_copy_callback(const char * path,void * _arg)662 static int archive_copy_callback(const char *path, void *_arg)
663 {
664 	const struct _copy_arg *arg = (const struct _copy_arg*)_arg;
665 	uint32_t size;
666 	uint8_t *data;
667 	int64_t mtime;
668 	int r;
669 
670 	INFO("Copying: %s\n", path);
671 	if (archive_read_file(arg->from, path, &data, &size, &mtime)) {
672 		ERROR("Failed reading: %s\n", path);
673 		return 1;
674 	}
675 	r = archive_write_file(arg->to, path, data, size, mtime);
676 	VB2_DEBUG("result=%d\n", r);
677 	free(data);
678 	return r;
679 }
680 
archive_copy(struct u_archive * from,struct u_archive * to)681 int archive_copy(struct u_archive *from, struct u_archive *to)
682 {
683 	struct _copy_arg arg = { .from = from, .to = to };
684 	return archive_walk(from, &arg, archive_copy_callback);
685 }
686