root/src/add-ons/kernel/file_systems/ntfs/libntfs/lcnalloc.c
/**
 * lcnalloc.c - Cluster (de)allocation code. Originated from the Linux-NTFS project.
 *
 * Copyright (c) 2002-2004 Anton Altaparmakov
 * Copyright (c) 2004 Yura Pakhuchiy
 * Copyright (c) 2004-2008 Szabolcs Szakacsits
 * Copyright (c) 2008-2009 Jean-Pierre Andre
 *
 * This program/include file 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/include file 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 (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#include "types.h"
#include "attrib.h"
#include "bitmap.h"
#include "debug.h"
#include "runlist.h"
#include "volume.h"
#include "lcnalloc.h"
#include "logging.h"
#include "misc.h"

/*
 * Plenty possibilities for big optimizations all over in the cluster
 * allocation, however at the moment the dominant bottleneck (~ 90%) is 
 * the update of the mapping pairs which converges to the cubic Faulhaber's
 * formula as the function of the number of extents (fragments, runs).
 */
#define NTFS_LCNALLOC_BSIZE 4096
#define NTFS_LCNALLOC_SKIP  NTFS_LCNALLOC_BSIZE

enum {
        ZONE_MFT = 1,
        ZONE_DATA1 = 2,
        ZONE_DATA2 = 4
} ;

static void ntfs_cluster_set_zone_pos(LCN start, LCN end, LCN *pos, LCN tc)
{
        ntfs_log_trace("pos: %lld  tc: %lld\n", (long long)*pos, (long long)tc);

        if (tc >= end)
                *pos = start;
        else if (tc >= start)
                *pos = tc;
}

static void ntfs_cluster_update_zone_pos(ntfs_volume *vol, u8 zone, LCN tc)
{
        ntfs_log_trace("tc = %lld, zone = %d\n", (long long)tc, zone);
        
        if (zone == ZONE_MFT)
                ntfs_cluster_set_zone_pos(vol->mft_lcn, vol->mft_zone_end,
                                          &vol->mft_zone_pos, tc);
        else if (zone == ZONE_DATA1)
                ntfs_cluster_set_zone_pos(vol->mft_zone_end, vol->nr_clusters,
                                          &vol->data1_zone_pos, tc);
        else /* zone == ZONE_DATA2 */
                ntfs_cluster_set_zone_pos(0, vol->mft_zone_start, 
                                          &vol->data2_zone_pos, tc);
}

/*
 *              Unmark full zones when a cluster has been freed in a full zone
 *
 *      Next allocation will reuse the freed cluster
 */

static void update_full_status(ntfs_volume *vol, LCN lcn)
{
        if (lcn >= vol->mft_zone_end) {
                if (vol->full_zones & ZONE_DATA1) {
                        ntfs_cluster_update_zone_pos(vol, ZONE_DATA1, lcn);
                        vol->full_zones &= ~ZONE_DATA1;
                }
        } else
                if (lcn < vol->mft_zone_start) {
                        if (vol->full_zones & ZONE_DATA2) {
                                ntfs_cluster_update_zone_pos(vol, ZONE_DATA2, lcn);
                                vol->full_zones &= ~ZONE_DATA2;
                        }
                } else {
                        if (vol->full_zones & ZONE_MFT) {
                                ntfs_cluster_update_zone_pos(vol, ZONE_MFT, lcn);
                                vol->full_zones &= ~ZONE_MFT;
                        }
                }
}
 
static s64 max_empty_bit_range(unsigned char *buf, int size)
{
        int i, j, run = 0;
        int max_range = 0;
        s64 start_pos = -1;
        
        ntfs_log_trace("Entering\n");
        
        i = 0;
        while (i < size) {
                switch (*buf) {
                case 0 :
                        do {
                                buf++;
                                run += 8;
                                i++;
                        } while ((i < size) && !*buf);
                        break;
                case 255 :
                        if (run > max_range) {
                                max_range = run;
                                start_pos = (s64)i * 8 - run;
                        }
                        run = 0;
                        do {
                                buf++;
                                i++;
                        } while ((i < size) && (*buf == 255));
                        break;
                default :
                        for (j = 0; j < 8; j++) {
                        
                                int bit = *buf & (1 << j);
                
                                if (bit) {
                                        if (run > max_range) {
                                                max_range = run;
                                                start_pos = (s64)i * 8 + (j - run);
                                        }
                                        run = 0;
                                } else 
                                        run++;
                        }
                        i++;
                        buf++;
                
                }
        }
        
        if (run > max_range)
                start_pos = (s64)i * 8 - run;
        
        return start_pos;
}

