head	1.15;
access;
symbols
	Version_2_1:1.11
	C_Demo_1:1.6;
locks; strict;
comment	@ * @;


1.15
date	92.03.05.00.36.28;	author hong;	state Exp;
branches;
next	1.14;

1.14
date	91.11.08.15.49.56;	author kemnitz;	state Exp;
branches;
next	1.13;

1.13
date	91.06.18.23.27.46;	author cimarron;	state Exp;
branches;
next	1.12;

1.12
date	91.04.10.16.05.16;	author sp;	state Exp;
branches;
next	1.11;

1.11
date	91.02.28.20.55.08;	author mao;	state Exp;
branches;
next	1.10;

1.10
date	91.01.22.17.31.33;	author sp;	state Exp;
branches;
next	1.9;

1.9
date	91.01.17.18.42.29;	author sp;	state Exp;
branches;
next	1.8;

1.8
date	90.11.10.17.59.26;	author sp;	state Exp;
branches;
next	1.7;

1.7
date	90.09.25.16.56.04;	author kemnitz;	state Exp;
branches;
next	1.6;

1.6
date	89.09.05.16.56.56;	author mao;	state C_Demo_1;
branches;
next	1.5;

1.5
date	89.05.07.17.45.59;	author hirohama;	state Exp;
branches;
next	1.4;

1.4
date	89.03.23.01.39.18;	author muir;	state Stab;
branches;
next	1.3;

1.3
date	89.02.26.16.49.22;	author hirohama;	state Exp;
branches;
next	1.2;

1.2
date	89.02.26.15.54.14;	author hirohama;	state Exp;
branches;
next	1.1;

1.1
date	89.01.17.05.52.10;	author cimarron;	state Exp;
branches;
next	;


desc
@@


1.15
log
@cleaning up and whacking out buffer leaks
@
text
@/*
 * rac.c --
 *	POSTGRES rule lock access code.
 */

#include "tmp/c.h"

RcsId("$Header: RCS/rac.c,v 1.14 91/11/08 15:49:56 kemnitz Exp Locker: hong $");

#include "access/htup.h"

#include "rules/rac.h"
#include "rules/prs2locks.h"
#include "rules/prs2.h"

#include "storage/block.h"
#include "storage/buf.h"
#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "storage/item.h"
#include "storage/itemid.h"
#include "storage/itemptr.h"
#include "storage/page.h"

#include "utils/inval.h"
#include "utils/palloc.h"
#include "utils/rel.h"
#include "utils/log.h"

/*
 * XXX direct structure accesses should be removed
 */

/*-------------------------------------------------------------------
 * HeapTupleFreeRuleLock
 *
 * Frees a lock, but only if it is a main memory lock.
 *
 */
void
HeapTupleFreeRuleLock(tuple)
HeapTuple tuple;
{
        if (tuple->t_locktype == MEM_RULE_LOCK) {
	    prs2FreeLocks(tuple->t_lock.l_lock);
	}
}

/*-------------------------------------------------------------------
 *
 * HeapTupleGetRuleLock
 *
 * A rule lock can be either an ItemPointer to the actual data in a
 * disk page or a RuleLock, i.e. a pointer to a main memory structure.
 *
 * This routine always return the main memory representation (if necessary
 * it makes the conversion)
 *
 * NOTE #1: When we save the tuple on disk, if the rule lock is empty
 * (i.e. a "(numOfLocks: 0)"::lock, which must not be confused with
 * an 'InvalidRuleLock') then we store an InvalidItemPointer.
 * So, if the disk representation of the tuple's lock is such an
 * InvalidItemPointer we have to create & return an empty lock.
 *
 * NOTE #2: We ALWAYS return a COPY of the locks. That means we
 * return a freshly palloced new lock, that can and probably MUST
 * be pfreed by the caller (to avoid memory leaks).
 */
