Logo Search packages:      
Sourcecode: parted version File versions

ext2_resize.c

/*
    ext2_resize.c -- ext2 resizer
    Copyright (C) 1998-2000 Free Software Foundation, Inc.
  
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#ifndef DISCOVER_ONLY

#include <stdio.h>
#include <stdlib.h>
#include "ext2.h"

static int ext2_add_group(struct ext2_fs *fs, blk_t groupsize)
{
      blk_t admin;
      int   group;
      blk_t groupstart;
      blk_t newgdblocks;
      int   sparse;

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_add_group\n");

      if (!ped_realloc ((void*) &fs->gd,
                    (fs->numgroups+1) * sizeof(struct ext2_group_desc)
                        + fs->blocksize))
            return 0;

      if (fs->opt_debug)
      {
            if (EXT2_SUPER_BLOCKS_COUNT(fs->sb) !=
                EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb)
                + fs->numgroups * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb))
            {
                  fprintf(stderr,
                        "ext2_add_group: last (existing) group "
                        "isn't complete!\n");

                  return 0;
            }
      }

      group = fs->numgroups;
      sparse = ext2_is_group_sparse(fs, group);
      groupstart = EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb)
                 + group * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

      admin = fs->adminblocks;
      if (!sparse)
            admin -= fs->gdblocks + 1;

      if (fs->opt_debug)
      {
            if (groupsize < fs->adminblocks ||
                groupsize > EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb))
            {
                  fprintf(stderr,
                        "ext2_add_group: groups of %i blocks are "
                        "impossible!\n", groupsize);

                  return 0;
            }
      }

      newgdblocks = howmany((fs->numgroups + 1)
                              * sizeof(struct ext2_group_desc),
                        fs->blocksize);
      if (newgdblocks != fs->gdblocks)
      {
            int i;

            for (i=0;i<fs->numgroups;i++)
                  if (ext2_is_group_sparse(fs, i))
                  {
                        blk_t start;

                        start = EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb)
                              + i * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);
                        ext2_set_block_state(fs,
                                    start + fs->gdblocks + 1, 1, 1);
                  }

            fs->gdblocks++;
            fs->adminblocks++;
            if (sparse)
                  admin++;
      }

      fs->numgroups++;

      fs->sb.s_inodes_count = PED_CPU_TO_LE32(
            EXT2_SUPER_INODES_COUNT(fs->sb)
            + EXT2_SUPER_INODES_PER_GROUP(fs->sb));
      fs->sb.s_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_BLOCKS_COUNT(fs->sb) + groupsize);
      fs->sb.s_free_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_FREE_BLOCKS_COUNT(fs->sb) + groupsize - admin);
      fs->sb.s_free_inodes_count = PED_CPU_TO_LE32(
            EXT2_SUPER_FREE_INODES_COUNT(fs->sb)
              + EXT2_SUPER_INODES_PER_GROUP(fs->sb));
      fs->metadirty |= EXT2_META_SB;

      {
            blk_t off;
            blk_t sparseoff;

            off = groupstart;
            sparseoff = off + fs->itoffset - 2;

            if (sparse)
            {
                  fs->gd[group].bg_block_bitmap
                        = PED_CPU_TO_LE32(sparseoff);
                  fs->gd[group].bg_inode_bitmap
                        = PED_CPU_TO_LE32(sparseoff + 1);
            }
            else
            {
                  fs->gd[group].bg_block_bitmap
                        = PED_CPU_TO_LE32(off);
                  fs->gd[group].bg_inode_bitmap
                        = PED_CPU_TO_LE32(off + 1);
            }

            /* Hey, I don't know _why_ either */
            fs->gd[group].bg_inode_table = PED_CPU_TO_LE32(sparseoff + 2);
      }

      fs->gd[group].bg_free_blocks_count = PED_CPU_TO_LE16(groupsize - admin);
      fs->gd[group].bg_free_inodes_count = PED_CPU_TO_LE16(
            EXT2_SUPER_INODES_PER_GROUP(fs->sb));
      fs->gd[group].bg_used_dirs_count = 0;
      fs->metadirty |= EXT2_META_SB | EXT2_META_GD;

      {
            struct ext2_buffer_head *bh;
            blk_t i;

            bh = ext2_bcreate(fs, EXT2_GROUP_BLOCK_BITMAP(fs->gd[group]));
            if (!bh)
                  return 0;

            if (sparse)
            {
                  bh->data[0] |= _bitmap[0];
                  for (i=1;i<=fs->gdblocks;i++)
                        bh->data[i>>3] |= _bitmap[i&7];
            }

            i = EXT2_GROUP_BLOCK_BITMAP(fs->gd[group]) - groupstart;
            bh->data[i>>3] |= _bitmap[i&7];

            i = EXT2_GROUP_INODE_BITMAP(fs->gd[group]) - groupstart;
            bh->data[i>>3] |= _bitmap[i&7];

            for (i=0;i<fs->inodeblocks;i++)
            {
                  blk_t j;

                  j = EXT2_GROUP_INODE_TABLE(fs->gd[group])
                      - groupstart + i;
                  bh->data[j>>3] |= _bitmap[j&7];
            }

            for (i=groupsize;i<EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);i++)
                  bh->data[i>>3] |= _bitmap[i&7];

            ext2_brelse(bh, 0);         /* this is a block bitmap */
      }

      if (!ext2_zero_blocks(fs, EXT2_GROUP_INODE_BITMAP(fs->gd[group]), 1))
            return 0;
      if (!ext2_zero_blocks(fs, EXT2_GROUP_INODE_TABLE(fs->gd[group]),
                        fs->inodeblocks))
            return 0;

      if (fs->opt_safe)
            if (!ext2_sync(fs))
                  return 0;

      return 1;
}