static int bitmap_writeback(ntfs_volume *vol, s64 pos, s64 size, void *b, 
                            u8 *writeback)
{
        s64 written;
        
        ntfs_log_trace("Entering\n");
        
        if (!*writeback)
                return 0;
        
        *writeback = 0;
        
        written = ntfs_attr_pwrite(vol->lcnbmp_na, pos, size, b);
        if (written != size) {
                if (!written)
                        errno = EIO;
                ntfs_log_perror("Bitmap write error (%lld, %lld)",
                                (long long)pos, (long long)size);
                return -1;
        }

        return 0;
}

/**
 * ntfs_cluster_alloc - allocate clusters on an ntfs volume
 * @vol:        mounted ntfs volume on which to allocate the clusters
 * @start_vcn:  vcn to use for the first allocated cluster
 * @count:      number of clusters to allocate
 * @start_lcn:  starting lcn at which to allocate the clusters (or -1 if none)
 * @zone:       zone from which to allocate the clusters
 *
 * Allocate @count clusters preferably starting at cluster @start_lcn or at the
 * current allocator position if @start_lcn is -1, on the mounted ntfs volume
 * @vol. @zone is either DATA_ZONE for allocation of normal clusters and
 * MFT_ZONE for allocation of clusters for the master file table, i.e. the
 * $MFT/$DATA attribute.
 *
 * On success return a runlist describing the allocated cluster(s).
 *
 * On error return NULL with errno set to the error code.
 *
 * Notes on the allocation algorithm
 * =================================
 *
 * There are two data zones. First is the area between the end of the mft zone
 * and the end of the volume, and second is the area between the start of the
 * volume and the start of the mft zone. On unmodified/standard NTFS 1.x
 * volumes, the second data zone doesn't exist due to the mft zone being
 * expanded to cover the start of the volume in order to reserve space for the
 * mft bitmap attribute.
 *
 * The complexity stems from the need of implementing the mft vs data zoned 
 * approach and from the fact that we have access to the lcn bitmap via up to 
 * NTFS_LCNALLOC_BSIZE bytes at a time, so we need to cope with crossing over 
 * boundaries of two buffers. Further, the fact that the allocator allows for 
 * caller supplied hints as to the location of where allocation should begin 
 * and the fact that the allocator keeps track of where in the data zones the 
 * next natural allocation should occur, contribute to the complexity of the 
 * function. But it should all be worthwhile, because this allocator: 
 *   1) implements MFT zone reservation
 *   2) causes reduction in fragmentation. 
 * The code is not optimized for speed.
 */