RuleLock
HeapTupleGetRuleLock(tuple, buffer)
	HeapTuple	tuple;
	Buffer		buffer;
{
	register RuleLock l;
	
	BlockNumber	blockNumber;
	Page		page;
	ItemId		itemId;
	Item		item;
	Item		returnItem;


	Assert(HeapTupleIsValid(tuple));

	/*
	 * sanity check
	 */
        if (tuple->t_locktype != MEM_RULE_LOCK &&
            tuple->t_locktype != DISK_RULE_LOCK) {
            elog(WARN,"HeapTupleGetRuleLock: locktype = '%c',(%d)\n",
                tuple->t_locktype, tuple->t_locktype);
        }

	/*
	 * if this is a main memory lock, then return it.
	 */
        if (tuple->t_locktype == MEM_RULE_LOCK) {
            l = tuple->t_lock.l_lock;
	    if (l==InvalidRuleLock) {
		elog(WARN, "HeapTupleGetRuleLock: Invalid Rule Lock!");
	    } else {
		return(prs2CopyLocks(l));
	    }
        }

	/*
	 * no, it is a disk lock. Check if it the item pointer is
	 * invalid and if yes, return an empty lock.
	 */
	if (!ItemPointerIsValid(&tuple->t_lock.l_ltid)) {
		return (prs2MakeLocks());
	}

	/*
	 * If this is a disk lock, then we *must* pass a valid
	 * buffer...
	 * Otherwise ... booooooom!
	 */
        if (!BufferIsValid(buffer)) {
            elog(WARN,"HeapTupleGetRuleLock: Invalid buffer");
        }

	/* ----------------
	 *	Yow! raw page access! shouldn't this use the am code?
	 * ----------------
	 */
	blockNumber = ItemPointerGetBlockNumber(&tuple->t_lock.l_ltid);
	
	buffer = ReadBuffer(BufferGetRelation(buffer), blockNumber);
	
	page = BufferSimpleGetPage(buffer);
	itemId = PageGetItemId(page,
		ItemPointerSimpleGetOffsetIndex(&tuple->t_lock.l_ltid));
	item = PageGetItem(page, itemId);
	
	returnItem = (Item) palloc(itemId->lp_len);	/* XXX */
	bcopy(item, returnItem, (int)itemId->lp_len);	/* XXX Clib-copy */
	
	ReleaseBuffer(buffer);
	
	/*
	 * `l' is a pointer to data in a buffer page.
	 * return a copy of it.
	 */
	l = LintCast(RuleLock, returnItem);
	
	return(prs2CopyLocks(l));
}

/*----------------------------------------------------------------------------
 * HeapTupleHasEmptyRuleLock
 *
 * return true if the given tuple has an empty rule lock.
 * We can always use 'HeapTupleGetRuleLock', test the locks, and then
 * pfreed it, but this routine avoids the calls to 'palloc'
 * and 'pfree' thus making postgres 153.34 % faster....
 *----------------------------------------------------------------------------
 */
bool
HeapTupleHasEmptyRuleLock(tuple, buffer)
	HeapTuple	tuple;
	Buffer		buffer;
{
	register RuleLock l;

	Assert(HeapTupleIsValid(tuple));

	/*
	 * sanity check
	 */
        if (tuple->t_locktype != MEM_RULE_LOCK &&
            tuple->t_locktype != DISK_RULE_LOCK) {
            elog(WARN,"HeapTupleHasEmptyRuleLock: locktype = '%c',(%d)\n",
                tuple->t_locktype, tuple->t_locktype);
        }

	/*
	 * is this is a main memory lock ?
	 */
        if (tuple->t_locktype == MEM_RULE_LOCK) {
            l = tuple->t_lock.l_lock;
	    if (l==InvalidRuleLock) {
		elog(WARN, "HeapTupleHasEmptyRuleLock: Invalid Rule Lock!");
	    } else {
		if (prs2RuleLockIsEmpty(l))
		    return(true);
		else
		    return(false);
	    }
        }

	/*
	 * no, it is a disk lock. Check if it the item pointer is
	 * invalid and if yes, return true (empty lock), otherwise
	 * return false.
	 */
	if (!ItemPointerIsValid(&tuple->t_lock.l_ltid))
	    return (true);
	else
	    return(false);
}