static int ext2_del_group(struct ext2_fs *fs)
{
      blk_t admin;
      int   group;
      blk_t groupsize;
      blk_t newgdblocks;
      int   sparse;

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_del_group\n");

      group = fs->numgroups - 1;
      sparse = ext2_is_group_sparse(fs, group);

      admin = fs->adminblocks;
      if (!sparse)
            admin -= fs->gdblocks + 1;

      groupsize = EXT2_SUPER_BLOCKS_COUNT(fs->sb)
              - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb)
              - group * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

      if (EXT2_SUPER_FREE_BLOCKS_COUNT(fs->sb) < groupsize - admin)
      {
            ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                  _("Filesystem is too occupied to remove a group!"));

            return 0;
      }

      if (EXT2_SUPER_FREE_INODES_COUNT(fs->sb)
            < EXT2_SUPER_INODES_PER_GROUP(fs->sb))
      {
            ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                  _("Filesystem has too many allocated inodes to "
                    "remove a group!"));
            return 0;
      }

      if (fs->opt_debug)
      {
            if (EXT2_GROUP_FREE_INODES_COUNT(fs->gd[group]) !=
                EXT2_SUPER_INODES_PER_GROUP(fs->sb))
            {
                  fprintf(stderr,
                        "ext2_del_group: this should not "
                        "happen anymore!\n");

                  return 0;
            }
      }

      newgdblocks = howmany((fs->numgroups - 1) *
                        sizeof(struct ext2_group_desc), fs->blocksize);

      if (newgdblocks != fs->gdblocks)
      {
            int i;

            for (i=0;i<fs->numgroups;i++)
                  if (ext2_is_group_sparse(fs, i))
                  {
                        blk_t start;

                        start = EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb) +
                              i * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);
                        ext2_set_block_state(fs,
                                         start + fs->gdblocks,
                                         0, 1);
                  }

            fs->gdblocks--;
            fs->adminblocks--;
            if (sparse)
                  admin--;
      }

      if (fs->opt_debug)
      {
            if (EXT2_GROUP_FREE_BLOCKS_COUNT(fs->gd[group])
                        != groupsize - admin)
            {
                  blk_t i;
                  blk_t num;
                  blk_t offset;

                  offset = EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb) +
                        group * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);
                  num = EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

                  for (i=0;i<num;i++)
                        if (ext2_is_data_block(fs, offset+i) &&
                            ext2_get_block_state(fs, offset+i))
                        {
                              fprintf(stderr,
                                    "error: block relocator "
                                    "should have relocated "
                                    "%i\n",
                                    offset+i);

                              return 0;
                        }
            }
      }

      fs->numgroups--;

      fs->sb.s_inodes_count = PED_CPU_TO_LE32(
            EXT2_SUPER_INODES_COUNT(fs->sb)
            - EXT2_SUPER_INODES_PER_GROUP(fs->sb));
      fs->sb.s_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_BLOCKS_COUNT(fs->sb) - groupsize);
      fs->sb.s_free_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_FREE_BLOCKS_COUNT(fs->sb) - (groupsize - admin));
      fs->sb.s_free_inodes_count = PED_CPU_TO_LE32(
            EXT2_SUPER_FREE_INODES_COUNT(fs->sb)
              - EXT2_SUPER_INODES_PER_GROUP(fs->sb));
      fs->metadirty |= EXT2_META_SB;

      if (fs->opt_safe)
            ext2_sync(fs);

      ped_realloc ((void*) &fs->gd,
                 fs->numgroups * sizeof(struct ext2_group_desc)
                        + fs->blocksize);

      return 1;
}