runlist *ntfs_cluster_alloc(ntfs_volume *vol, VCN start_vcn, s64 count,
                LCN start_lcn, const NTFS_CLUSTER_ALLOCATION_ZONES zone)
{
        LCN zone_start, zone_end;  /* current search range */
        LCN last_read_pos, lcn;
        LCN bmp_pos;            /* current bit position inside the bitmap */
        LCN prev_lcn = 0, prev_run_len = 0;
        s64 clusters, br;
        runlist *rl = NULL, *trl;
        u8 *buf, *byte, bit, writeback;
        u8 pass = 1;    /* 1: inside zone;  2: start of zone */
        u8 search_zone; /* 4: data2 (start) 1: mft (middle) 2: data1 (end) */
        u8 done_zones = 0;
        u8 has_guess, used_zone_pos;
        int err = 0, rlpos, rlsize, buf_size;

        ntfs_log_enter("Entering with count = 0x%llx, start_lcn = 0x%llx, "
                       "zone = %s_ZONE.\n", (long long)count, (long long)
                       start_lcn, zone == MFT_ZONE ? "MFT" : "DATA");
        
        if (!vol || count < 0 || start_lcn < -1 || !vol->lcnbmp_na ||
                        (s8)zone < FIRST_ZONE || zone > LAST_ZONE) {
                errno = EINVAL;
                ntfs_log_perror("%s: vcn: %lld, count: %lld, lcn: %lld", 
                                __FUNCTION__, (long long)start_vcn, 
                                (long long)count, (long long)start_lcn);
                goto out;
        }

        /* Return empty runlist if @count == 0 */
        if (!count) {
                rl = ntfs_malloc(0x1000);
                if (rl) {
                        rl[0].vcn = start_vcn;
                        rl[0].lcn = LCN_RL_NOT_MAPPED;
                        rl[0].length = 0;
                }
                goto out;
        }

        buf = ntfs_malloc(NTFS_LCNALLOC_BSIZE);
        if (!buf)
                goto out;
        /*
         * If no @start_lcn was requested, use the current zone
         * position otherwise use the requested @start_lcn.
         */
        has_guess = 1;
        zone_start = start_lcn;
        
        if (zone_start < 0) {
                if (zone == DATA_ZONE)
                        zone_start = vol->data1_zone_pos;
                else
                        zone_start = vol->mft_zone_pos;
                has_guess = 0;
        }
        
        used_zone_pos = has_guess ? 0 : 1;
        
        if (!zone_start || zone_start == vol->mft_zone_start ||
                        zone_start == vol->mft_zone_end)
                pass = 2;
                
        if (zone_start < vol->mft_zone_start) {
                zone_end = vol->mft_zone_start;
                search_zone = ZONE_DATA2;
        } else if (zone_start < vol->mft_zone_end) {
                zone_end = vol->mft_zone_end;
                search_zone = ZONE_MFT;
        } else {
                zone_end = vol->nr_clusters;
                search_zone = ZONE_DATA1;
        }
        
        bmp_pos = zone_start;

        /* Loop until all clusters are allocated. */
        clusters = count;
        rlpos = rlsize = 0;
        while (1) {
                        /* check whether we have exhausted the current zone */
                if (search_zone & vol->full_zones)
                        goto zone_pass_done;
                last_read_pos = bmp_pos >> 3;
                br = ntfs_attr_pread(vol->lcnbmp_na, last_read_pos, 
                                     NTFS_LCNALLOC_BSIZE, buf);
                if (br <= 0) {
                        if (!br)
                                goto zone_pass_done;
                        err = errno;
                        ntfs_log_perror("Reading $BITMAP failed");
                        goto err_ret;
                }
                /*
                 * We might have read less than NTFS_LCNALLOC_BSIZE bytes
                 * if we are close to the end of the attribute.
                 */
                buf_size = (int)br << 3;
                lcn = bmp_pos & 7;
                bmp_pos &= ~7;
                writeback = 0;
                
                while (lcn < buf_size) {
                        byte = buf + (lcn >> 3);
                        bit = 1 << (lcn & 7);
                        if (has_guess) {
                                if (*byte & bit) {
                                        has_guess = 0;
                                        break;
                                }
                        } else {
                                lcn = max_empty_bit_range(buf, br);
                                if (lcn < 0)
                                        break;
                                has_guess = 1;
                                continue;
                        }

                        /* First free bit is at lcn + bmp_pos. */
                         
                        /* Reallocate memory if necessary. */
                        if ((rlpos + 2) * (int)sizeof(runlist) >= rlsize) {
                                rlsize += 4096;
                                trl = realloc(rl, rlsize);
                                if (!trl) {
                                        err = ENOMEM;
                                        ntfs_log_perror("realloc() failed");
                                        goto wb_err_ret;
                                }
                                rl = trl;
                        }
                        
                        /* Allocate the bitmap bit. */
                        *byte |= bit;
                        writeback = 1;
                        if (NVolFreeSpaceKnown(vol)) {
                                if (vol->free_clusters <= 0)
                                        ntfs_log_error("Non-positive free"
                                               " clusters (%lld)!\n",
                                                (long long)vol->free_clusters);
                                else    
                                        vol->free_clusters--;
                        }
                        
                        /*
                         * Coalesce with previous run if adjacent LCNs.
                         * Otherwise, append a new run.
                         */
                        if (prev_lcn == lcn + bmp_pos - prev_run_len && rlpos) {
                                ntfs_log_debug("Cluster coalesce: prev_lcn: "
                                               "%lld  lcn: %lld  bmp_pos: %lld  "
                                               "prev_run_len: %lld\n", 
                                               (long long)prev_lcn, 
                                               (long long)lcn, (long long)bmp_pos, 
                                               (long long)prev_run_len);
                                rl[rlpos - 1].length = ++prev_run_len;
                        } else {
                                if (rlpos)
                                        rl[rlpos].vcn = rl[rlpos - 1].vcn +
                                                        prev_run_len;
                                else {
                                        rl[rlpos].vcn = start_vcn;
                                        ntfs_log_debug("Start_vcn: %lld\n", 
                                                       (long long)start_vcn);
                                }
                                
                                rl[rlpos].lcn = prev_lcn = lcn + bmp_pos;
                                rl[rlpos].length = prev_run_len = 1;
                                rlpos++;
                        }
                        
                        ntfs_log_debug("RUN:   %-16lld %-16lld %-16lld\n", 
                                       (long long)rl[rlpos - 1].vcn, 
                                       (long long)rl[rlpos - 1].lcn, 
                                       (long long)rl[rlpos - 1].length);
                        /* Done? */
                        if (!--clusters) {
                                if (used_zone_pos)
                                        ntfs_cluster_update_zone_pos(vol, 
                                                search_zone, lcn + bmp_pos + 1 +
                                                        NTFS_LCNALLOC_SKIP);
                                goto done_ret;
                        }
                        
                        lcn++;
                }
                
                if (bitmap_writeback(vol, last_read_pos, br, buf, &writeback)) {
                        err = errno;
                        goto err_ret;
                }
                
                if (!used_zone_pos) {
                        
                        used_zone_pos = 1;
                        
                        if (search_zone == ZONE_MFT)
                                zone_start = vol->mft_zone_pos;
                        else if (search_zone == ZONE_DATA1)
                                zone_start = vol->data1_zone_pos;
                        else
                                zone_start = vol->data2_zone_pos;
                        
                        if (!zone_start || zone_start == vol->mft_zone_start ||
                                        zone_start == vol->mft_zone_end)
                                pass = 2;
                        bmp_pos = zone_start;
                } else
                        bmp_pos += buf_size;
                
                if (bmp_pos < zone_end)
                        continue;

zone_pass_done:
                ntfs_log_trace("Finished current zone pass(%i).\n", pass);
                if (pass == 1) {
                        pass = 2;
                        zone_end = zone_start;
                        
                        if (search_zone == ZONE_MFT)
                                zone_start = vol->mft_zone_start;
                        else if (search_zone == ZONE_DATA1)
                                zone_start = vol->mft_zone_end;
                        else
                                zone_start = 0;
                        
                        /* Sanity check. */
                        if (zone_end < zone_start)
                                zone_end = zone_start;
                        
                        bmp_pos = zone_start;
                        
                        continue;
                } 
                /* pass == 2 */
done_zones_check:
                done_zones |= search_zone;
                vol->full_zones |= search_zone;
                if (done_zones < (ZONE_MFT + ZONE_DATA1 + ZONE_DATA2)) {
                        ntfs_log_trace("Switching zone.\n");
                        pass = 1;
                        if (rlpos) {
                                LCN tc = rl[rlpos - 1].lcn + 
                                      rl[rlpos - 1].length + NTFS_LCNALLOC_SKIP;
                                
                                if (used_zone_pos)
                                        ntfs_cluster_update_zone_pos(vol, 
                                                search_zone, tc);
                        }
                        
                        switch (search_zone) {
                        case ZONE_MFT:
                                ntfs_log_trace("Zone switch: mft -> data1\n");
switch_to_data1_zone:           search_zone = ZONE_DATA1;
                                zone_start = vol->data1_zone_pos;
                                zone_end = vol->nr_clusters;
                                if (zone_start == vol->mft_zone_end)
                                        pass = 2;
                                break;
                        case ZONE_DATA1:
                                ntfs_log_trace("Zone switch: data1 -> data2\n");
                                search_zone = ZONE_DATA2;
                                zone_start = vol->data2_zone_pos;
                                zone_end = vol->mft_zone_start;
                                if (!zone_start)
                                        pass = 2;
                                break;
                        case ZONE_DATA2:
                                if (!(done_zones & ZONE_DATA1)) {
                                        ntfs_log_trace("data2 -> data1\n");
                                        goto switch_to_data1_zone;
                                }
                                ntfs_log_trace("Zone switch: data2 -> mft\n");
                                search_zone = ZONE_MFT;
                                zone_start = vol->mft_zone_pos;
                                zone_end = vol->mft_zone_end;
                                if (zone_start == vol->mft_zone_start)
                                        pass = 2;
                                break;
                        }
                        
                        bmp_pos = zone_start;
                        
                        if (zone_start == zone_end) {
                                ntfs_log_trace("Empty zone, skipped.\n");
                                goto done_zones_check;
                        }
                        
                        continue;
                }
                
                ntfs_log_trace("All zones are finished, no space on device.\n");
                err = ENOSPC;
                goto err_ret;
        }
done_ret:
        ntfs_log_debug("At done_ret.\n");
        /* Add runlist terminator element. */
        rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length;
        rl[rlpos].lcn = LCN_RL_NOT_MAPPED;
        rl[rlpos].length = 0;
        if (bitmap_writeback(vol, last_read_pos, br, buf, &writeback)) {
                err = errno;
                goto err_ret;
        }
done_err_ret:
        free(buf);
        if (err) {
                errno = err;
                ntfs_log_perror("Failed to allocate clusters");
                rl = NULL;
        }
out:    
        ntfs_log_leave("\n");
        return rl;

wb_err_ret:
        ntfs_log_trace("At wb_err_ret.\n");
        if (bitmap_writeback(vol, last_read_pos, br, buf, &writeback))
                err = errno;
err_ret:
        ntfs_log_trace("At err_ret.\n");
        if (rl) {
                /* Add runlist terminator element. */
                rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length;
                rl[rlpos].lcn = LCN_RL_NOT_MAPPED;
                rl[rlpos].length = 0;
                ntfs_debug_runlist_dump(rl);
                ntfs_cluster_free_from_rl(vol, rl);
                free(rl);
                rl = NULL;
        }
        goto done_err_ret;
}