/*------------------------------------------------------------
 *
 * HeapTupleSetRuleLock
 *
 * Set the rule lock of a tuple.
 *
 * NOTE: the old lock of the tuple is pfreed (if it is a main
 *	memory pointer of course... If it is an item in a disk page
 *	it is left intact)
 *
 * NOTE: No copies of the tuple or the lock are made any more (this
 * routine used to -sometimes- make a copy of the tuple)
 */
void
HeapTupleSetRuleLock(tuple, buffer, lock)
	HeapTuple	tuple;
	Buffer		buffer;
	RuleLock	lock;
{
	RuleLock oldLock;
	Assert(HeapTupleIsValid(tuple));

	/*
	 * sanity check...
	 */
        if (tuple->t_locktype != MEM_RULE_LOCK &&
            tuple->t_locktype != DISK_RULE_LOCK) {
            elog(WARN,"HeapTupleGetRuleLock: locktype = '%c',(%d)\n",
                tuple->t_locktype, tuple->t_locktype);
        }

	HeapTupleFreeRuleLock(tuple);

        tuple->t_locktype = MEM_RULE_LOCK;
	tuple->t_lock.l_lock = lock;

}

/*------------------------------------------------------------
 *
 * HeapTupleStoreRuleLock
 *
 * Store a rule lock in a page.
 * The 'tuple' has a lock which can be either a "disk" or "main memory"
 * rule lock. In the first case we don't need to do anything because
 * the lock is already there! In the second case, add the lock data
 * somewhere in the disk page & update the tuple->t_lock stuff...
 *
 * NOTE: When we save the tuple on disk, if the rule lock is empty
 * (i.e. a "(numOfLocks: 0)"::lock, which must not be confused with
 * an 'InvalidRuleLock') then we store an InvalidItemPointer.
 */
void
HeapTupleStoreRuleLock(tuple, buffer)
	HeapTuple	tuple;
	Buffer		buffer;
{
	Relation	relation;
	Page		page;
	BlockNumber	blockNumber;
	OffsetNumber	offsetNumber;
	RuleLock	lock;
	Size		lockSize;

	Assert(HeapTupleIsValid(tuple));
	Assert(BufferIsValid(buffer));

	/*
	 * If it is a disk lock, then there is nothing to do...
	 */
	if (tuple->t_locktype == DISK_RULE_LOCK) {
	    return;
	}

	/*
	 * sanity check
	 */
        if (tuple->t_locktype != MEM_RULE_LOCK) {
            elog(WARN,"HeapTupleGetRuleLock: locktype = '%c',(%d)\n",
                tuple->t_locktype, tuple->t_locktype);
        }

	lock = tuple->t_lock.l_lock;

	/*
	 * if it is an empty lock, treat the trivial case..
	 */
        if (prs2RuleLockIsEmpty(lock)) {
		/*
		 * change it to an empty  disk lock 
		 */
                tuple->t_locktype = DISK_RULE_LOCK;
		ItemPointerSetInvalid(&tuple->t_lock.l_ltid);
		return;
	}

	relation = BufferGetRelation(buffer);

	/* Assert(0); */
	/* XXX START HERE */
	/*
	 * need to check if sufficient space.  If so, then place it.
	 * Otherwise, allocate a new page.  Etc.  Look at util/access.c
	 * for ideas and to have amputtup and amputnew call this funciton.
	 *
	 * XXX this should be rewritten to use the am code rather than
	 *	using the buffer and page code directly.
	 */

	page = BufferSimpleGetPage(buffer);

	lockSize = prs2LockSize(prs2GetNumberOfLocks(lock));

	if (PageGetFreeSpace(page) >= lockSize) {

		blockNumber = BufferGetBlockNumber(buffer);
		offsetNumber = PageAddItem(page, (Item) lock, lockSize,
			InvalidOffsetNumber, LP_USED | LP_LOCK);
	} else {
		Assert(lockSize < BLCKSZ); /* XXX cannot handle this yet */

		buffer = ReadBuffer(relation, P_NEW);
		
#ifndef NO_BUFFERISVALID
		if (!BufferIsValid(buffer)) {
		    elog(WARN,
			 "HeapTupleStoreRuleLock: cannot get new buffer");
		}
#endif NO_BUFFERISVALID
		
		BufferSimpleInitPage(buffer);
		blockNumber = BufferGetBlockNumber(buffer);
		offsetNumber = PageAddItem(BufferSimpleGetPage(buffer),
			(Item) lock, lockSize, InvalidOffsetNumber,
			LP_USED | LP_LOCK);
		WriteBuffer(buffer);
	}
	tuple->t_locktype = DISK_RULE_LOCK;
	ItemPointerSimpleSet(&tuple->t_lock.l_ltid, blockNumber, offsetNumber);

	/*
	 * And now the tricky & funny part... (not submitted to
	 * Rec.puzzles yet...)
	 *
	 * Shall we free the old memory locks or not ????
	 * "I say yes! It is safe to pfree them..." (Spyros said, just before
	 * the lights went out...)
	 *
	 * ---
	 * The lights went out, so DO NOT FREE the locks! I think the
	 * problem is that when 'RelationPutHeapTuple' calls this routine
	 * it passes to it an item pointer, i.e. a "disk" tuple, and
	 * not the main memory tuple. So finally when the executor does
	 * its cleanup, it ends up freeing this lock twice!
	 */
	/* prs2FreeLocks(lock); */

	/*
	 * And our last question for $200!
	 * the following call to "RelationInvalidateHeapTuple()"
	 * invalidates the cache...  Do we really need it ???
	 * 
	 * The correct answer is NO! "HeapTupleStoreRuleLock()"
	 * in only called by "RelationPutHeapTuple()"
	 * and "RelationPutLongHeapTuple()", which are called
	 * by "amreplace()", "aminsert()" and company, which
	 * invalidate the cache anyway...
	 */

	/* RelationInvalidateHeapTuple(relation, tuple); */

}
@