static int ext2_grow_group(struct ext2_fs *fs, blk_t newsize)
{
      int   group;
      blk_t groupoff;
      blk_t gblocks;
      blk_t i;

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_grow_group\n");

      group = fs->numgroups - 1;
      groupoff = group * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)
               + EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb);
      gblocks = EXT2_SUPER_BLOCKS_COUNT(fs->sb) - groupoff;

      if (fs->opt_debug)
      {
            if (newsize < gblocks)
            {
                  fprintf(stderr,
                        "ext2_grow_group: called to shrink group!\n");

                  return 0;
            }

            if (gblocks == newsize)
            {
                  fprintf(stderr, "ext2_grow_group: nothing to do!\n");
                  return 0;
            }
      }

      for (i=gblocks;i<newsize;i++)
            ext2_set_block_state(fs, groupoff + i, 0, 1);

      fs->sb.s_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_BLOCKS_COUNT(fs->sb) + newsize - gblocks);
      fs->metadirty |= EXT2_META_SB;

      if (fs->opt_safe)
            ext2_sync(fs);

      return 1;
}

static int ext2_shrink_group(struct ext2_fs *fs, blk_t newsize)
{
      blk_t admin;
      int   group;
      blk_t groupoff;
      blk_t gblocks;
      blk_t i;

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_shrink_group\n");

      group = fs->numgroups - 1;

      admin = fs->adminblocks;
      if (!ext2_is_group_sparse(fs, group))
            admin -= fs->gdblocks + 1;

      groupoff = group * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb)
               + EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb);
      gblocks = EXT2_SUPER_BLOCKS_COUNT(fs->sb) - groupoff;

      if (fs->opt_debug)
      {
            if (newsize < admin)
            {
                  fprintf(stderr,
                        "ext2_shrink_group: cant shrink a group "
                        "to %i blocks\n", newsize);

                  return 0;
            }

            if (newsize > gblocks)
            {
                  fprintf(stderr,
                        "ext2_shrink_group: called to grow group!\n");

                  return 0;
            }

            if (gblocks == newsize)
            {
                  fprintf(stderr,
                        "ext2_shrink_group: nothing to do!\n");

                  return 0;
            }
      }

      for (i=newsize;i<gblocks;i++)
      {
            if (fs->opt_debug && ext2_get_block_state(fs, groupoff + i))
            {
                  fprintf(stderr,
                        "error: block relocator should have relocated "
                        "%i\n",
                        groupoff + i);

                  return 0;
            }

            ext2_set_block_state(fs, groupoff + i, 1, 0);
      }

      i = gblocks - newsize;
      fs->sb.s_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_BLOCKS_COUNT(fs->sb) - i);
      fs->sb.s_free_blocks_count = PED_CPU_TO_LE32(
            EXT2_SUPER_FREE_BLOCKS_COUNT(fs->sb) - i);
      fs->gd[group].bg_free_blocks_count = PED_CPU_TO_LE16(
            EXT2_GROUP_FREE_BLOCKS_COUNT(fs->gd[group]) - i);

      fs->metadirty |= EXT2_META_SB | EXT2_META_GD;

      if (fs->opt_safe)
            ext2_sync(fs);

      return 1;
}






static int ext2_grow_fs(struct ext2_fs *fs, blk_t newsize, PedTimer* timer)
{
      blk_t diff;
      blk_t sizelast;
      blk_t origsize = EXT2_SUPER_BLOCKS_COUNT(fs->sb);

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_grow_fs\n");

      if (!ext2_block_relocate(fs, newsize))
            return 0;

      if (!ext2_metadata_push(fs, newsize))
            return 0;

      diff = newsize - EXT2_SUPER_BLOCKS_COUNT(fs->sb);
      sizelast = EXT2_SUPER_BLOCKS_COUNT(fs->sb)
               - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb)
               - (fs->numgroups-1) * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

      if (sizelast != EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb))
      {
            blk_t growto;

            growto = sizelast + diff;
            if (growto > EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb))
                  growto = EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

            if (!ext2_grow_group(fs, growto))
                  return 0;

            diff -= growto - sizelast;
      }

      ped_timer_reset (timer);
      ped_timer_set_state_name (timer, _("adding groups"));

      while (diff)
      {
            ped_timer_update (timer,
                            1.0 - 1.0 * diff / (newsize - origsize));

            sizelast = PED_MIN(diff, EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb));
            if (!ext2_add_group(fs, sizelast))
                  return 0;

            diff -= sizelast;
      }

      ped_timer_update (timer, 1.0);

      return 1;
}