/**
 * ntfs_cluster_free_from_rl - free clusters from runlist
 * @vol:        mounted ntfs volume on which to free the clusters
 * @rl:         runlist from which deallocate clusters
 *
 * On success return 0 and on error return -1 with errno set to the error code.
 */
int ntfs_cluster_free_from_rl(ntfs_volume *vol, runlist *rl)
{
        s64 nr_freed = 0;
        int ret = -1;

        ntfs_log_trace("Entering.\n");

        for (; rl->length; rl++) {

                ntfs_log_trace("Dealloc lcn 0x%llx, len 0x%llx.\n",
                               (long long)rl->lcn, (long long)rl->length);

                if (rl->lcn >= 0) { 
                        update_full_status(vol,rl->lcn);
                        if (ntfs_bitmap_clear_run(vol->lcnbmp_na, rl->lcn, 
                                                  rl->length)) {
                                ntfs_log_perror("Cluster deallocation failed "
                                               "(%lld, %lld)",
                                                (long long)rl->lcn, 
                                                (long long)rl->length);
                                goto out;
                        }
                        nr_freed += rl->length ; 
                }
        }

        ret = 0;
out:
        vol->free_clusters += nr_freed; 
        if (NVolFreeSpaceKnown(vol)
            && (vol->free_clusters > vol->nr_clusters))
                ntfs_log_error("Too many free clusters (%lld > %lld)!",
                               (long long)vol->free_clusters, 
                               (long long)vol->nr_clusters);
        return ret;
}

