Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 1 | /* |
| 2 | * badblocks.c - Bad blocks checker |
| 3 | * |
| 4 | * Copyright (C) 1992, 1993, 1994 Remy Card <card@masi.ibp.fr> |
| 5 | * Laboratoire MASI, Institut Blaise Pascal |
| 6 | * Universite Pierre et Marie Curie (Paris VI) |
| 7 | * |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 8 | * Copyright 1995, 1996, 1997 by Theodore Ts'o |
| 9 | * |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 10 | * This file is based on the minix file system programs fsck and mkfs |
| 11 | * written and copyrighted by Linus Torvalds <Linus.Torvalds@cs.helsinki.fi> |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 12 | * |
| 13 | * %Begin-Header% |
| 14 | * This file may be redistributed under the terms of the GNU Public |
| 15 | * License. |
| 16 | * %End-Header% |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 17 | */ |
| 18 | |
| 19 | /* |
| 20 | * History: |
| 21 | * 93/05/26 - Creation from e2fsck |
| 22 | * 94/02/27 - Made a separate bad blocks checker |
| 23 | */ |
| 24 | |
| 25 | #include <errno.h> |
| 26 | #include <fcntl.h> |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 27 | #ifdef HAVE_GETOPT_H |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 28 | #include <getopt.h> |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 29 | #endif |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 30 | #include <signal.h> |
| 31 | #include <stdio.h> |
| 32 | #include <stdlib.h> |
| 33 | #include <string.h> |
| 34 | #include <unistd.h> |
| 35 | |
| 36 | #include <sys/ioctl.h> |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 37 | #include <sys/types.h> |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 38 | |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 39 | #if HAVE_LINUX_FS_H |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 40 | #include <linux/fd.h> |
| 41 | #include <linux/fs.h> |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 42 | #endif |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 43 | |
| 44 | #include "et/com_err.h" |
Theodore Ts'o | d40259f | 1997-10-20 00:44:26 +0000 | [diff] [blame] | 45 | #include "ext2fs/ext2_io.h" |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 46 | |
| 47 | const char * program_name = "badblocks"; |
| 48 | |
| 49 | int v_flag = 0; /* verbose */ |
| 50 | int w_flag = 0; /* do r/w test */ |
| 51 | int s_flag = 0; /* show progress of test */ |
| 52 | |
| 53 | static volatile void usage (void) |
| 54 | { |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 55 | fprintf (stderr, "Usage: %s [-b block_size] [-o output_file] [-svw] device blocks_count\n [start_count]\n", |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 56 | program_name); |
| 57 | exit (1); |
| 58 | } |
| 59 | |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 60 | static unsigned long currently_testing = 0; |
| 61 | static unsigned long num_blocks = 0; |
| 62 | |
| 63 | static void print_status(void) |
| 64 | { |
| 65 | fprintf(stderr, "%9ld/%9ld", currently_testing, num_blocks); |
| 66 | fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); |
| 67 | fflush (stderr); |
| 68 | } |
| 69 | |
| 70 | static void alarm_intr (int alnum) |
| 71 | { |
| 72 | signal (SIGALRM, alarm_intr); |
| 73 | alarm(1); |
| 74 | if (!num_blocks) |
| 75 | return; |
| 76 | fprintf(stderr, "%9ld/%9ld", currently_testing, num_blocks); |
| 77 | fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); |
| 78 | fflush (stderr); |
| 79 | } |
| 80 | |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 81 | /* |
| 82 | * Perform a test of a block; return the number of blocks readable/writeable. |
| 83 | */ |
| 84 | static long do_test (int dev, char * buffer, int try, unsigned long block_size, |
| 85 | unsigned long current_block) |
| 86 | { |
| 87 | long got; |
| 88 | |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 89 | if (v_flag > 1) |
| 90 | print_status(); |
| 91 | |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 92 | /* Seek to the correct loc. */ |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 93 | if (ext2fs_llseek (dev, (ext2_loff_t) current_block * block_size, |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 94 | SEEK_SET) != (ext2_loff_t) current_block * block_size) |
| 95 | com_err (program_name, errno, "during seek"); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 96 | |
| 97 | /* Try the read */ |
| 98 | got = read (dev, buffer, try * block_size); |
| 99 | if (got < 0) |
| 100 | got = 0; |
| 101 | if (got & (block_size - 1)) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 102 | fprintf (stderr, |
| 103 | "Weird value (%ld) in do_test: probably bugs\n", |
| 104 | got); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 105 | got /= block_size; |
| 106 | return got; |
| 107 | } |
| 108 | |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 109 | static void flush_bufs (int dev, int sync) |
| 110 | { |
| 111 | if (v_flag |
| 112 | #if !defined (BLKFLSBUF) && !defined (FDFLUSH) |
| 113 | && sync |
| 114 | #endif |
| 115 | ) |
| 116 | fprintf (stderr, "Flushing buffers\n"); |
| 117 | |
| 118 | if (sync && fsync (dev) == -1) |
| 119 | com_err (program_name, errno, "during fsync"); |
| 120 | |
| 121 | #ifdef BLKLSBUF |
| 122 | ioctl (dev, BLKFLSBUF, 0); /* In case this is a HD */ |
| 123 | #endif |
| 124 | #ifdef FDFLUSH |
| 125 | ioctl (dev, FDFLUSH, 0); /* In case this is floppy */ |
| 126 | #endif |
| 127 | } |
| 128 | |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 129 | static void test_ro (int dev, unsigned long blocks_count, |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 130 | unsigned long block_size, FILE * out, |
| 131 | unsigned long from_count) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 132 | { |
| 133 | #define TEST_BUFFER_BLOCKS 16 |
| 134 | char * blkbuf; |
| 135 | int try; |
| 136 | long got; |
| 137 | |
| 138 | blkbuf = malloc (TEST_BUFFER_BLOCKS * block_size); |
| 139 | if (!blkbuf) |
| 140 | { |
| 141 | com_err (program_name, ENOMEM, "while allocating buffers"); |
| 142 | exit (1); |
| 143 | } |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 144 | flush_bufs (dev, 0); |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 145 | if (v_flag) { |
| 146 | fprintf (stderr, |
| 147 | "Checking for bad blocks in read-only mode\n"); |
| 148 | fprintf (stderr, "From block %lu to %lu\n", from_count, blocks_count); |
| 149 | } |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 150 | try = TEST_BUFFER_BLOCKS; |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 151 | currently_testing = from_count; |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 152 | num_blocks = blocks_count; |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 153 | if (s_flag || v_flag > 1) { |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 154 | fprintf(stderr, "Checking for bad blocks (read-only test): "); |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 155 | if (v_flag <= 1) |
| 156 | alarm_intr(SIGALRM); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 157 | } |
| 158 | while (currently_testing < blocks_count) |
| 159 | { |
| 160 | if (currently_testing + try > blocks_count) |
| 161 | try = blocks_count - currently_testing; |
| 162 | got = do_test (dev, blkbuf, try, block_size, currently_testing); |
| 163 | currently_testing += got; |
| 164 | if (got == try) { |
| 165 | try = TEST_BUFFER_BLOCKS; |
| 166 | continue; |
| 167 | } |
| 168 | else |
| 169 | try = 1; |
| 170 | if (got == 0) |
| 171 | fprintf (out, "%lu\n", currently_testing++); |
| 172 | } |
| 173 | num_blocks = 0; |
| 174 | alarm(0); |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 175 | if (s_flag || v_flag > 1) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 176 | fprintf(stderr, "done \n"); |
| 177 | fflush (stderr); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 178 | free (blkbuf); |
| 179 | } |
| 180 | |
| 181 | static void test_rw (int dev, unsigned long blocks_count, |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 182 | unsigned long block_size, FILE * out, |
| 183 | unsigned long from_count) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 184 | { |
| 185 | int i; |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 186 | char * buffer; |
| 187 | unsigned char pattern[] = {0xaa, 0x55, 0xff, 0x00}; |
| 188 | |
| 189 | buffer = malloc (2 * block_size); |
| 190 | if (!buffer) |
| 191 | { |
| 192 | com_err (program_name, ENOMEM, "while allocating buffers"); |
| 193 | exit (1); |
| 194 | } |
| 195 | |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 196 | flush_bufs (dev, 0); |
| 197 | |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 198 | if (v_flag) { |
| 199 | fprintf(stderr, |
| 200 | "Checking for bad blocks in read-write mode\n"); |
| 201 | fprintf(stderr, "From block %lu to %lu\n", |
| 202 | from_count, blocks_count); |
| 203 | } |
| 204 | for (i = 0; i < sizeof (pattern); i++) { |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 205 | memset (buffer, pattern[i], block_size); |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 206 | if (s_flag | v_flag) |
| 207 | fprintf (stderr, "Writing pattern 0x%08x: ", |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 208 | *((int *) buffer)); |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 209 | num_blocks = blocks_count; |
| 210 | currently_testing = from_count; |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 211 | if (s_flag && v_flag <= 1) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 212 | alarm_intr(SIGALRM); |
| 213 | for (; |
| 214 | currently_testing < blocks_count; |
| 215 | currently_testing++) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 216 | { |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 217 | if (ext2fs_llseek (dev, (ext2_loff_t) currently_testing * |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 218 | block_size, SEEK_SET) != |
| 219 | (ext2_loff_t) currently_testing * block_size) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 220 | com_err (program_name, errno, |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 221 | "during seek on block %d", |
| 222 | currently_testing); |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 223 | if (v_flag > 1) |
| 224 | print_status(); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 225 | write (dev, buffer, block_size); |
| 226 | } |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 227 | num_blocks = 0; |
| 228 | alarm (0); |
| 229 | if (s_flag | v_flag) |
| 230 | fprintf(stderr, "done \n"); |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 231 | flush_bufs (dev, 1); |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 232 | if (s_flag | v_flag) |
| 233 | fprintf (stderr, "Reading and comparing: "); |
| 234 | num_blocks = blocks_count; |
| 235 | currently_testing = from_count; |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 236 | if (s_flag && v_flag <= 1) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 237 | alarm_intr(SIGALRM); |
| 238 | for (; |
| 239 | currently_testing < blocks_count; |
| 240 | currently_testing++) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 241 | { |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 242 | if (ext2fs_llseek (dev, (ext2_loff_t) currently_testing * |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 243 | block_size, SEEK_SET) != |
| 244 | (ext2_loff_t) currently_testing * block_size) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 245 | com_err (program_name, errno, |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 246 | "during seek on block %d", |
| 247 | currently_testing); |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 248 | if (v_flag > 1) |
| 249 | print_status(); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 250 | if (read (dev, buffer + block_size, block_size) < block_size) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 251 | fprintf (out, "%ld\n", currently_testing); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 252 | else if (memcmp (buffer, buffer + block_size, block_size)) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 253 | fprintf (out, "%ld\n", currently_testing); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 254 | } |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 255 | num_blocks = 0; |
| 256 | alarm (0); |
| 257 | if (s_flag | v_flag) |
| 258 | fprintf(stderr, "done \n"); |
Theodore Ts'o | a418d3a | 1997-04-26 14:00:26 +0000 | [diff] [blame] | 259 | flush_bufs (dev, 0); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 260 | } |
| 261 | } |
| 262 | |
Theodore Ts'o | 00e5433 | 1997-09-16 02:13:52 +0000 | [diff] [blame] | 263 | int main (int argc, char ** argv) |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 264 | { |
Theodore Ts'o | 519149f | 1997-10-25 03:49:49 +0000 | [diff] [blame^] | 265 | int c; |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 266 | char * tmp; |
| 267 | char * device_name; |
| 268 | char * output_file = NULL; |
| 269 | FILE * out; |
| 270 | unsigned long block_size = 1024; |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 271 | unsigned long blocks_count, from_count; |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 272 | int dev; |
| 273 | |
| 274 | setbuf(stdout, NULL); |
| 275 | setbuf(stderr, NULL); |
| 276 | if (argc && *argv) |
| 277 | program_name = *argv; |
| 278 | while ((c = getopt (argc, argv, "b:o:svw")) != EOF) { |
| 279 | switch (c) { |
| 280 | case 'b': |
| 281 | block_size = strtoul (optarg, &tmp, 0); |
| 282 | if (*tmp || block_size > 4096) { |
| 283 | com_err (program_name, 0, |
| 284 | "bad block size - %s", optarg); |
| 285 | exit (1); |
| 286 | } |
| 287 | break; |
| 288 | case 'o': |
| 289 | output_file = optarg; |
| 290 | break; |
| 291 | case 's': |
| 292 | s_flag = 1; |
| 293 | break; |
| 294 | case 'v': |
Theodore Ts'o | 19c78dc | 1997-04-29 16:17:09 +0000 | [diff] [blame] | 295 | v_flag++; |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 296 | break; |
| 297 | case 'w': |
| 298 | w_flag = 1; |
| 299 | break; |
| 300 | default: |
| 301 | usage (); |
| 302 | } |
| 303 | } |
| 304 | if (optind > argc - 1) |
| 305 | usage (); |
| 306 | device_name = argv[optind++]; |
| 307 | if (optind > argc - 1) |
| 308 | usage (); |
| 309 | blocks_count = strtoul (argv[optind], &tmp, 0); |
| 310 | if (*tmp) |
| 311 | { |
| 312 | com_err (program_name, 0, "bad blocks count - %s", argv[optind]); |
| 313 | exit (1); |
| 314 | } |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 315 | if (++optind <= argc-1) { |
| 316 | from_count = strtoul (argv[optind], &tmp, 0); |
| 317 | } else from_count = 0; |
| 318 | if (from_count >= blocks_count) { |
| 319 | com_err (program_name, 0, "bad blocks range: %lu-%lu", |
| 320 | from_count, blocks_count); |
| 321 | exit (1); |
| 322 | } |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 323 | dev = open (device_name, w_flag ? O_RDWR : O_RDONLY); |
| 324 | if (dev == -1) |
| 325 | { |
| 326 | com_err (program_name, errno,"while trying to open %s", |
| 327 | device_name); |
| 328 | exit (1); |
| 329 | } |
| 330 | if (output_file && strcmp (output_file, "-") != 0) |
| 331 | { |
| 332 | out = fopen (output_file, "w"); |
| 333 | if (out == NULL) |
| 334 | { |
| 335 | com_err (program_name, errno,"while trying to open %s", |
| 336 | device_name); |
| 337 | exit (1); |
| 338 | } |
| 339 | } |
| 340 | else |
| 341 | out = stdout; |
| 342 | if (w_flag) |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 343 | test_rw (dev, blocks_count, block_size, out, from_count); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 344 | else |
Theodore Ts'o | f3db356 | 1997-04-26 13:34:30 +0000 | [diff] [blame] | 345 | test_ro (dev, blocks_count, block_size, out, from_count); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 346 | close (dev); |
| 347 | if (out != stdout) |
| 348 | fclose (out); |
Theodore Ts'o | 00e5433 | 1997-09-16 02:13:52 +0000 | [diff] [blame] | 349 | exit(0); |
Theodore Ts'o | 3839e65 | 1997-04-26 13:21:57 +0000 | [diff] [blame] | 350 | } |