static int ext2_shrink_fs(struct ext2_fs *fs, blk_t newsize,
                    PedTimer* timer)
{
      blk_t origsize = EXT2_SUPER_BLOCKS_COUNT (fs->sb);
      blk_t diff;
      int newgroups;
      blk_t sizelast;

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_shrink_fs\n");

      newgroups = howmany(newsize - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb),
                      EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb));
      if (EXT2_SUPER_BLOCKS_COUNT(fs->sb)
          - EXT2_SUPER_FREE_BLOCKS_COUNT(fs->sb) > newsize)
      {
            ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                  _("Your filesystem is too occupied to resize it to %i "
                    "blocks. Sorry."), newsize);
            return 0;
      }

      if (EXT2_SUPER_INODES_COUNT(fs->sb)
          - EXT2_SUPER_FREE_INODES_COUNT(fs->sb)
                  > newgroups * EXT2_SUPER_INODES_PER_GROUP(fs->sb))
      {
            ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                  _("Your filesystem has too much occupied inodes to "
                    "resize it to %i blocks. Sorry."), newsize);
            return 0;
      }

      if (!ext2_inode_relocate(fs, newgroups))
            return 0;

      if (!ext2_block_relocate(fs, newsize))
            return 0;

      diff = EXT2_SUPER_BLOCKS_COUNT(fs->sb) - newsize;

      ped_timer_reset (timer);
      ped_timer_set_state_name (timer, _("shrinking"));

      while (diff > 0)
      {
            ped_timer_update (timer,
                          1.0 - 1.0 * diff / (origsize - newsize));

            sizelast = EXT2_SUPER_BLOCKS_COUNT(fs->sb)
                     - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb) -
                     (fs->numgroups - 1)
                        * EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);

            if (diff < sizelast)
            {
                  if (!ext2_shrink_group(fs, sizelast - diff))
                        return 0;

                  diff = 0;
            }
            else
            {
                  if (!ext2_del_group(fs))
                        return 0;

                  diff -= sizelast;
            }
      }
      
      ped_timer_update (timer, 1.0);

      return 1;
}


int ext2_resize_fs(struct ext2_fs *fs, blk_t newsize, PedTimer* timer)
{
      blk_t residue;
      int status;

      if (EXT2_SUPER_STATE(fs->sb) & EXT2_ERROR_FS)
      {
            ped_exception_throw (
                  PED_EXCEPTION_WARNING, PED_EXCEPTION_CANCEL,
                  _("Filesystem has errors!  You should run e2fsck."));
            return 0;
      }

      if (!(EXT2_SUPER_STATE(fs->sb) & EXT2_VALID_FS))
      {
            ped_exception_throw (
                  PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
                  _("Filesystem was not cleanly unmounted!  "
                    "You should e2fsck."));
            return 0;
      }

      if (EXT2_SUPER_FEATURE_COMPAT(fs->sb)
                  & EXT2_FEATURE_COMPAT_HAS_DIR_INDEX) {
            if (ped_exception_throw (
                  PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE_CANCEL,
                  _("The file system has the 'dir_index' feature "
                    "enabled.  Parted can only resize the file system "
                    "if it disables this feature.  You can enable it "
                    "later by running 'tune2fs -O dir_index DEVICE' "
                    "and then 'e2fsck -fD DEVICE'."))
                        != PED_EXCEPTION_IGNORE)
                  return 0;
            fs->sb.s_feature_compat
                  = PED_CPU_TO_LE32(EXT2_SUPER_FEATURE_COMPAT(fs->sb)
                                & ~EXT2_FEATURE_COMPAT_HAS_DIR_INDEX);
            fs->metadirty |= EXT2_META_SB;
      }

      if (fs->opt_verbose)
            fprintf(stderr, "ext2_resize_fs\n");

      residue = (newsize - EXT2_SUPER_FIRST_DATA_BLOCK(fs->sb))
               % EXT2_SUPER_BLOCKS_PER_GROUP(fs->sb);
      if (residue && residue <= fs->adminblocks)
            newsize -= residue;

      if (newsize == EXT2_SUPER_BLOCKS_COUNT(fs->sb))
            return 1;

      fs->relocator_pool
            = (unsigned char *)ped_malloc(ext2_relocator_pool_size << 10);
      if (!fs->relocator_pool)
            return 0;
      fs->relocator_pool_end
            = fs->relocator_pool + (ext2_relocator_pool_size << 10);

      if (newsize < EXT2_SUPER_BLOCKS_COUNT(fs->sb))
            status = ext2_shrink_fs(fs, newsize, timer);
      else
            status = ext2_grow_fs(fs, newsize, timer);

      ped_free(fs->relocator_pool);
      fs->relocator_pool = NULL;
      fs->relocator_pool_end = NULL;

      return status;
}
#endif /* !DISCOVER_ONLY */

Generated by  Doxygen 1.6.0   Back to index