Initial import of binutils 2.22 on the new vendor branch
[dragonfly.git] / contrib / lvm2 / dist / tools / lvresize.c
1 /*      $NetBSD: lvresize.c,v 1.1.1.3 2009/12/02 00:25:53 haad Exp $    */
2
3 /*
4  * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
5  * Copyright (C) 2004-2009 Red Hat, Inc. All rights reserved.
6  *
7  * This file is part of LVM2.
8  *
9  * This copyrighted material is made available to anyone wishing to use,
10  * modify, copy, or redistribute it subject to the terms and conditions
11  * of the GNU Lesser General Public License v.2.1.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, write to the Free Software Foundation,
15  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17
18 #include "tools.h"
19
20 #define SIZE_BUF 128
21
22 struct lvresize_params {
23         const char *vg_name;
24         const char *lv_name;
25
26         uint32_t stripes;
27         uint32_t stripe_size;
28         uint32_t mirrors;
29
30         const struct segment_type *segtype;
31
32         /* size */
33         uint32_t extents;
34         uint64_t size;
35         sign_t sign;
36         percent_t percent;
37
38         enum {
39                 LV_ANY = 0,
40                 LV_REDUCE = 1,
41                 LV_EXTEND = 2
42         } resize;
43
44         int resizefs;
45         int nofsck;
46
47         int argc;
48         char **argv;
49 };
50
51 static int _validate_stripesize(struct cmd_context *cmd,
52                                 const struct volume_group *vg,
53                                 struct lvresize_params *lp)
54 {
55         if (arg_sign_value(cmd, stripesize_ARG, 0) == SIGN_MINUS) {
56                 log_error("Stripesize may not be negative.");
57                 return 0;
58         }
59
60         if (arg_uint_value(cmd, stripesize_ARG, 0) > STRIPE_SIZE_LIMIT * 2) {
61                 log_error("Stripe size cannot be larger than %s",
62                           display_size(cmd, (uint64_t) STRIPE_SIZE_LIMIT));
63                 return 0;
64         }
65
66         if (!(vg->fid->fmt->features & FMT_SEGMENTS))
67                 log_warn("Varied stripesize not supported. Ignoring.");
68         else if (arg_uint_value(cmd, stripesize_ARG, 0) > vg->extent_size * 2) {
69                 log_error("Reducing stripe size %s to maximum, "
70                           "physical extent size %s",
71                           display_size(cmd,
72                                        (uint64_t) arg_uint_value(cmd, stripesize_ARG, 0)),
73                           display_size(cmd, (uint64_t) vg->extent_size));
74                 lp->stripe_size = vg->extent_size;
75         } else
76                 lp->stripe_size = arg_uint_value(cmd, stripesize_ARG, 0);
77
78         if (lp->mirrors) {
79                 log_error("Mirrors and striping cannot be combined yet.");
80                 return 0;
81         }
82         if (lp->stripe_size & (lp->stripe_size - 1)) {
83                 log_error("Stripe size must be power of 2");
84                 return 0;
85         }
86
87         return 1;
88 }
89
90 static int _request_confirmation(struct cmd_context *cmd,
91                                  const struct volume_group *vg,
92                                  const struct logical_volume *lv,
93                                  const struct lvresize_params *lp)
94 {
95         struct lvinfo info;
96
97         memset(&info, 0, sizeof(info));
98
99         if (!lv_info(cmd, lv, &info, 1, 0) && driver_version(NULL, 0)) {
100                 log_error("lv_info failed: aborting");
101                 return 0;
102         }
103
104         if (lp->resizefs) {
105                 if (!info.exists) {
106                         log_error("Logical volume %s must be activated "
107                                   "before resizing filesystem", lp->lv_name);
108                         return 0;
109                 }
110                 return 1;
111         }
112
113         if (!info.exists)
114                 return 1;
115
116         log_warn("WARNING: Reducing active%s logical volume to %s",
117                  info.open_count ? " and open" : "",
118                  display_size(cmd, (uint64_t) lp->extents * vg->extent_size));
119
120         log_warn("THIS MAY DESTROY YOUR DATA (filesystem etc.)");
121
122         if (!arg_count(cmd, force_ARG)) {
123                 if (yes_no_prompt("Do you really want to reduce %s? [y/n]: ",
124                                   lp->lv_name) == 'n') {
125                         log_print("Logical volume %s NOT reduced", lp->lv_name);
126                         return 0;
127                 }
128                 if (sigint_caught())
129                         return 0;
130         }
131
132         return 1;
133 }
134
135 enum fsadm_cmd_e { FSADM_CMD_CHECK, FSADM_CMD_RESIZE };
136 #define FSADM_CMD "fsadm"
137 #define FSADM_CMD_MAX_ARGS 6
138
139 /*
140  * FSADM_CMD --dry-run --verbose --force check lv_path
141  * FSADM_CMD --dry-run --verbose --force resize lv_path size
142  */
143 static int _fsadm_cmd(struct cmd_context *cmd,
144                       const struct volume_group *vg,
145                       const struct lvresize_params *lp,
146                       enum fsadm_cmd_e fcmd)
147 {
148         char lv_path[PATH_MAX];
149         char size_buf[SIZE_BUF];
150         const char *argv[FSADM_CMD_MAX_ARGS + 2];
151         unsigned i = 0;
152
153         argv[i++] = FSADM_CMD;
154
155         if (test_mode())
156                 argv[i++] = "--dry-run";
157
158         if (verbose_level() >= _LOG_NOTICE)
159                 argv[i++] = "--verbose";
160
161         if (arg_count(cmd, force_ARG))
162                 argv[i++] = "--force";
163
164         argv[i++] = (fcmd == FSADM_CMD_RESIZE) ? "resize" : "check";
165
166         if (dm_snprintf(lv_path, PATH_MAX, "%s%s/%s", cmd->dev_dir, lp->vg_name,
167                         lp->lv_name) < 0) {
168                 log_error("Couldn't create LV path for %s", lp->lv_name);
169                 return 0;
170         }
171
172         argv[i++] = lv_path;
173
174         if (fcmd == FSADM_CMD_RESIZE) {
175                 if (dm_snprintf(size_buf, SIZE_BUF, "%" PRIu64 "K",
176                                 (uint64_t) lp->extents * vg->extent_size / 2) < 0) {
177                         log_error("Couldn't generate new LV size string");
178                         return 0;
179                 }
180
181                 argv[i++] = size_buf;
182         }
183
184         argv[i] = NULL;
185
186         return exec_cmd(cmd, argv);
187 }
188
189 static int _lvresize_params(struct cmd_context *cmd, int argc, char **argv,
190                             struct lvresize_params *lp)
191 {
192         const char *cmd_name;
193         char *st;
194         unsigned dev_dir_found = 0;
195
196         lp->sign = SIGN_NONE;
197         lp->resize = LV_ANY;
198
199         cmd_name = command_name(cmd);
200         if (!strcmp(cmd_name, "lvreduce"))
201                 lp->resize = LV_REDUCE;
202         if (!strcmp(cmd_name, "lvextend"))
203                 lp->resize = LV_EXTEND;
204
205         /*
206          * Allow omission of extents and size if the user has given us
207          * one or more PVs.  Most likely, the intent was "resize this
208          * LV the best you can with these PVs"
209          */
210         if ((arg_count(cmd, extents_ARG) + arg_count(cmd, size_ARG) == 0) &&
211             (argc >= 2)) {
212                 lp->extents = 100;
213                 lp->percent = PERCENT_PVS;
214                 lp->sign = SIGN_PLUS;
215         } else if ((arg_count(cmd, extents_ARG) +
216                     arg_count(cmd, size_ARG) != 1)) {
217                 log_error("Please specify either size or extents but not "
218                           "both.");
219                 return 0;
220         }
221
222         if (arg_count(cmd, extents_ARG)) {
223                 lp->extents = arg_uint_value(cmd, extents_ARG, 0);
224                 lp->sign = arg_sign_value(cmd, extents_ARG, SIGN_NONE);
225                 lp->percent = arg_percent_value(cmd, extents_ARG, PERCENT_NONE);
226         }
227
228         /* Size returned in kilobyte units; held in sectors */
229         if (arg_count(cmd, size_ARG)) {
230                 lp->size = arg_uint64_value(cmd, size_ARG, UINT64_C(0));
231                 lp->sign = arg_sign_value(cmd, size_ARG, SIGN_NONE);
232                 lp->percent = PERCENT_NONE;
233         }
234
235         if (lp->resize == LV_EXTEND && lp->sign == SIGN_MINUS) {
236                 log_error("Negative argument not permitted - use lvreduce");
237                 return 0;
238         }
239
240         if (lp->resize == LV_REDUCE && lp->sign == SIGN_PLUS) {
241                 log_error("Positive sign not permitted - use lvextend");
242                 return 0;
243         }
244
245         lp->resizefs = arg_is_set(cmd, resizefs_ARG);
246         lp->nofsck = arg_is_set(cmd, nofsck_ARG);
247
248         if (!argc) {
249                 log_error("Please provide the logical volume name");
250                 return 0;
251         }
252
253         lp->lv_name = argv[0];
254         argv++;
255         argc--;
256
257         if (!(lp->lv_name = skip_dev_dir(cmd, lp->lv_name, &dev_dir_found)) ||
258             !(lp->vg_name = extract_vgname(cmd, lp->lv_name))) {
259                 log_error("Please provide a volume group name");
260                 return 0;
261         }
262
263         if (!validate_name(lp->vg_name)) {
264                 log_error("Volume group name %s has invalid characters",
265                           lp->vg_name);
266                 return 0;
267         }
268
269         if ((st = strrchr(lp->lv_name, '/')))
270                 lp->lv_name = st + 1;
271
272         lp->argc = argc;
273         lp->argv = argv;
274
275         return 1;
276 }
277
278 static int _lvresize(struct cmd_context *cmd, struct volume_group *vg,
279                      struct lvresize_params *lp)
280 {
281         struct logical_volume *lv;
282         struct lvinfo info;
283         uint32_t stripesize_extents = 0;
284         uint32_t seg_stripes = 0, seg_stripesize = 0, seg_size = 0;
285         uint32_t seg_mirrors = 0;
286         uint32_t extents_used = 0;
287         uint32_t size_rest;
288         uint32_t pv_extent_count = 0;
289         alloc_policy_t alloc;
290         struct logical_volume *lock_lv;
291         struct lv_list *lvl;
292         struct lv_segment *seg;
293         uint32_t seg_extents;
294         uint32_t sz, str;
295         struct dm_list *pvh = NULL;
296
297         /* does LV exist? */
298         if (!(lvl = find_lv_in_vg(vg, lp->lv_name))) {
299                 log_error("Logical volume %s not found in volume group %s",
300                           lp->lv_name, lp->vg_name);
301                 return ECMD_FAILED;
302         }
303
304         if (arg_count(cmd, stripes_ARG)) {
305                 if (vg->fid->fmt->features & FMT_SEGMENTS)
306                         lp->stripes = arg_uint_value(cmd, stripes_ARG, 1);
307                 else
308                         log_warn("Varied striping not supported. Ignoring.");
309         }
310
311         if (arg_count(cmd, mirrors_ARG)) {
312                 if (vg->fid->fmt->features & FMT_SEGMENTS)
313                         lp->mirrors = arg_uint_value(cmd, mirrors_ARG, 1) + 1;
314                 else
315                         log_warn("Mirrors not supported. Ignoring.");
316                 if (arg_sign_value(cmd, mirrors_ARG, 0) == SIGN_MINUS) {
317                         log_error("Mirrors argument may not be negative");
318                         return EINVALID_CMD_LINE;
319                 }
320         }
321
322         if (arg_count(cmd, stripesize_ARG) &&
323             !_validate_stripesize(cmd, vg, lp))
324                 return EINVALID_CMD_LINE;
325
326         lv = lvl->lv;
327
328         if (lv->status & LOCKED) {
329                 log_error("Can't resize locked LV %s", lv->name);
330                 return ECMD_FAILED;
331         }
332
333         if (lv->status & CONVERTING) {
334                 log_error("Can't resize %s while lvconvert in progress", lv->name);
335                 return ECMD_FAILED;
336         }
337
338         alloc = arg_uint_value(cmd, alloc_ARG, lv->alloc);
339
340         if (lp->size) {
341                 if (lp->size % vg->extent_size) {
342                         if (lp->sign == SIGN_MINUS)
343                                 lp->size -= lp->size % vg->extent_size;
344                         else
345                                 lp->size += vg->extent_size -
346                                     (lp->size % vg->extent_size);
347
348                         log_print("Rounding up size to full physical extent %s",
349                                   display_size(cmd, (uint64_t) lp->size));
350                 }
351
352                 lp->extents = lp->size / vg->extent_size;
353         }
354
355         if (!(pvh = lp->argc ? create_pv_list(cmd->mem, vg, lp->argc,
356                                                      lp->argv, 1) : &vg->pvs)) {
357                 stack;
358                 return ECMD_FAILED;
359         }
360
361         switch(lp->percent) {
362                 case PERCENT_VG:
363                         lp->extents = lp->extents * vg->extent_count / 100;
364                         break;
365                 case PERCENT_FREE:
366                         lp->extents = lp->extents * vg->free_count / 100;
367                         break;
368                 case PERCENT_LV:
369                         lp->extents = lp->extents * lv->le_count / 100;
370                         break;
371                 case PERCENT_PVS:
372                         if (lp->argc) {
373                                 pv_extent_count = pv_list_extents_free(pvh);
374                                 lp->extents = lp->extents * pv_extent_count / 100;
375                         } else
376                                 lp->extents = lp->extents * vg->extent_count / 100;
377                         break;
378                 case PERCENT_NONE:
379                         break;
380         }
381
382         if (lp->sign == SIGN_PLUS)
383                 lp->extents += lv->le_count;
384
385         if (lp->sign == SIGN_MINUS) {
386                 if (lp->extents >= lv->le_count) {
387                         log_error("Unable to reduce %s below 1 extent",
388                                   lp->lv_name);
389                         return EINVALID_CMD_LINE;
390                 }
391
392                 lp->extents = lv->le_count - lp->extents;
393         }
394
395         if (!lp->extents) {
396                 log_error("New size of 0 not permitted");
397                 return EINVALID_CMD_LINE;
398         }
399
400         if (lp->extents == lv->le_count) {
401                 if (!lp->resizefs) {
402                         log_error("New size (%d extents) matches existing size "
403                                   "(%d extents)", lp->extents, lv->le_count);
404                         return EINVALID_CMD_LINE;
405                 }
406                 lp->resize = LV_EXTEND; /* lets pretend zero size extension */
407         }
408
409         seg_size = lp->extents - lv->le_count;
410
411         /* Use segment type of last segment */
412         dm_list_iterate_items(seg, &lv->segments) {
413                 lp->segtype = seg->segtype;
414         }
415
416         /* FIXME Support LVs with mixed segment types */
417         if (lp->segtype != arg_ptr_value(cmd, type_ARG, lp->segtype)) {
418                 log_error("VolumeType does not match (%s)", lp->segtype->name);
419                 return EINVALID_CMD_LINE;
420         }
421
422         /* If extending, find stripes, stripesize & size of last segment */
423         if ((lp->extents > lv->le_count) &&
424             !(lp->stripes == 1 || (lp->stripes > 1 && lp->stripe_size))) {
425                 dm_list_iterate_items(seg, &lv->segments) {
426                         if (!seg_is_striped(seg))
427                                 continue;
428
429                         sz = seg->stripe_size;
430                         str = seg->area_count;
431
432                         if ((seg_stripesize && seg_stripesize != sz &&
433                              !lp->stripe_size) ||
434                             (seg_stripes && seg_stripes != str && !lp->stripes)) {
435                                 log_error("Please specify number of "
436                                           "stripes (-i) and stripesize (-I)");
437                                 return EINVALID_CMD_LINE;
438                         }
439
440                         seg_stripesize = sz;
441                         seg_stripes = str;
442                 }
443
444                 if (!lp->stripes)
445                         lp->stripes = seg_stripes;
446
447                 if (!lp->stripe_size && lp->stripes > 1) {
448                         if (seg_stripesize) {
449                                 log_print("Using stripesize of last segment %s",
450                                           display_size(cmd, (uint64_t) seg_stripesize));
451                                 lp->stripe_size = seg_stripesize;
452                         } else {
453                                 lp->stripe_size =
454                                         find_config_tree_int(cmd,
455                                                         "metadata/stripesize",
456                                                         DEFAULT_STRIPESIZE) * 2;
457                                 log_print("Using default stripesize %s",
458                                           display_size(cmd, (uint64_t) lp->stripe_size));
459                         }
460                 }
461         }
462
463         /* If extending, find mirrors of last segment */
464         if ((lp->extents > lv->le_count)) {
465                 dm_list_iterate_back_items(seg, &lv->segments) {
466                         if (seg_is_mirrored(seg))
467                                 seg_mirrors = lv_mirror_count(seg->lv);
468                         else
469                                 seg_mirrors = 0;
470                         break;
471                 }
472                 if (!arg_count(cmd, mirrors_ARG) && seg_mirrors) {
473                         log_print("Extending %" PRIu32 " mirror images.",
474                                   seg_mirrors);
475                         lp->mirrors = seg_mirrors;
476                 }
477                 if ((arg_count(cmd, mirrors_ARG) || seg_mirrors) &&
478                     (lp->mirrors != seg_mirrors)) {
479                         log_error("Cannot vary number of mirrors in LV yet.");
480                         return EINVALID_CMD_LINE;
481                 }
482         }
483
484         /* If reducing, find stripes, stripesize & size of last segment */
485         if (lp->extents < lv->le_count) {
486                 extents_used = 0;
487
488                 if (lp->stripes || lp->stripe_size || lp->mirrors)
489                         log_error("Ignoring stripes, stripesize and mirrors "
490                                   "arguments when reducing");
491
492                 dm_list_iterate_items(seg, &lv->segments) {
493                         seg_extents = seg->len;
494
495                         if (seg_is_striped(seg)) {
496                                 seg_stripesize = seg->stripe_size;
497                                 seg_stripes = seg->area_count;
498                         }
499
500                         if (seg_is_mirrored(seg))
501                                 seg_mirrors = lv_mirror_count(seg->lv);
502                         else
503                                 seg_mirrors = 0;
504
505                         if (lp->extents <= extents_used + seg_extents)
506                                 break;
507
508                         extents_used += seg_extents;
509                 }
510
511                 seg_size = lp->extents - extents_used;
512                 lp->stripe_size = seg_stripesize;
513                 lp->stripes = seg_stripes;
514                 lp->mirrors = seg_mirrors;
515         }
516
517         if (lp->stripes > 1 && !lp->stripe_size) {
518                 log_error("Stripesize for striped segment should not be 0!");
519                 return EINVALID_CMD_LINE;
520         }
521
522         if ((lp->stripes > 1)) {
523                 if (!(stripesize_extents = lp->stripe_size / vg->extent_size))
524                         stripesize_extents = 1;
525
526                 if ((size_rest = seg_size % (lp->stripes * stripesize_extents))) {
527                         log_print("Rounding size (%d extents) down to stripe "
528                                   "boundary size for segment (%d extents)",
529                                   lp->extents, lp->extents - size_rest);
530                         lp->extents = lp->extents - size_rest;
531                 }
532
533                 if (lp->stripe_size < STRIPE_SIZE_MIN) {
534                         log_error("Invalid stripe size %s",
535                                   display_size(cmd, (uint64_t) lp->stripe_size));
536                         return EINVALID_CMD_LINE;
537                 }
538         }
539
540         if (lp->extents < lv->le_count) {
541                 if (lp->resize == LV_EXTEND) {
542                         log_error("New size given (%d extents) not larger "
543                                   "than existing size (%d extents)",
544                                   lp->extents, lv->le_count);
545                         return EINVALID_CMD_LINE;
546                 }
547                 lp->resize = LV_REDUCE;
548         } else if (lp->extents > lv->le_count) {
549                 if (lp->resize == LV_REDUCE) {
550                         log_error("New size given (%d extents) not less than "
551                                   "existing size (%d extents)", lp->extents,
552                                   lv->le_count);
553                         return EINVALID_CMD_LINE;
554                 }
555                 lp->resize = LV_EXTEND;
556         }
557
558         if (lv_is_origin(lv)) {
559                 if (lp->resize == LV_REDUCE) {
560                         log_error("Snapshot origin volumes cannot be reduced "
561                                   "in size yet.");
562                         return ECMD_FAILED;
563                 }
564
565                 memset(&info, 0, sizeof(info));
566
567                 if (lv_info(cmd, lv, &info, 0, 0) && info.exists) {
568                         log_error("Snapshot origin volumes can be resized "
569                                   "only while inactive: try lvchange -an");
570                         return ECMD_FAILED;
571                 }
572         }
573
574         if ((lp->resize == LV_REDUCE) && lp->argc)
575                 log_warn("Ignoring PVs on command line when reducing");
576
577         /* Request confirmation before operations that are often mistakes. */
578         if ((lp->resizefs || (lp->resize == LV_REDUCE)) &&
579             !_request_confirmation(cmd, vg, lv, lp)) {
580                 stack;
581                 return ECMD_FAILED;
582         }
583
584         if (lp->resizefs) {
585                 if (!lp->nofsck &&
586                     !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_CHECK)) {
587                         stack;
588                         return ECMD_FAILED;
589                 }
590
591                 if ((lp->resize == LV_REDUCE) &&
592                     !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_RESIZE)) {
593                         stack;
594                         return ECMD_FAILED;
595                 }
596         }
597
598         if (!archive(vg)) {
599                 stack;
600                 return ECMD_FAILED;
601         }
602
603         log_print("%sing logical volume %s to %s",
604                   (lp->resize == LV_REDUCE) ? "Reduc" : "Extend",
605                   lp->lv_name,
606                   display_size(cmd, (uint64_t) lp->extents * vg->extent_size));
607
608         if (lp->resize == LV_REDUCE) {
609                 if (!lv_reduce(lv, lv->le_count - lp->extents)) {
610                         stack;
611                         return ECMD_FAILED;
612                 }
613         } else if ((lp->extents > lv->le_count) && /* Ensure we extend */
614                    !lv_extend(lv, lp->segtype, lp->stripes,
615                               lp->stripe_size, lp->mirrors,
616                               lp->extents - lv->le_count,
617                               NULL, 0u, 0u, pvh, alloc)) {
618                 stack;
619                 return ECMD_FAILED;
620         }
621
622         /* store vg on disk(s) */
623         if (!vg_write(vg)) {
624                 stack;
625                 return ECMD_FAILED;
626         }
627
628         /* If snapshot, must suspend all associated devices */
629         if (lv_is_cow(lv))
630                 lock_lv = origin_from_cow(lv);
631         else
632                 lock_lv = lv;
633
634         if (!suspend_lv(cmd, lock_lv)) {
635                 log_error("Failed to suspend %s", lp->lv_name);
636                 vg_revert(vg);
637                 backup(vg);
638                 return ECMD_FAILED;
639         }
640
641         if (!vg_commit(vg)) {
642                 stack;
643                 resume_lv(cmd, lock_lv);
644                 backup(vg);
645                 return ECMD_FAILED;
646         }
647
648         if (!resume_lv(cmd, lock_lv)) {
649                 log_error("Problem reactivating %s", lp->lv_name);
650                 backup(vg);
651                 return ECMD_FAILED;
652         }
653
654         backup(vg);
655
656         log_print("Logical volume %s successfully resized", lp->lv_name);
657
658         if (lp->resizefs && (lp->resize == LV_EXTEND) &&
659             !_fsadm_cmd(cmd, vg, lp, FSADM_CMD_RESIZE)) {
660                 stack;
661                 return ECMD_FAILED;
662         }
663
664         return ECMD_PROCESSED;
665 }
666
667 int lvresize(struct cmd_context *cmd, int argc, char **argv)
668 {
669         struct lvresize_params lp;
670         struct volume_group *vg;
671         int r;
672
673         memset(&lp, 0, sizeof(lp));
674
675         if (!_lvresize_params(cmd, argc, argv, &lp))
676                 return EINVALID_CMD_LINE;
677
678         log_verbose("Finding volume group %s", lp.vg_name);
679         vg = vg_read_for_update(cmd, lp.vg_name, NULL, 0);
680         if (vg_read_error(vg)) {
681                 vg_release(vg);
682                 stack;
683                 return ECMD_FAILED;
684         }
685
686         if (!(r = _lvresize(cmd, vg, &lp)))
687                 stack;
688
689         unlock_and_release_vg(cmd, vg, lp.vg_name);
690
691         return r;
692 }