| 1 | |
| 2 |
|
| 3 | |
| 4 | * Directory operations for Coda filesystem
|
| 5 | * Original version: (C) 1996 P. Braam and M. Callahan
|
| 6 | * Rewritten for Linux 2.1. (C) 1997 Carnegie Mellon University
|
| 7 | *
|
| 8 | * Carnegie Mellon encourages users to contribute improvements to
|
| 9 | * the Coda project. Contact Peter Braam ([email protected]).
|
| 10 |
|
| 11 |
|
| 12 | #include <linux/types.h>
|
| 13 | #include <linux/kernel.h>
|
| 14 | #include <linux/time.h>
|
| 15 | #include <linux/fs.h>
|
| 16 | #include <linux/slab.h>
|
| 17 | #include <linux/file.h>
|
| 18 | #include <linux/stat.h>
|
| 19 | #include <linux/errno.h>
|
| 20 | #include <linux/string.h>
|
| 21 | #include <linux/spinlock.h>
|
| 22 | #include <linux/namei.h>
|
| 23 | #include <linux/uaccess.h>
|
| 24 |
|
| 25 | #include <linux/coda.h>
|
| 26 | #include "coda_psdev.h"
|
| 27 | #include "coda_linux.h"
|
| 28 | #include "coda_cache.h"
|
| 29 |
|
| 30 | #include "coda_int.h"
|
| 31 |
|
| 32 |
|
| 33 | static int coda_return_EIO(void)
|
| 34 | {
|
| 35 | return -EIO;
|
| 36 | }
|
| 37 | #define CODA_EIO_ERROR ((void *) (coda_return_EIO))
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 | static struct dentry *coda_lookup(struct inode *dir, struct dentry *entry, unsigned int flags)
|
| 42 | {
|
| 43 | struct super_block *sb = dir->i_sb;
|
| 44 | const char *name = entry->d_name.name;
|
| 45 | size_t length = entry->d_name.len;
|
| 46 | struct inode *inode;
|
| 47 | int type = 0;
|
| 48 |
|
| 49 | if (length > CODA_MAXNAMLEN) {
|
| 50 | pr_err("name too long: lookup, %s %zu\n",
|
| 51 | coda_i2s(dir), length);
|
| 52 | return ERR_PTR(-ENAMETOOLONG);
|
| 53 | }
|
| 54 |
|
| 55 |
|
| 56 | if (is_root_inode(dir) && coda_iscontrol(name, length)) {
|
| 57 | inode = coda_cnode_makectl(sb);
|
| 58 | type = CODA_NOCACHE;
|
| 59 | } else {
|
| 60 | struct CodaFid fid = { { 0, } };
|
| 61 | int error = venus_lookup(sb, coda_i2f(dir), name, length,
|
| 62 | &type, &fid);
|
| 63 | inode = !error ? coda_cnode_make(&fid, sb) : ERR_PTR(error);
|
| 64 | }
|
| 65 |
|
| 66 | if (!IS_ERR(inode) && (type & CODA_NOCACHE))
|
| 67 | coda_flag_inode(inode, C_VATTR | C_PURGE);
|
| 68 |
|
| 69 | if (inode == ERR_PTR(-ENOENT))
|
| 70 | inode = NULL;
|
| 71 |
|
| 72 | return d_splice_alias(inode, entry);
|
| 73 | }
|
| 74 |
|
| 75 |
|
| 76 | int coda_permission(const struct mnt_idmap *idmap, struct inode *inode,
|
| 77 | int mask)
|
| 78 | {
|
| 79 | int error;
|
| 80 |
|
| 81 | if (mask & MAY_NOT_BLOCK)
|
| 82 | return -ECHILD;
|
| 83 |
|
| 84 | mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
|
| 85 |
|
| 86 | if (!mask)
|
| 87 | return 0;
|
| 88 |
|
| 89 | if ((mask & MAY_EXEC) && !execute_ok(inode))
|
| 90 | return -EACCES;
|
| 91 |
|
| 92 | if (coda_cache_check(inode, mask))
|
| 93 | return 0;
|
| 94 |
|
| 95 | error = venus_access(inode->i_sb, coda_i2f(inode), mask);
|
| 96 |
|
| 97 | if (!error)
|
| 98 | coda_cache_enter(inode, mask);
|
| 99 |
|
| 100 | return error;
|
| 101 | }
|
| 102 |
|
| 103 |
|
| 104 | static inline void coda_dir_update_mtime(struct inode *dir)
|
| 105 | {
|
| 106 | #ifdef REQUERY_VENUS_FOR_MTIME
|
| 107 | |
| 108 | * attributes from venus next time the inode is referenced
|
| 109 | coda_flag_inode(dir, C_VATTR);
|
| 110 | #else
|
| 111 | |
| 112 | * granularity of the mtime is coarse anyways so we might actually be
|
| 113 | * right most of the time. Note: we only do this for directories.
|
| 114 | inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
|
| 115 | #endif
|
| 116 | }
|
| 117 |
|
| 118 | |
| 119 | * trick to fool GNU find's optimizations. If we can't be sure of the link
|
| 120 | * (because of volume mount points) we set i_nlink to 1 which forces find
|
| 121 | * to consider every child as a possible directory. We should also never
|
| 122 | * see an increment or decrement for deleted directories where i_nlink == 0
|
| 123 | static inline void coda_dir_inc_nlink(struct inode *dir)
|
| 124 | {
|
| 125 | if (dir->i_nlink >= 2)
|
| 126 | inc_nlink(dir);
|
| 127 | }
|
| 128 |
|
| 129 | static inline void coda_dir_drop_nlink(struct inode *dir)
|
| 130 | {
|
| 131 | if (dir->i_nlink > 2)
|
| 132 | drop_nlink(dir);
|
| 133 | }
|
| 134 |
|
| 135 |
|
| 136 | static int coda_create(const struct mnt_idmap *idmap, struct inode *dir,
|
| 137 | struct dentry *de, umode_t mode)
|
| 138 | {
|
| 139 | int error;
|
| 140 | const char *name=de->d_name.name;
|
| 141 | int length=de->d_name.len;
|
| 142 | struct inode *inode;
|
| 143 | struct CodaFid newfid;
|
| 144 | struct coda_vattr attrs;
|
| 145 |
|
| 146 | if (is_root_inode(dir) && coda_iscontrol(name, length))
|
| 147 | return -EPERM;
|
| 148 |
|
| 149 | error = venus_create(dir->i_sb, coda_i2f(dir), name, length,
|
| 150 | 0, mode, &newfid, &attrs);
|
| 151 | if (error)
|
| 152 | goto err_out;
|
| 153 |
|
| 154 | inode = coda_iget(dir->i_sb, &newfid, &attrs);
|
| 155 | if (IS_ERR(inode)) {
|
| 156 | error = PTR_ERR(inode);
|
| 157 | goto err_out;
|
| 158 | }
|
| 159 |
|
| 160 |
|
| 161 | coda_dir_update_mtime(dir);
|
| 162 | d_instantiate(de, inode);
|
| 163 | return 0;
|
| 164 | err_out:
|
| 165 | d_drop(de);
|
| 166 | return error;
|
| 167 | }
|
| 168 |
|
| 169 | static struct dentry *coda_mkdir(const struct mnt_idmap *idmap, struct inode *dir,
|
| 170 | struct dentry *de, umode_t mode)
|
| 171 | {
|
| 172 | struct inode *inode;
|
| 173 | struct coda_vattr attrs;
|
| 174 | const char *name = de->d_name.name;
|
| 175 | int len = de->d_name.len;
|
| 176 | int error;
|
| 177 | struct CodaFid newfid;
|
| 178 |
|
| 179 | if (is_root_inode(dir) && coda_iscontrol(name, len))
|
| 180 | return ERR_PTR(-EPERM);
|
| 181 |
|
| 182 | |
| 183 | * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded
|
| 184 | * verbatim to userspace, which has only ever been given the permission
|
| 185 | * bits. Strip the type bit until venus is known to cope with it.
|
| 186 |
|
| 187 | attrs.va_mode = mode & ~S_IFDIR;
|
| 188 | error = venus_mkdir(dir->i_sb, coda_i2f(dir),
|
| 189 | name, len, &newfid, &attrs);
|
| 190 | if (error)
|
| 191 | goto err_out;
|
| 192 |
|
| 193 | inode = coda_iget(dir->i_sb, &newfid, &attrs);
|
| 194 | if (IS_ERR(inode)) {
|
| 195 | error = PTR_ERR(inode);
|
| 196 | goto err_out;
|
| 197 | }
|
| 198 |
|
| 199 |
|
| 200 | coda_dir_inc_nlink(dir);
|
| 201 | coda_dir_update_mtime(dir);
|
| 202 | d_instantiate(de, inode);
|
| 203 | return NULL;
|
| 204 | err_out:
|
| 205 | d_drop(de);
|
| 206 | return ERR_PTR(error);
|
| 207 | }
|
| 208 |
|
| 209 |
|
| 210 | static int coda_link(struct dentry *source_de, struct inode *dir_inode,
|
| 211 | struct dentry *de)
|
| 212 | {
|
| 213 | struct inode *inode = d_inode(source_de);
|
| 214 | const char * name = de->d_name.name;
|
| 215 | int len = de->d_name.len;
|
| 216 | int error;
|
| 217 |
|
| 218 | if (is_root_inode(dir_inode) && coda_iscontrol(name, len))
|
| 219 | return -EPERM;
|
| 220 |
|
| 221 | error = venus_link(dir_inode->i_sb, coda_i2f(inode),
|
| 222 | coda_i2f(dir_inode), (const char *)name, len);
|
| 223 | if (error) {
|
| 224 | d_drop(de);
|
| 225 | return error;
|
| 226 | }
|
| 227 |
|
| 228 | coda_dir_update_mtime(dir_inode);
|
| 229 | ihold(inode);
|
| 230 | d_instantiate(de, inode);
|
| 231 | inc_nlink(inode);
|
| 232 | return 0;
|
| 233 | }
|
| 234 |
|
| 235 |
|
| 236 | static int coda_symlink(const struct mnt_idmap *idmap,
|
| 237 | struct inode *dir_inode, struct dentry *de,
|
| 238 | const char *symname)
|
| 239 | {
|
| 240 | const char *name = de->d_name.name;
|
| 241 | int len = de->d_name.len;
|
| 242 | int symlen;
|
| 243 | int error;
|
| 244 |
|
| 245 | if (is_root_inode(dir_inode) && coda_iscontrol(name, len))
|
| 246 | return -EPERM;
|
| 247 |
|
| 248 | symlen = strlen(symname);
|
| 249 | if (symlen > CODA_MAXPATHLEN)
|
| 250 | return -ENAMETOOLONG;
|
| 251 |
|
| 252 | |
| 253 | * This entry is now negative. Since we do not create
|
| 254 | * an inode for the entry we have to drop it.
|
| 255 |
|
| 256 | d_drop(de);
|
| 257 | error = venus_symlink(dir_inode->i_sb, coda_i2f(dir_inode), name, len,
|
| 258 | symname, symlen);
|
| 259 |
|
| 260 |
|
| 261 | if (!error)
|
| 262 | coda_dir_update_mtime(dir_inode);
|
| 263 |
|
| 264 | return error;
|
| 265 | }
|
| 266 |
|
| 267 |
|
| 268 | static int coda_unlink(struct inode *dir, struct dentry *de)
|
| 269 | {
|
| 270 | int error;
|
| 271 | const char *name = de->d_name.name;
|
| 272 | int len = de->d_name.len;
|
| 273 |
|
| 274 | error = venus_remove(dir->i_sb, coda_i2f(dir), name, len);
|
| 275 | if (error)
|
| 276 | return error;
|
| 277 |
|
| 278 | coda_dir_update_mtime(dir);
|
| 279 | drop_nlink(d_inode(de));
|
| 280 | return 0;
|
| 281 | }
|
| 282 |
|
| 283 | static int coda_rmdir(struct inode *dir, struct dentry *de)
|
| 284 | {
|
| 285 | const char *name = de->d_name.name;
|
| 286 | int len = de->d_name.len;
|
| 287 | int error;
|
| 288 |
|
| 289 | error = venus_rmdir(dir->i_sb, coda_i2f(dir), name, len);
|
| 290 | if (!error) {
|
| 291 |
|
| 292 | if (d_really_is_positive(de))
|
| 293 | clear_nlink(d_inode(de));
|
| 294 |
|
| 295 |
|
| 296 | coda_dir_drop_nlink(dir);
|
| 297 | coda_dir_update_mtime(dir);
|
| 298 | }
|
| 299 | return error;
|
| 300 | }
|
| 301 |
|
| 302 |
|
| 303 | static int coda_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
|
| 304 | struct dentry *old_dentry, struct inode *new_dir,
|
| 305 | struct dentry *new_dentry, unsigned int flags)
|
| 306 | {
|
| 307 | const char *old_name = old_dentry->d_name.name;
|
| 308 | const char *new_name = new_dentry->d_name.name;
|
| 309 | int old_length = old_dentry->d_name.len;
|
| 310 | int new_length = new_dentry->d_name.len;
|
| 311 | int error;
|
| 312 |
|
| 313 | if (flags)
|
| 314 | return -EINVAL;
|
| 315 |
|
| 316 | error = venus_rename(old_dir->i_sb, coda_i2f(old_dir),
|
| 317 | coda_i2f(new_dir), old_length, new_length,
|
| 318 | (const char *) old_name, (const char *)new_name);
|
| 319 | if (!error) {
|
| 320 | if (d_really_is_positive(new_dentry)) {
|
| 321 | if (d_is_dir(new_dentry)) {
|
| 322 | coda_dir_drop_nlink(old_dir);
|
| 323 | coda_dir_inc_nlink(new_dir);
|
| 324 | }
|
| 325 | coda_flag_inode(d_inode(new_dentry), C_VATTR);
|
| 326 | }
|
| 327 | coda_dir_update_mtime(old_dir);
|
| 328 | coda_dir_update_mtime(new_dir);
|
| 329 | }
|
| 330 | return error;
|
| 331 | }
|
| 332 |
|
| 333 | static inline unsigned int CDT2DT(unsigned char cdt)
|
| 334 | {
|
| 335 | unsigned int dt;
|
| 336 |
|
| 337 | switch(cdt) {
|
| 338 | case CDT_UNKNOWN: dt = DT_UNKNOWN; break;
|
| 339 | case CDT_FIFO: dt = DT_FIFO; break;
|
| 340 | case CDT_CHR: dt = DT_CHR; break;
|
| 341 | case CDT_DIR: dt = DT_DIR; break;
|
| 342 | case CDT_BLK: dt = DT_BLK; break;
|
| 343 | case CDT_REG: dt = DT_REG; break;
|
| 344 | case CDT_LNK: dt = DT_LNK; break;
|
| 345 | case CDT_SOCK: dt = DT_SOCK; break;
|
| 346 | case CDT_WHT: dt = DT_WHT; break;
|
| 347 | default: dt = DT_UNKNOWN; break;
|
| 348 | }
|
| 349 | return dt;
|
| 350 | }
|
| 351 |
|
| 352 |
|
| 353 | static int coda_venus_readdir(struct file *coda_file, struct dir_context *ctx)
|
| 354 | {
|
| 355 | struct coda_file_info *cfi;
|
| 356 | struct coda_inode_info *cii;
|
| 357 | struct file *host_file;
|
| 358 | struct venus_dirent *vdir;
|
| 359 | unsigned long vdir_size = offsetof(struct venus_dirent, d_name);
|
| 360 | unsigned int type;
|
| 361 | struct qstr name;
|
| 362 | ino_t ino;
|
| 363 | int ret;
|
| 364 |
|
| 365 | cfi = coda_ftoc(coda_file);
|
| 366 | host_file = cfi->cfi_container;
|
| 367 |
|
| 368 | cii = ITOC(file_inode(coda_file));
|
| 369 |
|
| 370 | vdir = kmalloc_obj(*vdir);
|
| 371 | if (!vdir) return -ENOMEM;
|
| 372 |
|
| 373 | if (!dir_emit_dots(coda_file, ctx))
|
| 374 | goto out;
|
| 375 |
|
| 376 | while (1) {
|
| 377 | loff_t pos = ctx->pos - 2;
|
| 378 |
|
| 379 |
|
| 380 | ret = kernel_read(host_file, vdir, sizeof(*vdir), &pos);
|
| 381 | if (ret < 0) {
|
| 382 | pr_err("%s: read dir %s failed %d\n",
|
| 383 | __func__, coda_f2s(&cii->c_fid), ret);
|
| 384 | break;
|
| 385 | }
|
| 386 | if (ret == 0) break;
|
| 387 |
|
| 388 |
|
| 389 | if (ret < vdir_size || ret < vdir_size + vdir->d_namlen) {
|
| 390 | pr_err("%s: short read on %s\n",
|
| 391 | __func__, coda_f2s(&cii->c_fid));
|
| 392 | ret = -EBADF;
|
| 393 | break;
|
| 394 | }
|
| 395 |
|
| 396 | if (vdir->d_reclen < vdir_size + vdir->d_namlen) {
|
| 397 | pr_err("%s: invalid dir %s\n",
|
| 398 | __func__, coda_f2s(&cii->c_fid));
|
| 399 | ret = -EBADF;
|
| 400 | break;
|
| 401 | }
|
| 402 |
|
| 403 | name.len = vdir->d_namlen;
|
| 404 | name.name = vdir->d_name;
|
| 405 |
|
| 406 |
|
| 407 | if (name.name[0] == '.' && (name.len == 1 ||
|
| 408 | (name.name[1] == '.' && name.len == 2)))
|
| 409 | vdir->d_fileno = name.len = 0;
|
| 410 |
|
| 411 |
|
| 412 | if (vdir->d_fileno && name.len) {
|
| 413 | ino = vdir->d_fileno;
|
| 414 | type = CDT2DT(vdir->d_type);
|
| 415 | if (!dir_emit(ctx, name.name, name.len, ino, type))
|
| 416 | break;
|
| 417 | }
|
| 418 | |
| 419 | * we've already established it is non-zero.
|
| 420 | ctx->pos += vdir->d_reclen;
|
| 421 | }
|
| 422 | out:
|
| 423 | kfree(vdir);
|
| 424 | return 0;
|
| 425 | }
|
| 426 |
|
| 427 |
|
| 428 | static int coda_readdir(struct file *coda_file, struct dir_context *ctx)
|
| 429 | {
|
| 430 | struct coda_file_info *cfi;
|
| 431 | struct file *host_file;
|
| 432 | int ret;
|
| 433 |
|
| 434 | cfi = coda_ftoc(coda_file);
|
| 435 | host_file = cfi->cfi_container;
|
| 436 |
|
| 437 | ret = iterate_dir(host_file, ctx);
|
| 438 | if (ret != -ENOTDIR)
|
| 439 | return ret;
|
| 440 |
|
| 441 | return coda_venus_readdir(coda_file, ctx);
|
| 442 | }
|
| 443 |
|
| 444 |
|
| 445 | static int coda_dentry_revalidate(struct inode *dir, const struct qstr *name,
|
| 446 | struct dentry *de, unsigned int flags)
|
| 447 | {
|
| 448 | struct inode *inode;
|
| 449 | struct coda_inode_info *cii;
|
| 450 |
|
| 451 | if (flags & LOOKUP_RCU)
|
| 452 | return -ECHILD;
|
| 453 |
|
| 454 | inode = d_inode(de);
|
| 455 | if (!inode || is_root_inode(inode))
|
| 456 | goto out;
|
| 457 |
|
| 458 | cii = ITOC(d_inode(de));
|
| 459 | if (!(cii->c_flags & (C_PURGE | C_FLUSH)))
|
| 460 | goto out;
|
| 461 |
|
| 462 | shrink_dcache_parent(de);
|
| 463 |
|
| 464 |
|
| 465 | if (cii->c_flags & C_FLUSH)
|
| 466 | coda_flag_inode_children(inode, C_FLUSH);
|
| 467 |
|
| 468 | if (d_count(de) > 1)
|
| 469 |
|
| 470 | goto out;
|
| 471 |
|
| 472 |
|
| 473 | spin_lock(&cii->c_lock);
|
| 474 | cii->c_flags &= ~(C_VATTR | C_PURGE | C_FLUSH);
|
| 475 | spin_unlock(&cii->c_lock);
|
| 476 | return 0;
|
| 477 | out:
|
| 478 | return 1;
|
| 479 | }
|
| 480 |
|
| 481 | |
| 482 | * This is the callback from dput() when d_count is going to 0.
|
| 483 | * We use this to unhash dentries with bad inodes.
|
| 484 |
|
| 485 | static int coda_dentry_delete(const struct dentry * dentry)
|
| 486 | {
|
| 487 | struct inode *inode = d_inode(dentry);
|
| 488 |
|
| 489 | if (!inode)
|
| 490 | return 0;
|
| 491 |
|
| 492 | if (ITOC(inode)->c_flags & C_PURGE)
|
| 493 | return 1;
|
| 494 |
|
| 495 | return 0;
|
| 496 | }
|
| 497 |
|
| 498 |
|
| 499 |
|
| 500 | |
| 501 | * This is called when we want to check if the inode has
|
| 502 | * changed on the server. Coda makes this easy since the
|
| 503 | * cache manager Venus issues a downcall to the kernel when this
|
| 504 | * happens
|
| 505 |
|
| 506 | int coda_revalidate_inode(struct inode *inode)
|
| 507 | {
|
| 508 | struct coda_vattr attr;
|
| 509 | int error;
|
| 510 | int old_mode;
|
| 511 | ino_t old_ino;
|
| 512 | struct coda_inode_info *cii = ITOC(inode);
|
| 513 |
|
| 514 | if (!cii->c_flags)
|
| 515 | return 0;
|
| 516 |
|
| 517 | if (cii->c_flags & (C_VATTR | C_PURGE | C_FLUSH)) {
|
| 518 | error = venus_getattr(inode->i_sb, &(cii->c_fid), &attr);
|
| 519 | if (error)
|
| 520 | return -EIO;
|
| 521 |
|
| 522 | |
| 523 | - it's ino changed
|
| 524 | - type changes must be permitted for repair and
|
| 525 | missing mount points.
|
| 526 |
|
| 527 | old_mode = inode->i_mode;
|
| 528 | old_ino = inode->i_ino;
|
| 529 | coda_vattr_to_iattr(inode, &attr);
|
| 530 |
|
| 531 | if ((old_mode & S_IFMT) != (inode->i_mode & S_IFMT)) {
|
| 532 | pr_warn("inode %llu, fid %s changed type!\n",
|
| 533 | inode->i_ino, coda_f2s(&(cii->c_fid)));
|
| 534 | }
|
| 535 |
|
| 536 | |
| 537 | with a global one, here we lose and declare the inode bad
|
| 538 | if (inode->i_ino != old_ino)
|
| 539 | return -EIO;
|
| 540 |
|
| 541 | coda_flag_inode_children(inode, C_FLUSH);
|
| 542 |
|
| 543 | spin_lock(&cii->c_lock);
|
| 544 | cii->c_flags &= ~(C_VATTR | C_PURGE | C_FLUSH);
|
| 545 | spin_unlock(&cii->c_lock);
|
| 546 | }
|
| 547 | return 0;
|
| 548 | }
|
| 549 |
|
| 550 | const struct dentry_operations coda_dentry_operations = {
|
| 551 | .d_revalidate = coda_dentry_revalidate,
|
| 552 | .d_delete = coda_dentry_delete,
|
| 553 | };
|
| 554 |
|
| 555 | const struct inode_operations coda_dir_inode_operations = {
|
| 556 | .create = coda_create,
|
| 557 | .lookup = coda_lookup,
|
| 558 | .link = coda_link,
|
| 559 | .unlink = coda_unlink,
|
| 560 | .symlink = coda_symlink,
|
| 561 | .mkdir = coda_mkdir,
|
| 562 | .rmdir = coda_rmdir,
|
| 563 | .mknod = CODA_EIO_ERROR,
|
| 564 | .rename = coda_rename,
|
| 565 | .permission = coda_permission,
|
| 566 | .getattr = coda_getattr,
|
| 567 | .setattr = coda_setattr,
|
| 568 | };
|
| 569 |
|
| 570 | WRAP_DIR_ITER(coda_readdir) |
| 571 | const struct file_operations coda_dir_operations = {
|
| 572 | .llseek = generic_file_llseek,
|
| 573 | .read = generic_read_dir,
|
| 574 | .iterate_shared = shared_coda_readdir,
|
| 575 | .open = coda_open,
|
| 576 | .release = coda_release,
|
| 577 | .fsync = coda_fsync,
|
| 578 | };
|