Print this page
7656 unlinking directory on tmpfs can cause kernel panic


   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.


  24  */
  25 
  26 #pragma ident   "%Z%%M% %I%     %E% SMI"
  27 
  28 #include <sys/types.h>
  29 #include <sys/param.h>
  30 #include <sys/sysmacros.h>
  31 #include <sys/systm.h>
  32 #include <sys/time.h>
  33 #include <sys/vfs.h>
  34 #include <sys/vnode.h>
  35 #include <sys/errno.h>
  36 #include <sys/cmn_err.h>
  37 #include <sys/cred.h>
  38 #include <sys/stat.h>
  39 #include <sys/debug.h>
  40 #include <sys/policy.h>
  41 #include <sys/fs/tmpnode.h>
  42 #include <sys/fs/tmp.h>
  43 #include <sys/vtrace.h>
  44 
  45 static int tdircheckpath(struct tmpnode *, struct tmpnode *, struct cred *);
  46 static int tdirrename(struct tmpnode *, struct tmpnode *, struct tmpnode *,
  47         char *, struct tmpnode *, struct tdirent *, struct cred *);


 508         if (dir->tn_dir->td_prev == tpdp) {
 509                 dir->tn_dir->td_prev = tpdp->td_prev;
 510         }
 511         ASSERT(tpdp->td_next != tpdp);
 512         ASSERT(tpdp->td_prev != tpdp);
 513 
 514         /*
 515          * tpdp points to the correct directory entry
 516          */
 517         namelen = strlen(tpdp->td_name) + 1;
 518 
 519         tmp_memfree(tpdp, sizeof (struct tdirent) + namelen);
 520         dir->tn_size -= (sizeof (struct tdirent) + namelen);
 521         dir->tn_dirents--;
 522 
 523         gethrestime(&now);
 524         dir->tn_mtime = now;
 525         dir->tn_ctime = now;
 526         tp->tn_ctime = now;
 527 




 528         ASSERT(tp->tn_nlink > 0);
 529         DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 530         if (op == DR_RMDIR && tp->tn_type == VDIR) {



 531                 tdirtrunc(tp);
 532                 ASSERT(tp->tn_nlink == 0);
 533         }

 534         return (0);
 535 }
 536 
 537 /*
 538  * tdirinit is used internally to initialize a directory (dir)
 539  * with '.' and '..' entries without checking permissions and locking
 540  */
 541 void
 542 tdirinit(
 543         struct tmpnode *parent,         /* parent of directory to initialize */
 544         struct tmpnode *dir)            /* the new directory */
 545 {
 546         struct tdirent *dot, *dotdot;
 547         timestruc_t now;
 548 
 549         ASSERT(RW_WRITE_HELD(&parent->tn_rwlock));
 550         ASSERT(dir->tn_type == VDIR);
 551 
 552         dot = tmp_memalloc(sizeof (struct tdirent) + 2, TMP_MUSTHAVE);
 553         dotdot = tmp_memalloc(sizeof (struct tdirent) + 3, TMP_MUSTHAVE);


 598         }
 599 
 600         dir->tn_dir = dot;
 601         dir->tn_size = 2 * sizeof (struct tdirent) + 5;      /* dot and dotdot */
 602         dir->tn_dirents = 2;
 603         dir->tn_nlink = 2;
 604 }
 605 
 606 
 607 /*
 608  * tdirtrunc is called to remove all directory entries under this directory.
 609  */
 610 void
 611 tdirtrunc(struct tmpnode *dir)
 612 {
 613         struct tdirent *tdp;
 614         struct tmpnode *tp;
 615         size_t namelen;
 616         timestruc_t now;
 617         int isvattrdir, isdotdot, skip_decr;

 618 
 619         ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
 620         ASSERT(dir->tn_type == VDIR);
 621 
 622         isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
 623         for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
 624                 ASSERT(tdp->td_next != tdp);
 625                 ASSERT(tdp->td_prev != tdp);
 626                 ASSERT(tdp->td_tmpnode);
 627 
 628                 dir->tn_dir = tdp->td_next;
 629                 namelen = strlen(tdp->td_name) + 1;
 630 
 631                 /*
 632                  * Adjust the link counts to account for this directory
 633                  * entry removal. Hidden attribute directories may
 634                  * not be empty as they may be truncated as a side-
 635                  * effect of removing the parent. We do hold/rele
 636                  * operations to free up these tmpnodes.
 637                  *
 638                  * Skip the link count adjustment for parents of
 639                  * attribute directories as those link counts
 640                  * do not include the ".." reference in the hidden
 641                  * directories.
 642                  */
 643                 tp = tdp->td_tmpnode;
 644                 isdotdot = (strcmp("..", tdp->td_name) == 0);
 645                 skip_decr = (isvattrdir && isdotdot);
 646                 if (!skip_decr) {
 647                         ASSERT(tp->tn_nlink > 0);
 648                         DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 649                 }
 650 
 651                 tmpfs_hash_out(tdp);
 652 
 653                 tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
 654                 dir->tn_size -= (sizeof (struct tdirent) + namelen);
 655                 dir->tn_dirents--;






















 656         }

 657 
 658         gethrestime(&now);
 659         dir->tn_mtime = now;
 660         dir->tn_ctime = now;
 661 
 662         ASSERT(dir->tn_dir == NULL);
 663         ASSERT(dir->tn_size == 0);
 664         ASSERT(dir->tn_dirents == 0);
 665 }
 666 
 667 /*
 668  * Check if the source directory is in the path of the target directory.
 669  * The target directory is locked by the caller.
 670  *
 671  * XXX - The source and target's should be different upon entry.
 672  */
 673 static int
 674 tdircheckpath(
 675         struct tmpnode *fromtp,
 676         struct tmpnode  *toparent,




   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  *
  25  * Copyright 2016 RackTop Systems.
  26  */
  27 


  28 #include <sys/types.h>
  29 #include <sys/param.h>
  30 #include <sys/sysmacros.h>
  31 #include <sys/systm.h>
  32 #include <sys/time.h>
  33 #include <sys/vfs.h>
  34 #include <sys/vnode.h>
  35 #include <sys/errno.h>
  36 #include <sys/cmn_err.h>
  37 #include <sys/cred.h>
  38 #include <sys/stat.h>
  39 #include <sys/debug.h>
  40 #include <sys/policy.h>
  41 #include <sys/fs/tmpnode.h>
  42 #include <sys/fs/tmp.h>
  43 #include <sys/vtrace.h>
  44 
  45 static int tdircheckpath(struct tmpnode *, struct tmpnode *, struct cred *);
  46 static int tdirrename(struct tmpnode *, struct tmpnode *, struct tmpnode *,
  47         char *, struct tmpnode *, struct tdirent *, struct cred *);


 508         if (dir->tn_dir->td_prev == tpdp) {
 509                 dir->tn_dir->td_prev = tpdp->td_prev;
 510         }
 511         ASSERT(tpdp->td_next != tpdp);
 512         ASSERT(tpdp->td_prev != tpdp);
 513 
 514         /*
 515          * tpdp points to the correct directory entry
 516          */
 517         namelen = strlen(tpdp->td_name) + 1;
 518 
 519         tmp_memfree(tpdp, sizeof (struct tdirent) + namelen);
 520         dir->tn_size -= (sizeof (struct tdirent) + namelen);
 521         dir->tn_dirents--;
 522 
 523         gethrestime(&now);
 524         dir->tn_mtime = now;
 525         dir->tn_ctime = now;
 526         tp->tn_ctime = now;
 527 
 528         /*
 529          * If this is a _REMOVE (unlink) operation there may
 530          * be other links to the directory entry.
 531          */
 532         ASSERT(tp->tn_nlink > 0);
 533         DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 534         if (op == DR_RMDIR || (op == DR_REMOVE && tp->tn_type == VDIR)) {
 535                 if (tp->tn_nlink > 1) {
 536                         ASSERT(op == DR_REMOVE);
 537                 } else {
 538                         tdirtrunc(tp);
 539                         ASSERT(tp->tn_nlink == 0);
 540                 }
 541         }
 542         return (0);
 543 }
 544 
 545 /*
 546  * tdirinit is used internally to initialize a directory (dir)
 547  * with '.' and '..' entries without checking permissions and locking
 548  */
 549 void
 550 tdirinit(
 551         struct tmpnode *parent,         /* parent of directory to initialize */
 552         struct tmpnode *dir)            /* the new directory */
 553 {
 554         struct tdirent *dot, *dotdot;
 555         timestruc_t now;
 556 
 557         ASSERT(RW_WRITE_HELD(&parent->tn_rwlock));
 558         ASSERT(dir->tn_type == VDIR);
 559 
 560         dot = tmp_memalloc(sizeof (struct tdirent) + 2, TMP_MUSTHAVE);
 561         dotdot = tmp_memalloc(sizeof (struct tdirent) + 3, TMP_MUSTHAVE);


 606         }
 607 
 608         dir->tn_dir = dot;
 609         dir->tn_size = 2 * sizeof (struct tdirent) + 5;      /* dot and dotdot */
 610         dir->tn_dirents = 2;
 611         dir->tn_nlink = 2;
 612 }
 613 
 614 
 615 /*
 616  * tdirtrunc is called to remove all directory entries under this directory.
 617  */
 618 void
 619 tdirtrunc(struct tmpnode *dir)
 620 {
 621         struct tdirent *tdp;
 622         struct tmpnode *tp;
 623         size_t namelen;
 624         timestruc_t now;
 625         int isvattrdir, isdotdot, skip_decr;
 626         int lock_held;
 627 
 628         ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
 629         ASSERT(dir->tn_type == VDIR);
 630 
 631         isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
 632         for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
 633                 ASSERT(tdp->td_next != tdp);
 634                 ASSERT(tdp->td_prev != tdp);
 635                 ASSERT(tdp->td_tmpnode);
 636 
 637                 dir->tn_dir = tdp->td_next;
 638                 namelen = strlen(tdp->td_name) + 1;
 639 
 640                 /*
 641                  * Adjust the link counts to account for this directory
 642                  * entry removal. Hidden attribute directories may
 643                  * not be empty as they may be truncated as a side-
 644                  * effect of removing the parent. We do hold/rele
 645                  * operations to free up these tmpnodes.
 646                  *
 647                  * Skip the link count adjustment for parents of
 648                  * attribute directories as those link counts
 649                  * do not include the ".." reference in the hidden
 650                  * directories.
 651                  */
 652                 tp = tdp->td_tmpnode;
 653                 isdotdot = (strcmp("..", tdp->td_name) == 0);
 654                 skip_decr = (isvattrdir && isdotdot);
 655                 if (!skip_decr) {
 656                         ASSERT(tp->tn_nlink > 0);
 657                         DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 658                 }
 659 
 660                 tmpfs_hash_out(tdp);
 661 
 662                 tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
 663                 dir->tn_size -= (sizeof (struct tdirent) + namelen);
 664                 dir->tn_dirents--;
 665 
 666                 /*
 667                  * This directory entry may itself be a directory with
 668                  * entries and removing it may have created orphans.
 669                  * On a normal filesystem like UFS this wouldn't be
 670                  * a huge problem because fcsk can reclaim them.  For
 671                  * TMPFS which resides in RAM however, it means we
 672                  * end up leaking memory.
 673                  *
 674                  * To avoid this we also truncate child directories,
 675                  * but only if they have no other links to them.
 676                  */
 677                 if (!isdotdot && tp->tn_type == VDIR && tp != dir) {
 678                         if (tp->tn_nlink > 1)
 679                                 continue;
 680                         lock_held = RW_WRITE_HELD(&tp->tn_rwlock);
 681                         if (!lock_held)
 682                                 rw_enter(&tp->tn_rwlock, RW_WRITER);
 683                         tdirtrunc(tp);
 684                         if (!lock_held)
 685                                 rw_exit(&tp->tn_rwlock);
 686                         ASSERT(tp->tn_nlink == 0);
 687                 }
 688         }
 689 
 690         gethrestime(&now);
 691         dir->tn_mtime = now;
 692         dir->tn_ctime = now;
 693 
 694         ASSERT(dir->tn_dir == NULL);
 695         ASSERT(dir->tn_size == 0);
 696         ASSERT(dir->tn_dirents == 0);
 697 }
 698 
 699 /*
 700  * Check if the source directory is in the path of the target directory.
 701  * The target directory is locked by the caller.
 702  *
 703  * XXX - The source and target's should be different upon entry.
 704  */
 705 static int
 706 tdircheckpath(
 707         struct tmpnode *fromtp,
 708         struct tmpnode  *toparent,