Commit 2840c566 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs

Pull reiserfs and ext3 changes from Jan Kara:
 "Big reiserfs cleanup from Jeff, an ext3 deadlock fix, and some small
  cleanups"

* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs: (34 commits)
  reiserfs: Fix compilation breakage with CONFIG_REISERFS_CHECK
  ext3: Fix deadlock in data=journal mode when fs is frozen
  reiserfs: call truncate_setsize under tailpack mutex
  fs/jbd/revoke.c: replace shift loop by ilog2
  reiserfs: remove obsolete __constant_cpu_to_le32
  reiserfs: balance_leaf refactor, split up balance_leaf_when_delete
  reiserfs: balance_leaf refactor, format balance_leaf_finish_node
  reiserfs: balance_leaf refactor, format balance_leaf_new_nodes_paste
  reiserfs: balance_leaf refactor, format balance_leaf_paste_right
  reiserfs: balance_leaf refactor, format balance_leaf_insert_right
  reiserfs: balance_leaf refactor, format balance_leaf_paste_left
  reiserfs: balance_leaf refactor, format balance_leaf_insert_left
  reiserfs: balance_leaf refactor, pull out balance_leaf{left, right, new_nodes, finish_node}
  reiserfs: balance_leaf refactor, pull out balance_leaf_finish_node_paste
  reiserfs: balance_leaf refactor pull out balance_leaf_finish_node_insert
  reiserfs: balance_leaf refactor, pull out balance_leaf_new_nodes_paste
  reiserfs: balance_leaf refactor, pull out balance_leaf_new_nodes_insert
  reiserfs: balance_leaf refactor, pull out balance_leaf_paste_right
  reiserfs: balance_leaf refactor, pull out balance_leaf_insert_right
  reiserfs: balance_leaf refactor, pull out balance_leaf_paste_left
  ...