1.14
log
@prototype checkin
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.13 91/06/18 23:27:46 cimarron Exp Locker: sp $");
d129 1
a129 3
	buffer = RelationGetBuffer(BufferGetRelation(buffer),
				   blockNumber,
				   L_PIN);
d139 1
a139 1
	BufferPut(buffer, L_UNPIN);
d324 1
a324 1
		buffer = RelationGetBuffer(relation, P_NEW, L_NEW);
d338 1
a338 1
		BufferPut(buffer, L_UN | L_EX | L_WRITE);
@


1.13
log
@reorganized executor to use tuple table properly for nested dot stuff
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.12 91/04/10 16:05:16 sp Exp Locker: cimarron $");
d321 1
a321 1
		offsetNumber = PageAddItem(page, lock, lockSize,
d338 1
a338 1
			lock, lockSize, InvalidOffsetNumber,
@


1.12
log
@HeapTupleGetRuleLock now always return a COPY of the rule lock.
This copy can and should be pfreed (to avoid memory leaks)
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.11 91/02/28 20:55:08 mao Exp Locker: sp $");
d75 6
d82 1
d123 4
a126 8

{
	BlockNumber	blockNumber;
	Page		page;
	ItemId		itemId;
	Item		item;
	Item		returnItem;

d128 5
a132 2
	buffer = RelationGetBuffer(BufferGetRelation(buffer), blockNumber,
		L_PIN);
d137 1
d140 1
d142 1
a142 1
	l = LintCast(RuleLock, returnItem);
d147 2
a150 1
}
d309 3
d324 1
a324 1
		Assert(lockSize < BLCKSZ);	/* XXX cannot handle this yet */
d327 1
d330 2
a331 1
			elog(WARN, "HeapTupleStoreRuleLock: cannot get new buffer");
d333 2
a334 1
#endif
@


1.11
log
@typecast calls to palloc
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.10 91/01/22 17:31:33 sp Exp Locker: mao $");
d55 1
d59 1
a59 1
 * NOTE: When we save the tuple on disk, if the rule lock is empty
d64 4
d74 2
d91 6
a96 1
            return(tuple->t_lock.l_lock);
d134 6
a139 1
	return (LintCast(RuleLock, returnItem));
d143 2
a144 1
/*------------------------------------------------------------
d146 52
a361 39


#ifdef OBSOLETE_BEYOND_BELIEF
/*=====================================================================
 * HeapStoreRuleLock used to be a little bit different:
 * It was *replacing* the lock of a tuple with a new one,
 * so it was first trying to free the disk space occupied by the first
 * lock. This probably screws time range queries involving rules
 * though..
 *
 * Anyway, there was some obscure code for "disk" rule lock removal
 * and as probably its writer (M. Hirohama???) is far away, and
 * nobody ever will want to duplicate his effort I decided to keep this
 * piece of code just in case we might need it again (God forbids!)
 *
 * sp.
 *=====================================================================
 */

        if (tuple->t_locktype == DISK_RULE_LOCK &&
            ItemPointerIsValid(&tuple->t_lock.l_ltid)) {
		/* XXX Is the physical removal of the lock safe? */

		if (ItemPointerGetBlockNumber(&tuple->t_lock.l_ltid) ==
				BufferGetBlockNumber(buffer)) {

			PageRemoveItem(page,
				PageGetItemId(page,
					ItemPointerSimpleGetOffsetIndex(
						&tuple->t_lock.l_ltid)));
		} else {
			;
			/*
			 * XXX for now, let the vacuum demon remove locks
			 * on other pages
			 */
		}
	}
#endif OBSOLETE_BEYOND_BELIEF
@


1.10
log
@this strange, obsolete & dangerous `psize(lock)' has been replaced by
the clean, sophisticated, safe and totally awesome (batteries not
included) 'prs2LockSize'
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.9 91/01/17 18:42:29 sp Exp $");
d119 1
a119 1
	returnItem = palloc(itemId->lp_len);	/* XXX */
@


1.9
log
@New function 'HeapTupleFreeRuleLock' and some other changes,
because now no tuple can have an InvalidRuleLock (it will always
have a valid (poosibly empty) lock).
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.8 90/11/10 17:59:26 sp Exp $");
d188 1
d234 1
d236 1
a236 1
	if (PageGetFreeSpace(page) >= psize(lock)) {
d239 1
a239 1
		offsetNumber = PageAddItem(page, lock, psize(lock),
d242 1
a242 1
		Assert(psize(lock) < BLCKSZ);	/* XXX cannot handle this yet */
d253 1
a253 1
			lock, psize(lock), InvalidOffsetNumber,
@


1.8
log
@Major face lift in "HeapTupleStoreRuleLock()".
Now it only takes 2 arguments...
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.7 90/09/25 16:56:04 kemnitz Exp Locker: sp $");
d14 1
d35 1
d37 14
d58 5
d88 2
a89 1
	 * no, it is a disk lock. Check if it is invalid
d92 1
a92 1
		return (InvalidRuleLock);
d157 1
a157 3
	if (tuple->t_locktype == MEM_RULE_LOCK) {
	    prs2FreeLocks(tuple->t_lock.l_lock);
	}
d174 3
d263 1
a263 1
	 * "I say yes! It is safe to pfree them..." (Spyros said just before
d265 7
d273 1
a273 1
	prs2FreeLocks(lock);
@


1.7
log
@Updating from revision 1.6 to revision 1.11
@
text
@d8 1
a8 1
RcsId("$Header: RCS/rac.c,v 1.11 90/09/10 21:24:24 plai Exp $");
d13 1
a13 1
#include "rules/rlock.h"
d32 11
d50 8
a57 3
	if (!BufferIsValid(buffer)) {
		return (tuple->t_lock.l_lock);
	}
d59 10
a69 1

d72 11
d104 14
a117 1
HeapTuple
d123 1
d126 8
a133 2
	if (BufferIsValid(buffer)) {
		HeapTuple	palloctup();
d135 2
a136 1
		tuple = palloctup(tuple, buffer, InvalidRelation);
d139 1
a141 1
	return (tuple);
d144 11
d156 1
a156 1
HeapTupleStoreRuleLock(tuple, buffer, lock)
a158 1
	RuleLock	lock;
d164 1
d169 25
a193 1
	if (!RuleLockIsValid(lock)) {
a209 24
#ifdef NOT_YET
	/** ------------- XXX --------- SOS ------------
	 ** Sometimes (when defining or removing a rule)
	 ** the `ItemPointerIsValid' test calls elog(WARN...).
	 ** The reason is that `t_lock' is a union:
	 ** 
         ** union {
         **        ItemPointerData l_ltid; /* TID of the lock */
         **        RuleLock        l_lock; /* internal lock format */
         ** } t_lock;
	 **
	 ** Now, even if we set `t_lock.l_lock' to a perfectly valid pointer,
	 ** it is possible that `ItemPointerIsValid(&tuple->t_lock.l_ltid)'
	 ** can fail confused and abort the Xact (see the definition
	 ** of `ItemPointerIsValid').
	 **
	 ** So, for the time being I decided to skip this test altogether,
	 ** (in which case locks are not physically deleted ever from
	 ** a page).
	 **
	 ** In the long run, this test can be rewritten so that it
	 ** understands whether to test `t_lock.l_lock' or `t_lock.l_ltid'
	 **
	 **/
a210 21
	if (ItemPointerIsValid(&tuple->t_lock.l_ltid)) {

		/* XXX Is the physical removal of the lock safe? */

		if (ItemPointerGetBlockNumber(&tuple->t_lock.l_ltid) ==
				BufferGetBlockNumber(buffer)) {

			PageRemoveItem(page,
				PageGetItemId(page,
					ItemPointerSimpleGetOffsetIndex(
						&tuple->t_lock.l_ltid)));
		} else {
			;
			/*
			 * XXX for now, let the vacuum demon remove locks
			 * on other pages
			 */
		}
	}
#endif NOT_YET

d232 1
d235 24
a258 1
	RelationInvalidateHeapTuple(relation, tuple);
d260 39
@


1.6
log
@Working version of C-only demo
@
text
@d6 1
a6 1
#include "c.h"
d8 1
a8 14
#include "block.h"
#include "buf.h"
#include "bufmgr.h"
#include "bufpage.h"
#include "htup.h"
#include "inval.h"
#include "item.h"
#include "itemid.h"
#include "itemptr.h"
#include "log.h"
#include "page.h"
#include "palloc.h"
#include "rel.h"
#include "rlock.h"
d10 1
a10 1
#include "rac.h"
d12 2
a13 1
RcsId("$Header: /usr6/postgres/mao/postgres/src/access/heap/RCS/rac.c,v 1.5 89/05/07 17:45:59 hirohama Exp $");
d15 14
d118 25
d162 1
d173 1
d177 1
@


1.5
log
@mmgr.h -> palloc.h
@
text
@d25 1
a25 1
RcsId("$Header: rac.c,v 1.4 89/03/23 01:39:18 hirohama Locked $");
@


1.4
log
@I wish rcs -u worked
@
text
@a17 1
#include "mmgr.h"
d19 1
d25 1
a25 1
RcsId("$Header: /usr6/postgres/muir/postgres/src/access/heap/RCS/rac.c,v 1.3 89/02/26 16:49:22 hirohama Exp $");
@


1.3
log
@forgot an include
@
text
@d25 1
a25 1
RcsId("$Header: rac.c,v 1.2 89/02/26 15:54:14 hirohama Locked $");
@


1.2
log
@HeapTupleStoreRuleLock:  now invalidates the tuple on which the lock is stored
@
text
@d13 1
d25 1
a25 1
RcsId("$Header: rac.c,v 1.1 89/01/17 05:52:10 hirohama Locked $");
@


1.1
log
@Initial revision
@
text
@a0 1

a1 26
 * 
 * POSTGRES Data Base Management System
 * 
 * Copyright (c) 1988 Regents of the University of California
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for educational, research, and non-profit purposes and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of the University of California not be used in advertising
 * or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Permission to incorporate this
 * software into commercial products can be obtained from the Campus
 * Software Office, 295 Evans Hall, University of California, Berkeley,
 * Ca., 94720 provided only that the the requestor give the University
 * of California a free licence to any derived software for educational
 * and research purposes.  The University of California makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 * 
 */



/*
d24 1
a24 1
RcsId("$Header: rac.c,v 1.1 88/11/11 16:42:01 postgres Exp $");
d117 2
d155 2
@