/*
 *              Basic cluster run free
 *      Returns 0 if successful
 */

int ntfs_cluster_free_basic(ntfs_volume *vol, s64 lcn, s64 count)
{
        s64 nr_freed = 0;
        int ret = -1;

        ntfs_log_trace("Entering.\n");
        ntfs_log_trace("Dealloc lcn 0x%llx, len 0x%llx.\n",
                               (long long)lcn, (long long)count);

        if (lcn >= 0) { 
                update_full_status(vol,lcn);
                if (ntfs_bitmap_clear_run(vol->lcnbmp_na, lcn, 
                                                  count)) {
                        ntfs_log_perror("Cluster deallocation failed "
                                       "(%lld, %lld)",
                                        (long long)lcn, 
                                        (long long)count);
                                goto out;
                }
                nr_freed += count; 
        }
        ret = 0;
out:
        vol->free_clusters += nr_freed;
        if (vol->free_clusters > vol->nr_clusters)
                ntfs_log_error("Too many free clusters (%lld > %lld)!",
                               (long long)vol->free_clusters, 
                               (long long)vol->nr_clusters);
        return ret;
}

/**
 * ntfs_cluster_free - free clusters on an ntfs volume
 * @vol:        mounted ntfs volume on which to free the clusters
 * @na:         attribute whose runlist describes the clusters to free
 * @start_vcn:  vcn in @rl at which to start freeing clusters
 * @count:      number of clusters to free or -1 for all clusters
 *
 * Free @count clusters starting at the cluster @start_vcn in the runlist
 * described by the attribute @na from the mounted ntfs volume @vol.
 *
 * If @count is -1, all clusters from @start_vcn to the end of the runlist
 * are deallocated.
 *
 * On success return the number of deallocated clusters (not counting sparse
 * clusters) and on error return -1 with errno set to the error code.
 */