parents 859862dd 19ef1229
......@@ -1716,17 +1716,17 @@ static int ext3_journalled_writepage(struct page *page,
WARN_ON_ONCE(IS_RDONLY(inode) &&
!(EXT3_SB(inode->i_sb)->s_mount_state & EXT3_ERROR_FS));
if (ext3_journal_current_handle())
goto no_write;
trace_ext3_journalled_writepage(page);
handle = ext3_journal_start(inode, ext3_writepage_trans_blocks(inode));
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto no_write;
}
if (!page_has_buffers(page) || PageChecked(page)) {
if (ext3_journal_current_handle())
goto no_write;
handle = ext3_journal_start(inode,
ext3_writepage_trans_blocks(inode));
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto no_write;
}
/*
* It's mmapped pagecache. Add buffers and journal it. There
* doesn't seem much point in redirtying the page here.
......@@ -1749,17 +1749,18 @@ static int ext3_journalled_writepage(struct page *page,
atomic_set(&EXT3_I(inode)->i_datasync_tid,
handle->h_transaction->t_tid);
unlock_page(page);
err = ext3_journal_stop(handle);
if (!ret)
ret = err;
} else {
/*
* It may be a page full of checkpoint-mode buffers. We don't
* really know unless we go poke around in the buffer_heads.
* But block_write_full_page will do the right thing.
* It is a page full of checkpoint-mode buffers. Go and write
* them. They should have been already mapped when they went
* to the journal so provide NULL get_block function to catch
* errors.
*/
ret = block_write_full_page(page, ext3_get_block, wbc);
ret = block_write_full_page(page, NULL, wbc);
}
err = ext3_journal_stop(handle);
if (!ret)
ret = err;
out:
return ret;
......
......@@ -231,19 +231,15 @@ record_cache_failure:
static struct jbd_revoke_table_s *journal_init_revoke_table(int hash_size)
{
int shift = 0;
int tmp = hash_size;
int i;
struct jbd_revoke_table_s *table;
table = kmem_cache_alloc(revoke_table_cache, GFP_KERNEL);
if (!table)
goto out;
while((tmp >>= 1UL) != 0UL)
shift++;
table->hash_size = hash_size;
table->hash_shift = shift;
table->hash_shift = ilog2(hash_size);
table->hash_table =
kmalloc(hash_size * sizeof(struct list_head), GFP_KERNEL);
if (!table->hash_table) {
......@@ -252,8 +248,8 @@ static struct jbd_revoke_table_s *journal_init_revoke_table(int hash_size)
goto out;
}
for (tmp = 0; tmp < hash_size; tmp++)
INIT_LIST_HEAD(&table->hash_table[tmp]);
for (i = 0; i < hash_size; i++)
INIT_LIST_HEAD(&table->hash_table[i]);
out:
return table;
......
This diff is collapsed.
......@@ -59,7 +59,10 @@ static inline bool is_privroot_deh(struct inode *dir, struct reiserfs_de_head *d
int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
{
struct cpu_key pos_key; /* key of current position in the directory (key of directory entry) */
/* key of current position in the directory (key of directory entry) */
struct cpu_key pos_key;
INITIALIZE_PATH(path_to_entry);
struct buffer_head *bh;
int item_num, entry_num;
......@@ -77,21 +80,28 @@ int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
reiserfs_check_lock_depth(inode->i_sb, "readdir");
/* form key for search the next directory entry using f_pos field of
file structure */
/*
* form key for search the next directory entry using
* f_pos field of file structure
*/
make_cpu_key(&pos_key, inode, ctx->pos ?: DOT_OFFSET, TYPE_DIRENTRY, 3);
next_pos = cpu_key_k_offset(&pos_key);
path_to_entry.reada = PATH_READA;
while (1) {
research:
/* search the directory item, containing entry with specified key */
research:
/*
* search the directory item, containing entry with
* specified key
*/
search_res =
search_by_entry_key(inode->i_sb, &pos_key, &path_to_entry,
&de);
if (search_res == IO_ERROR) {
// FIXME: we could just skip part of directory which could
// not be read
/*
* FIXME: we could just skip part of directory
* which could not be read
*/
ret = -EIO;
goto out;
}
......@@ -102,41 +112,49 @@ int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
store_ih(&tmp_ih, ih);
/* we must have found item, that is item of this directory, */
RFALSE(COMP_SHORT_KEYS(&(ih->ih_key), &pos_key),
RFALSE(COMP_SHORT_KEYS(&ih->ih_key, &pos_key),
"vs-9000: found item %h does not match to dir we readdir %K",
ih, &pos_key);
RFALSE(item_num > B_NR_ITEMS(bh) - 1,
"vs-9005 item_num == %d, item amount == %d",
item_num, B_NR_ITEMS(bh));
/* and entry must be not more than number of entries in the item */
RFALSE(I_ENTRY_COUNT(ih) < entry_num,
/*
* and entry must be not more than number of entries
* in the item
*/
RFALSE(ih_entry_count(ih) < entry_num,
"vs-9010: entry number is too big %d (%d)",
entry_num, I_ENTRY_COUNT(ih));
entry_num, ih_entry_count(ih));
/*
* go through all entries in the directory item beginning
* from the entry, that has been found
*/
if (search_res == POSITION_FOUND
|| entry_num < I_ENTRY_COUNT(ih)) {
/* go through all entries in the directory item beginning from the entry, that has been found */
|| entry_num < ih_entry_count(ih)) {
struct reiserfs_de_head *deh =
B_I_DEH(bh, ih) + entry_num;
for (; entry_num < I_ENTRY_COUNT(ih);
for (; entry_num < ih_entry_count(ih);
entry_num++, deh++) {
int d_reclen;
char *d_name;
ino_t d_ino;
loff_t cur_pos = deh_offset(deh);
/* it is hidden entry */
if (!de_visible(deh))
/* it is hidden entry */
continue;
d_reclen = entry_length(bh, ih, entry_num);
d_name = B_I_DEH_ENTRY_FILE_NAME(bh, ih, deh);
if (d_reclen <= 0 ||
d_name + d_reclen > bh->b_data + bh->b_size) {
/* There is corrupted data in entry,
* We'd better stop here */
/*
* There is corrupted data in entry,
* We'd better stop here
*/
pathrelse(&path_to_entry);
ret = -EIO;
goto out;
......@@ -145,10 +163,10 @@ int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
if (!d_name[d_reclen - 1])
d_reclen = strlen(d_name);
/* too big to send back to VFS */
if (d_reclen >
REISERFS_MAX_NAME(inode->i_sb->
s_blocksize)) {
/* too big to send back to VFS */
continue;
}
......@@ -173,10 +191,14 @@ int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
goto research;
}
}
// Note, that we copy name to user space via temporary
// buffer (local_buf) because filldir will block if
// user space buffer is swapped out. At that time
// entry can move to somewhere else
/*
* Note, that we copy name to user space via
* temporary buffer (local_buf) because
* filldir will block if user space buffer is
* swapped out. At that time entry can move to
* somewhere else
*/
memcpy(local_buf, d_name, d_reclen);
/*
......@@ -209,22 +231,26 @@ int reiserfs_readdir_inode(struct inode *inode, struct dir_context *ctx)
} /* for */
}
/* end of directory has been reached */
if (item_num != B_NR_ITEMS(bh) - 1)
// end of directory has been reached
goto end;
/* item we went through is last item of node. Using right
delimiting key check is it directory end */
/*
* item we went through is last item of node. Using right
* delimiting key check is it directory end
*/
rkey = get_rkey(&path_to_entry, inode->i_sb);
if (!comp_le_keys(rkey, &MIN_KEY)) {
/* set pos_key to key, that is the smallest and greater
that key of the last entry in the item */
/*
* set pos_key to key, that is the smallest and greater
* that key of the last entry in the item
*/
set_cpu_key_k_offset(&pos_key, next_pos);
continue;
}
/* end of directory has been reached */
if (COMP_SHORT_KEYS(rkey, &pos_key)) {
// end of directory has been reached
goto end;
}
......@@ -248,71 +274,73 @@ static int reiserfs_readdir(struct file *file, struct dir_context *ctx)
return reiserfs_readdir_inode(file_inode(file), ctx);
}
/* compose directory item containing "." and ".." entries (entries are
not aligned to 4 byte boundary) */
/* the last four params are LE */
/*
* compose directory item containing "." and ".." entries (entries are
* not aligned to 4 byte boundary)
*/
void make_empty_dir_item_v1(char *body, __le32 dirid, __le32 objid,
__le32 par_dirid, __le32 par_objid)
{
struct reiserfs_de_head *deh;
struct reiserfs_de_head *dot, *dotdot;
memset(body, 0, EMPTY_DIR_SIZE_V1);
deh = (struct reiserfs_de_head *)body;
dot = (struct reiserfs_de_head *)body;
dotdot = dot + 1;
/* direntry header of "." */
put_deh_offset(&(deh[0]), DOT_OFFSET);
put_deh_offset(dot, DOT_OFFSET);
/* these two are from make_le_item_head, and are are LE */
deh[0].deh_dir_id = dirid;
deh[0].deh_objectid = objid;
deh[0].deh_state = 0; /* Endian safe if 0 */
put_deh_location(&(deh[0]), EMPTY_DIR_SIZE_V1 - strlen("."));
mark_de_visible(&(deh[0]));
dot->deh_dir_id = dirid;
dot->deh_objectid = objid;
dot->deh_state = 0; /* Endian safe if 0 */
put_deh_location(dot, EMPTY_DIR_SIZE_V1 - strlen("."));
mark_de_visible(dot);
/* direntry header of ".." */
put_deh_offset(&(deh[1]), DOT_DOT_OFFSET);
put_deh_offset(dotdot, DOT_DOT_OFFSET);
/* key of ".." for the root directory */
/* these two are from the inode, and are are LE */
deh[1].deh_dir_id = par_dirid;
deh[1].deh_objectid = par_objid;
deh[1].deh_state = 0; /* Endian safe if 0 */
put_deh_location(&(deh[1]), deh_location(&(deh[0])) - strlen(".."));
mark_de_visible(&(deh[1]));
dotdot->deh_dir_id = par_dirid;
dotdot->deh_objectid = par_objid;
dotdot->deh_state = 0; /* Endian safe if 0 */
put_deh_location(dotdot, deh_location(dot) - strlen(".."));
mark_de_visible(dotdot);
/* copy ".." and "." */
memcpy(body + deh_location(&(deh[0])), ".", 1);
memcpy(body + deh_location(&(deh[1])), "..", 2);
memcpy(body + deh_location(dot), ".", 1);
memcpy(body + deh_location(dotdot), "..", 2);
}
/* compose directory item containing "." and ".." entries */
void make_empty_dir_item(char *body, __le32 dirid, __le32 objid,
__le32 par_dirid, __le32 par_objid)
{
struct reiserfs_de_head *deh;
struct reiserfs_de_head *dot, *dotdot;
memset(body, 0, EMPTY_DIR_SIZE);
deh = (struct reiserfs_de_head *)body;
dot = (struct reiserfs_de_head *)body;
dotdot = dot + 1;
/* direntry header of "." */
put_deh_offset(&(deh[0]), DOT_OFFSET);
put_deh_offset(dot, DOT_OFFSET);
/* these two are from make_le_item_head, and are are LE */
deh[0].deh_dir_id = dirid;
deh[0].deh_objectid = objid;
deh[0].deh_state = 0; /* Endian safe if 0 */
put_deh_location(&(deh[0]), EMPTY_DIR_SIZE - ROUND_UP(strlen(".")));
mark_de_visible(&(deh[0]));
dot->deh_dir_id = dirid;
dot->deh_objectid = objid;
dot->deh_state = 0; /* Endian safe if 0 */
put_deh_location(dot, EMPTY_DIR_SIZE - ROUND_UP(strlen(".")));
mark_de_visible(dot);
/* direntry header of ".." */
put_deh_offset(&(deh[1]), DOT_DOT_OFFSET);
put_deh_offset(dotdot, DOT_DOT_OFFSET);
/* key of ".." for the root directory */
/* these two are from the inode, and are are LE */
deh[1].deh_dir_id = par_dirid;
deh[1].deh_objectid = par_objid;
deh[1].deh_state = 0; /* Endian safe if 0 */
put_deh_location(&(deh[1]),
deh_location(&(deh[0])) - ROUND_UP(strlen("..")));
mark_de_visible(&(deh[1]));
dotdot->deh_dir_id = par_dirid;
dotdot->deh_objectid = par_objid;
dotdot->deh_state = 0; /* Endian safe if 0 */
put_deh_location(dotdot, deh_location(dot) - ROUND_UP(strlen("..")));
mark_de_visible(dotdot);
/* copy ".." and "." */
memcpy(body + deh_location(&(deh[0])), ".", 1);
memcpy(body + deh_location(&(deh[1])), "..", 2);
memcpy(body + deh_location(dot), ".", 1);
memcpy(body + deh_location(dotdot), "..", 2);
}
This diff is collapsed.
......@@ -15,20 +15,20 @@
#include <linux/quotaops.h>
/*
** We pack the tails of files on file close, not at the time they are written.
** This implies an unnecessary copy of the tail and an unnecessary indirect item
** insertion/balancing, for files that are written in one write.
** It avoids unnecessary tail packings (balances) for files that are written in
** multiple writes and are small enough to have tails.
**
** file_release is called by the VFS layer when the file is closed. If
** this is the last open file descriptor, and the file
** small enough to have a tail, and the tail is currently in an
** unformatted node, the tail is converted back into a direct item.
**
** We use reiserfs_truncate_file to pack the tail, since it already has
** all the conditions coded.
*/
* We pack the tails of files on file close, not at the time they are written.
* This implies an unnecessary copy of the tail and an unnecessary indirect item
* insertion/balancing, for files that are written in one write.
* It avoids unnecessary tail packings (balances) for files that are written in
* multiple writes and are small enough to have tails.
*
* file_release is called by the VFS layer when the file is closed. If
* this is the last open file descriptor, and the file
* small enough to have a tail, and the tail is currently in an
* unformatted node, the tail is converted back into a direct item.
*
* We use reiserfs_truncate_file to pack the tail, since it already has
* all the conditions coded.
*/
static int reiserfs_file_release(struct inode *inode, struct file *filp)
{
......@@ -41,10 +41,10 @@ static int reiserfs_file_release(struct inode *inode, struct file *filp)
if (atomic_add_unless(&REISERFS_I(inode)->openers, -1, 1))
return 0;
mutex_lock(&(REISERFS_I(inode)->tailpack));
mutex_lock(&REISERFS_I(inode)->tailpack);
if (!atomic_dec_and_test(&REISERFS_I(inode)->openers)) {
mutex_unlock(&(REISERFS_I(inode)->tailpack));
mutex_unlock(&REISERFS_I(inode)->tailpack);
return 0;
}
......@@ -52,31 +52,35 @@ static int reiserfs_file_release(struct inode *inode, struct file *filp)
if ((!(REISERFS_I(inode)->i_flags & i_pack_on_close_mask) ||
!tail_has_to_be_packed(inode)) &&
REISERFS_I(inode)->i_prealloc_count <= 0) {
mutex_unlock(&(REISERFS_I(inode)->tailpack));
mutex_unlock(&REISERFS_I(inode)->tailpack);
return 0;
}
reiserfs_write_lock(inode->i_sb);
/* freeing preallocation only involves relogging blocks that
/*
* freeing preallocation only involves relogging blocks that
* are already in the current transaction. preallocation gets
* freed at the end of each transaction, so it is impossible for
* us to log any additional blocks (including quota blocks)
*/
err = journal_begin(&th, inode->i_sb, 1);
if (err) {
/* uh oh, we can't allow the inode to go away while there
/*
* uh oh, we can't allow the inode to go away while there
* is still preallocation blocks pending. Try to join the
* aborted transaction
*/
jbegin_failure = err;
err = journal_join_abort(&th, inode->i_sb, 1);
err = journal_join_abort(&th, inode->i_sb);
if (err) {
/* hmpf, our choices here aren't good. We can pin the inode
* which will disallow unmount from every happening, we can
* do nothing, which will corrupt random memory on unmount,
* or we can forcibly remove the file from the preallocation
* list, which will leak blocks on disk. Lets pin the inode
/*
* hmpf, our choices here aren't good. We can pin
* the inode which will disallow unmount from ever
* happening, we can do nothing, which will corrupt
* random memory on unmount, or we can forcibly
* remove the file from the preallocation list, which
* will leak blocks on disk. Lets pin the inode
* and let the admin know what is going on.
*/
igrab(inode);
......@@ -92,7 +96,7 @@ static int reiserfs_file_release(struct inode *inode, struct file *filp)
#ifdef REISERFS_PREALLOCATE
reiserfs_discard_prealloc(&th, inode);
#endif
err = journal_end(&th, inode->i_sb, 1);
err = journal_end(&th);
/* copy back the error code from journal_begin */
if (!err)
......@@ -102,35 +106,38 @@ static int reiserfs_file_release(struct inode *inode, struct file *filp)
(REISERFS_I(inode)->i_flags & i_pack_on_close_mask) &&
tail_has_to_be_packed(inode)) {
/* if regular file is released by last holder and it has been
appended (we append by unformatted node only) or its direct
item(s) had to be converted, then it may have to be
indirect2direct converted */
/*
* if regular file is released by last holder and it has been
* appended (we append by unformatted node only) or its direct
* item(s) had to be converted, then it may have to be
* indirect2direct converted
*/
err = reiserfs_truncate_file(inode, 0);
}
out:
out:
reiserfs_write_unlock(inode->i_sb);
mutex_unlock(&(REISERFS_I(inode)->tailpack));
mutex_unlock(&REISERFS_I(inode)->tailpack);
return err;
}
static int reiserfs_file_open(struct inode *inode, struct file *file)
{
int err = dquot_file_open(inode, file);
/* somebody might be tailpacking on final close; wait for it */
if (!atomic_inc_not_zero(&REISERFS_I(inode)->openers)) {
/* somebody might be tailpacking on final close; wait for it */
mutex_lock(&(REISERFS_I(inode)->tailpack));
mutex_lock(&REISERFS_I(inode)->tailpack);
atomic_inc(&REISERFS_I(inode)->openers);
mutex_unlock(&(REISERFS_I(inode)->tailpack));
mutex_unlock(&REISERFS_I(inode)->tailpack);
}
return err;
}
void reiserfs_vfs_truncate_file(struct inode *inode)
{
mutex_lock(&(REISERFS_I(inode)->tailpack));
mutex_lock(&REISERFS_I(inode)->tailpack);
reiserfs_truncate_file(inode, 1);
mutex_unlock(&(REISERFS_I(inode)->tailpack));
mutex_unlock(&REISERFS_I(inode)->tailpack);
}
/* Sync a reiserfs file. */
......@@ -205,10 +212,11 @@ int reiserfs_commit_page(struct inode *inode, struct page *page,
set_buffer_uptodate(bh);
if (logit) {
reiserfs_prepare_for_journal(s, bh, 1);
journal_mark_dirty(&th, s, bh);
journal_mark_dirty(&th, bh);
} else if (!buffer_dirty(bh)) {
mark_buffer_dirty(bh);
/* do data=ordered on any page past the end
/*
* do data=ordered on any page past the end
* of file and any buffer marked BH_New.
*/
if (reiserfs_data_ordered(inode->i_sb) &&
......@@ -219,8 +227,8 @@ int reiserfs_commit_page(struct inode *inode, struct page *page,
}
}
if (logit) {
ret = journal_end(&th, s, bh_per_page + 1);
drop_write_lock:
ret = journal_end(&th);
drop_write_lock:
reiserfs_write_unlock(s);
}
/*
......
This diff is collapsed.
......@@ -12,12 +12,6 @@
* Yura's function is added (04/07/2000)
*/
//
// keyed_hash
// yura_hash
// r5_hash
//
#include <linux/kernel.h>
#include "reiserfs.h"
#include <asm/types.h>
......@@ -56,7 +50,7 @@ u32 keyed_hash(const signed char *msg, int len)
u32 pad;
int i;
// assert(len >= 0 && len < 256);
/* assert(len >= 0 && len < 256); */
pad = (u32) len | ((u32) len << 8);
pad |= pad << 16;
......@@ -127,9 +121,10 @@ u32 keyed_hash(const signed char *msg, int len)
return h0 ^ h1;
}
/* What follows in this file is copyright 2000 by Hans Reiser, and the
* licensing of what follows is governed by reiserfs/README */
/*
* What follows in this file is copyright 2000 by Hans Reiser, and the
* licensing of what follows is governed by reiserfs/README
*/
u32 yura_hash(const signed char *msg, int len)
{
int j, pow;
......
This diff is collapsed.
This diff is collapsed.
......@@ -15,7 +15,8 @@
* reiserfs_ioctl - handler for ioctl for inode
* supported commands:
* 1) REISERFS_IOC_UNPACK - try to unpack tail from direct item into indirect
* and prevent packing file (argument arg has to be non-zero)
* and prevent packing file (argument arg has t
* be non-zero)
* 2) REISERFS_IOC_[GS]ETFLAGS, REISERFS_IOC_[GS]ETVERSION
* 3) That's all for a while ...
*/
......@@ -132,7 +133,10 @@ setversion_out:
long reiserfs_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
/* These are just misnamed, they actually get/put from/to user an int */
/*
* These are just misnamed, they actually
* get/put from/to user an int
*/
switch (cmd) {
case REISERFS_IOC32_UNPACK:
cmd = REISERFS_IOC_UNPACK;
......@@ -160,10 +164,10 @@ long reiserfs_compat_ioctl(struct file *file, unsigned int cmd,
int reiserfs_commit_write(struct file *f, struct page *page,
unsigned from, unsigned to);
/*
** reiserfs_unpack
** Function try to convert tail from direct item into indirect.
** It set up nopack attribute in the REISERFS_I(inode)->nopack
*/
* reiserfs_unpack
* Function try to convert tail from direct item into indirect.
* It set up nopack attribute in the REISERFS_I(inode)->nopack
*/
int reiserfs_unpack(struct inode *inode, struct file *filp)
{
int retval = 0;
......@@ -194,9 +198,10 @@ int reiserfs_unpack(struct inode *inode, struct file *filp)
goto out;
}
/* we unpack by finding the page with the tail, and calling
** __reiserfs_write_begin on that page. This will force a
** reiserfs_get_block to unpack the tail for us.
/*