int ntfs_cluster_free(ntfs_volume *vol, ntfs_attr *na, VCN start_vcn, s64 count)
{
        runlist *rl;
        s64 delta, to_free, nr_freed = 0;
        int ret = -1;

        if (!vol || !vol->lcnbmp_na || !na || start_vcn < 0 ||
                        (count < 0 && count != -1)) {
                ntfs_log_trace("Invalid arguments!\n");
                errno = EINVAL;
                return -1;
        }
        
        ntfs_log_enter("Entering for inode 0x%llx, attr 0x%x, count 0x%llx, "
                       "vcn 0x%llx.\n", (unsigned long long)na->ni->mft_no,
                       le32_to_cpu(na->type), (long long)count, (long long)start_vcn);

        rl = ntfs_attr_find_vcn(na, start_vcn);
        if (!rl) {
                if (errno == ENOENT)
                        ret = 0;
                goto leave;
        }

        if (rl->lcn < 0 && rl->lcn != LCN_HOLE) {
                errno = EIO;
                ntfs_log_perror("%s: Unexpected lcn (%lld)", __FUNCTION__, 
                                (long long)rl->lcn);
                goto leave;
        }

        /* Find the starting cluster inside the run that needs freeing. */
        delta = start_vcn - rl->vcn;

        /* The number of clusters in this run that need freeing. */
        to_free = rl->length - delta;
        if (count >= 0 && to_free > count)
                to_free = count;

        if (rl->lcn != LCN_HOLE) {
                /* Do the actual freeing of the clusters in this run. */
                update_full_status(vol,rl->lcn + delta);
                if (ntfs_bitmap_clear_run(vol->lcnbmp_na, rl->lcn + delta,
                                          to_free))
                        goto leave;
                nr_freed = to_free;
        } 

        /* Go to the next run and adjust the number of clusters left to free. */
        ++rl;
        if (count >= 0)
                count -= to_free;

        /*
         * Loop over the remaining runs, using @count as a capping value, and
         * free them.
         */
        for (; rl->length && count != 0; ++rl) {
                // FIXME: Need to try ntfs_attr_map_runlist() for attribute
                //        list support! (AIA)
                if (rl->lcn < 0 && rl->lcn != LCN_HOLE) {
                        // FIXME: Eeek! We need rollback! (AIA)
                        errno = EIO;
                        ntfs_log_perror("%s: Invalid lcn (%lli)", 
                                        __FUNCTION__, (long long)rl->lcn);
                        goto out;
                }

                /* The number of clusters in this run that need freeing. */
                to_free = rl->length;
                if (count >= 0 && to_free > count)
                        to_free = count;

                if (rl->lcn != LCN_HOLE) {
                        update_full_status(vol,rl->lcn);
                        if (ntfs_bitmap_clear_run(vol->lcnbmp_na, rl->lcn,
                                        to_free)) {
                                // FIXME: Eeek! We need rollback! (AIA)
                                ntfs_log_perror("%s: Clearing bitmap run failed",
                                                __FUNCTION__);
                                goto out;
                        }
                        nr_freed += to_free;
                }

                if (count >= 0)
                        count -= to_free;
        }

        if (count != -1 && count != 0) {
                // FIXME: Eeek! BUG()
                errno = EIO;
                ntfs_log_perror("%s: count still not zero (%lld)", __FUNCTION__,
                               (long long)count);
                goto out;
        }

        ret = nr_freed;
out:
        vol->free_clusters += nr_freed ; 
        if (vol->free_clusters > vol->nr_clusters)
                ntfs_log_error("Too many free clusters (%lld > %lld)!",
                               (long long)vol->free_clusters, 
                               (long long)vol->nr_clusters);
leave:  
        ntfs_log_leave("\n");
        return ret;
}