memmanage 46.3 KB
Newer Older
Neil Turton's avatar
Neil Turton committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
     File and memory management for Filer_Action

Revision History:

0.07  13-May-90  ARW  Totally new implementation to old interface
*/

#define Timing 0

/*

Theory:

(1) read blocks (or all) of source file(s) into a contiguous chunk of store
    - extend as necessary up to next slot size
31
    - be prepared to lose information if slot size has to be shrunk
Neil Turton's avatar
Neil Turton committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

(2) write blocks to destination file(s)

(3) repeat until done

Notes:

(a) dynamically resize the block quanta for source and destination according
    to the real time it took: its programmed to update the user information
    (or allow other tasks in) around time_quanta (may get to 2*time_quanta).
    Hysteresis built in to the change in block size:
    - growth: <4096: +1024 AND NOT 1023
              <16K:  +4K AND NOT (4K-1)
              else:  double size
    - shrink: <4096: -256
              <16K:  -1024
              else:  -(quarter size)
    Initial block size is initial_block_size for both (=4096?)

(b) if a file is smaller than src_block, it is *loaded
    if a file is smaller than dest_block, it is *saved
    - these operations are not checked against time_quanta

(c) care needed to ensure that a contiguous block exists and can be moved
    by memcpy when it needs to be (user changes slot allocation - "action_slot").

 JRS 17/1/92 Measure the average transfer rate at the initial block size. If the blocksize
 is reduced below a certain critical value, then the transfer rate is compared with the initial
 value and if it is substantially worse, the value of time_quanta is increased, to allow
 blocksize to increase and get a better transfer rate at the expense of 'lumpy' response.
 Added hysteresis to time_quanta to prevent it always growing and shrinking

*/

#if 0
67 68 69
#define debugmem(k) dprintf k
#else
#define debugmem(k) /* Disabled */
Neil Turton's avatar
Neil Turton committed
70 71 72 73
#endif

#include <stdio.h>
#include <string.h>
74
#include <stdint.h>
Neil Turton's avatar
Neil Turton committed
75 76 77 78
#include <time.h>
#include <stdlib.h>

#include "kernel.h"
79
#include "Interface/HighFSI.h"
Neil Turton's avatar
Neil Turton committed
80 81 82 83

#include "os.h"
#include "wimp.h"

84
#include "Options.h"
Neil Turton's avatar
Neil Turton committed
85 86 87 88 89 90 91
#include "chains.h"
#include "allerrs.h"
#include "malloc+.h"
#include "memmanage.h"
#include "debug.h"

#if Timing
92
static clock_t copytime;
Neil Turton's avatar
Neil Turton committed
93 94 95
#endif

#define No 0
96
#define Yes (!No)
Neil Turton's avatar
Neil Turton committed
97 98 99 100 101 102 103 104 105 106 107 108 109

#define InitialBlockSize 4096
#define SmallBlockSize   3072 /* check the transfer rate is not too low if the blocksize reduces below this */
#define MinimumWorkingSize InitialBlockSize*3

#define NominalTimeQuanta 25 /* cs */
#define MaxTimeQuanta    100
#define TimeQuantaHysteresis 3

/*
     The file headers are held in a doubly linked list, on the malloc+ heap
*/
typedef enum {
110 111 112 113 114 115 116 117 118 119 120
    write_not_started,
    write_f_tried_unlock,   /* open/save failed - have tried to unlock it */
    write_f_tried_cdirs,    /* open/save failed - have tried to cdir it */
    write_f_tried_both,     /* open/save failed - have tried both cdirs and unlock */
    write_openit,           /* have created it, now open it */
    write_truncateopen,     /* have opened it now truncate it */
    write_blocks,           /* next thing is writing blocks out */
    write_closeit,          /* next thing is to close it */
    write_adjust_access,    /* next thing is adjust the access */
    write_adjust_info,      /* next thing is adjust the info */
    write_all_done          /* and junk the fh */
121
}   write_activity;
Neil Turton's avatar
Neil Turton committed
122 123 124

typedef struct
{
125 126 127 128 129
    chain_link      link;
    char            *source_filename;
    int             source_file_handle;
    char            *destination_filename;
    int             destination_file_handle;
130
    uint32_t        size;
131 132 133 134
    int             reload;
    int             execute;
    int             attributes;
    int             objecttype;
135 136 137
    BOOL            forceit;
    uint32_t        read_to;
    uint32_t        written_to;
138 139 140
    write_activity  write_state;
    int             start_of_buffer;        /* offset into memory */

141
#ifdef USE_PROGRESS_BAR
Robert Sprowson's avatar
Robert Sprowson committed
142 143 144
    uint32_t        total_progress;
    uint32_t        read_progress;
    uint32_t        write_progress;
145
#endif
146
}   files_header;
Neil Turton's avatar
Neil Turton committed
147 148 149 150 151 152 153

/*
     Structures for copying files
*/
static chain_header files_chain;        /* All the files registered with memmanage */
static int application_max_size;
static char *buffer_base_address;       /* Base of buffer area */
Robert Sprowson's avatar
Robert Sprowson committed
154 155
static int buffer_offset;               /* offset to add to bottom, top, and any other offsets into the area */
                                        /* used when the area contents are shuffled */
Neil Turton's avatar
Neil Turton committed
156 157 158 159 160 161 162
static int buffer_bottom;               /* offset to 1st used byte in buffer area */
static int buffer_top;                  /* offset to top free section in buffer area */
static int buffer_end;                  /* offset to end of buffer area */
static int src_block_size;
static int dest_block_size;
static int time_quanta = NominalTimeQuanta;
static int minimum_block_size = 512;
163 164 165

BOOL  finished_obj_was_file;
char *finished_obj_source_name;
Neil Turton's avatar
Neil Turton committed
166 167 168 169 170 171 172 173 174 175 176 177 178

/* JRS addition 16/1/92 */
/* accumulate transfer times at initial blocksize to compare timings at smaller
 * blocksizes to detect when the time per byte increases unacceptably. This triggers
 * an increase in time_quanta which causes the blocksize to grow and the overall
 * transfer rate to improve */

#define timing_Reading 0
#define timing_Writing 1

#define timing_NAccumulated 4  /* accumulate this many timings to average out variation */

static struct
Robert Sprowson's avatar
Robert Sprowson committed
179 180 181 182 183
{
    int rate; /* transfer rate calculated from accumulator and blocksize */
    int accumulator;
    int naccumulated;
}   timing[2]; /* read and write initial timing */
184 185 186
/*
     Rate evaluation functions
*/
Neil Turton's avatar
Neil Turton committed
187 188 189

static void timing_init(void)
{
190 191 192 193 194 195 196
    int rw;
    for (rw = 0; rw < 2; rw++) /* reading and writing */
    {
        timing[rw].rate = 0;
        timing[rw].accumulator = 0;
        timing[rw].naccumulated = 0;
    }
Neil Turton's avatar
Neil Turton committed
197 198 199 200
}

static int t__accumulatortorate(int accumulated_time, int blocksize)
{ /* convert an accumulated timing to a transfer rate value */
201
    if (accumulated_time < 1) accumulated_time = 1; /* ensure no div by zero */
202 203
    debugmem(( "t__accumulatortorate: %d,%d->%d\n", accumulated_time, blocksize,(blocksize<<8) / accumulated_time));

204
    return (blocksize<<8) / accumulated_time; /* shift is to gain integer value range */
Neil Turton's avatar
Neil Turton committed
205 206 207 208 209
}

static void timing_accumulate(int rw, int t, int blocksize)
/* accumulate a timing for read or write, return Yes if it is full (enough have been accumulated) */
{
210 211 212 213 214 215 216 217 218
    assert((rw==timing_Reading)||(rw==timing_Writing));
    assert(timing[rw].naccumulated < timing_NAccumulated);
    timing[rw].naccumulated++;
    timing[rw].accumulator += t;
    if ( timing[rw].naccumulated >= timing_NAccumulated )
    { /* accumulator is full and rate is not yet calculated; calculate rate */
        assert(timing[rw].rate == 0);
        timing[rw].rate = t__accumulatortorate(timing[rw].accumulator, blocksize);
    }
Neil Turton's avatar
Neil Turton committed
219 220 221 222
}

static int timing_accumulating(int rw)
{
223 224
    assert((rw==timing_Reading)||(rw==timing_Writing));
    return (timing[rw].naccumulated < timing_NAccumulated);
Neil Turton's avatar
Neil Turton committed
225 226 227 228 229
}

static int timing_poorrate(int rw, int t, int blocksize)
{ /* compare the given time, converted to a rate against the rate for the initial blocksize.
   * Return Yes if it is worse than 2/3 of the initial rate */
230 231
    assert((rw==timing_Reading)||(rw==timing_Writing));
    return ( t__accumulatortorate(t * timing_NAccumulated, blocksize) <  (2*timing[rw].rate)/3 );
Neil Turton's avatar
Neil Turton committed
232 233 234 235 236
}


static int application_current_size( void )
{
237 238 239
    int currentslot = -1;
    int nextslot = -1;
    int freepool;
Neil Turton's avatar
Neil Turton committed
240

241
    wimp_slotsize( &currentslot, &nextslot, &freepool );
Neil Turton's avatar
Neil Turton committed
242

243
    return currentslot;
Neil Turton's avatar
Neil Turton committed
244 245 246 247
}

static int current_next_slot( void )
{
248 249 250
    int currentslot = -1;
    int nextslot = -1;
    int freepool;
Neil Turton's avatar
Neil Turton committed
251

252
    wimp_slotsize( &currentslot, &nextslot, &freepool );
Neil Turton's avatar
Neil Turton committed
253

254
    return nextslot;
Neil Turton's avatar
Neil Turton committed
255 256 257
}

/*
258
    returns amount actually changed by
Neil Turton's avatar
Neil Turton committed
259 260 261
*/
static int extend_application_slot( int amount )
{
262 263 264 265
    int oldslot;
    int currentslot;
    int nextslot = -1;
    int freepool;
Neil Turton's avatar
Neil Turton committed
266

267 268 269
    if ( amount != 0 )
    {
        oldslot = application_current_size();
Neil Turton's avatar
Neil Turton committed
270

271
        currentslot = oldslot + amount;
Neil Turton's avatar
Neil Turton committed
272

273
        wimp_slotsize( &currentslot, &nextslot, &freepool );
Neil Turton's avatar
Neil Turton committed
274

275
        debugmem(( "extend_application_slot: amount = %d, new size = %d\n",amount,currentslot));
Neil Turton's avatar
Neil Turton committed
276

277 278 279 280 281 282
        return currentslot - oldslot;
    }
    else
    {
        return 0;
    }
Neil Turton's avatar
Neil Turton committed
283 284 285
}

/*
286
    Ensure that the top to end amount free is at least size
Neil Turton's avatar
Neil Turton committed
287
*/
Robert Sprowson's avatar
Robert Sprowson committed
288
static BOOL buffer_ensure( int size )
Neil Turton's avatar
Neil Turton committed
289
{
Robert Sprowson's avatar
Robert Sprowson committed
290
    if ( buffer_end - buffer_top < size )
291
    {
Robert Sprowson's avatar
Robert Sprowson committed
292
        buffer_end += extend_application_slot( size - (buffer_end - buffer_top) );
293
    }
Neil Turton's avatar
Neil Turton committed
294

Robert Sprowson's avatar
Robert Sprowson committed
295
    return buffer_end - buffer_top >= size;
Neil Turton's avatar
Neil Turton committed
296 297 298
}

/*
299 300 301
    Grow the buffer so that "size" is available, limiting by application_max_siz.
    Reduces *size to fit space.
    Returns Yes if not enough space available for original requested *size.
Neil Turton's avatar
Neil Turton committed
302 303 304
*/
static int grow_buffer( int *size )
{
305
    BOOL answer = No;
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327

    /*
        Upper bound by application_max_size
    */
    if ( (int)(buffer_base_address + *size + buffer_top + buffer_offset) > application_max_size + 0x8000 )
    {
        *size = application_max_size + 0x8000 - (int)(buffer_base_address + buffer_top + buffer_offset);
        answer = Yes;
    }

    /*
        Try to ensure that much space
    */
    answer = answer || !buffer_ensure( *size );

    if ( buffer_end - buffer_top < *size )
    {
        *size = buffer_end - buffer_top;
        answer = Yes;
    }

    return answer;
Neil Turton's avatar
Neil Turton committed
328 329 330 331
}

static os_error *close_file( int *handle )
{
332 333
    os_regset r;
    os_error *err;
Neil Turton's avatar
Neil Turton committed
334

335 336
    r.r[0] = 0; /* close file */
    r.r[1] = *handle;
Neil Turton's avatar
Neil Turton committed
337

338
    err = os_find( &r );
Neil Turton's avatar
Neil Turton committed
339

340 341 342 343
    /*
         Even a failed close leaves the file closed afterwards
    */
    *handle = 0;
Neil Turton's avatar
Neil Turton committed
344

345
    return err;
Neil Turton's avatar
Neil Turton committed
346 347 348 349 350 351
}

/*
     Creates the file to be the given size, dead, with read/write
     access only.
*/
Robert Sprowson's avatar
Robert Sprowson committed
352
static os_error *create_file_dead( char *filename, uint32_t size )
Neil Turton's avatar
Neil Turton committed
353
{
354 355
    os_filestr fileplace;
    os_error *err;
Neil Turton's avatar
Neil Turton committed
356

357
    fileplace.action   = OSFile_Create;
358 359 360 361
    fileplace.name     = filename;
    fileplace.loadaddr = (int) 0xdeaddead;
    fileplace.execaddr = (int) 0xdeaddead;
    fileplace.start    = 0;
Robert Sprowson's avatar
Robert Sprowson committed
362
    fileplace.end      = (int)size;
Neil Turton's avatar
Neil Turton committed
363

364
    err = os_file( &fileplace );
Neil Turton's avatar
Neil Turton committed
365

366 367
    if ( err )
         return err;
Neil Turton's avatar
Neil Turton committed
368

369
    fileplace.action = OSFile_WriteAttr;
370
    fileplace.name = filename;
371
    fileplace.end = read_attribute | write_attribute;
Neil Turton's avatar
Neil Turton committed
372

373
    return os_file( &fileplace );
Neil Turton's avatar
Neil Turton committed
374 375 376 377
}

static os_error *create_directory( char *dirname )
{
378
    os_filestr fileplace;
Neil Turton's avatar
Neil Turton committed
379

380
    fileplace.action = OSFile_CreateDir;
381 382
    fileplace.name = dirname;
    fileplace.start = 0;
Neil Turton's avatar
Neil Turton committed
383

384
    return os_file( &fileplace );
Neil Turton's avatar
Neil Turton committed
385 386 387 388
}

static os_error *write_catalogue_information( char *filename, int reload, int execute, int attributes )
{
389
    os_filestr fileplace;
Neil Turton's avatar
Neil Turton committed
390

391
    fileplace.action    = OSFile_WriteInfo;
392 393 394 395
    fileplace.name      = filename;
    fileplace.loadaddr  = reload;
    fileplace.execaddr  = execute;
    fileplace.end       = attributes;
Neil Turton's avatar
Neil Turton committed
396

397
    return os_file( &fileplace );
Neil Turton's avatar
Neil Turton committed
398 399 400 401
}

static os_error *write_attributes( char *filename, int attributes )
{
402
    os_filestr fileplace;
Neil Turton's avatar
Neil Turton committed
403

404
    fileplace.action    = OSFile_WriteAttr;
405 406
    fileplace.name      = filename;
    fileplace.end       = attributes;
Neil Turton's avatar
Neil Turton committed
407

408
    return os_file( &fileplace );
Neil Turton's avatar
Neil Turton committed
409 410 411 412
}

static char *first_dirbreak( char *filename )
{
413 414 415 416 417 418 419 420
    char *trypos;
    char *endpos;

    /*
        Point trypos past the -<fsname>- / <fsname>: bit
    */
    if ( filename[0] == '-' )
    {
Neil Turton's avatar
Neil Turton committed
421
        /*
422
            -<fsname>-
Neil Turton's avatar
Neil Turton committed
423
        */
424 425 426 427 428 429 430 431 432 433
        trypos = strchr( filename + 1, '-' ) + 1;
    }
    else if ( filename[0] != ':' )
    {
        /*
            <fsname>: perhaps
        */
        trypos = strchr( filename, ':' );

        if ( trypos == NULL )
Neil Turton's avatar
Neil Turton committed
434
        {
435 436 437 438
            /*
                 It wasn't <fsname>:, never mind
            */
            trypos = filename;
Neil Turton's avatar
Neil Turton committed
439
        }
440
        else
Neil Turton's avatar
Neil Turton committed
441
        {
442
            trypos++;
Neil Turton's avatar
Neil Turton committed
443
        }
444 445 446 447 448 449 450 451 452 453 454 455 456 457
    }
    else
    {
        trypos = filename;
    }

    /*
        Move trypos past the :<devname> or :<devname>. bit if it's there
    */
    if ( *trypos == ':' )
    {
        endpos = strchr( trypos, '.' );

        if ( endpos == NULL )
Neil Turton's avatar
Neil Turton committed
458
        {
459
            trypos = trypos + strlen( trypos );
Neil Turton's avatar
Neil Turton committed
460
        }
461
        else
Neil Turton's avatar
Neil Turton committed
462
        {
463
            trypos = endpos + 1;
Neil Turton's avatar
Neil Turton committed
464
        }
465 466 467 468 469 470 471 472
    }

    /*
         Move past the first component (and any $. &. %. @. or \.)
    */
    if ( *trypos != '\0' )
    {
        switch( *trypos )
Neil Turton's avatar
Neil Turton committed
473
        {
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
        case '$':
        case '&':
        case '%':
        case '@':
        case '\\':

            trypos ++;

            if ( *trypos == '.' )
                trypos++;

             /*
                Fall through to skipping over the next component
             */

        default:
            endpos = strchr( trypos, '.' );

            if ( endpos == NULL )
            {
                trypos = trypos + strlen( trypos );
            }
            else
            {
                trypos = endpos;
            }

            break;
Neil Turton's avatar
Neil Turton committed
502
        }
503
    }
Neil Turton's avatar
Neil Turton committed
504

505 506 507 508
    /*
         trypos now points past the first non-special component
         of the filename or at the terminating nul
    */
Neil Turton's avatar
Neil Turton committed
509

510
    return trypos;
Neil Turton's avatar
Neil Turton committed
511 512 513 514
}

static os_error *ensure_file_directories( char *filename )
{
515 516
    char *currpos;
    os_error *err;
Neil Turton's avatar
Neil Turton committed
517

518 519 520 521 522
    for ( currpos = first_dirbreak( filename );
          currpos && *currpos;
          currpos = strchr( currpos + 1, '.' ) )
    {
        *currpos = '\0';
Neil Turton's avatar
Neil Turton committed
523

524
        err = create_directory( filename );
Neil Turton's avatar
Neil Turton committed
525

526
        *currpos = '.';
Neil Turton's avatar
Neil Turton committed
527

528 529 530
        if ( err )
            return err;
    }
Neil Turton's avatar
Neil Turton committed
531

532
    return NULL;
Neil Turton's avatar
Neil Turton committed
533 534 535 536 537 538 539 540 541
}

/*
     Ensures files used by fh are closed, and ignores any errors it gets
     back. The destination file, if closed, will be truncated to the
     written_to size, and set to dead.
*/
static void ensure_files_closed( files_header *fh )
{
542 543
    if ( fh->destination_file_handle )
        close_file( &fh->destination_file_handle );
Neil Turton's avatar
Neil Turton committed
544

545 546
    if ( fh->source_file_handle )
        close_file( &fh->source_file_handle );
Neil Turton's avatar
Neil Turton committed
547 548 549 550 551 552 553
}

/*
     Totally junk an fh: remove it from the files chain
*/
static void remove_fh( files_header *fh )
{
554
    chain_remove_link( &fh->link );
Neil Turton's avatar
Neil Turton committed
555

556
    ensure_files_closed( fh );
Neil Turton's avatar
Neil Turton committed
557

558 559 560 561 562
    if ( fh->start_of_buffer + fh->read_to - fh->written_to >= buffer_top )
    {
        /*
            removing buffer at end
        */
Neil Turton's avatar
Neil Turton committed
563

564 565 566 567 568 569 570 571 572 573
        if ( fh->start_of_buffer < buffer_top )
            buffer_top = fh->start_of_buffer;
    }
    else
    {
        /*
            removing buffer at start
        */
        buffer_bottom += fh->read_to - fh->written_to;
    }
Neil Turton's avatar
Neil Turton committed
574

575 576
    if ( fh->source_filename )
        overflowing_free( fh->source_filename );
Robert Sprowson's avatar
Robert Sprowson committed
577

578 579
    overflowing_free( fh->destination_filename );
    overflowing_free( fh );
Neil Turton's avatar
Neil Turton committed
580 581 582
}

/*
583
     Assuming no buffer in buffer chain, and no open files, remove
Neil Turton's avatar
Neil Turton committed
584 585 586 587
     forwards file from files_chain.
*/
static void remove_file_from_chain( void )
{
588 589 590 591 592 593 594 595 596 597 598 599
    chain_link *link = files_chain.forwards;
    files_header *fh;

    if ( !link->forwards )
        return;

    fh = chain_link_Wrapper( link );

    /*
        Special indication that it has been a directory that's just
        been finished
    */
600
    if ( fh->objecttype == object_directory )
601
    {
602
        finished_obj_was_file = No;
603 604 605
    }
    else
    {
606
        finished_obj_was_file = Yes;
607 608 609 610 611
    }

    /*
        Record the source filename of the finished file
    */
612 613
    if ( finished_obj_source_name )
        overflowing_free( finished_obj_source_name );
614

615
    finished_obj_source_name = fh->source_filename;
616 617 618
    fh->source_filename = NULL;

    remove_fh( fh );
Neil Turton's avatar
Neil Turton committed
619 620 621 622 623 624 625
}

/*
     returns the files_handle of the next file to read
*/
static files_header *next_fh_to_read( void )
{
626 627
    chain_link *link;
    files_header *fh;
Neil Turton's avatar
Neil Turton committed
628

629 630 631
    for ( link = files_chain.forwards; link->forwards; link = link->forwards )
    {
        fh = chain_link_Wrapper( link );
Neil Turton's avatar
Neil Turton committed
632

633
        if ( fh->objecttype != object_directory &&
634 635 636 637
             ( fh->read_to < fh->size ||
               fh->source_file_handle ) )
        {
            return fh;
Neil Turton's avatar
Neil Turton committed
638
        }
639
    }
Neil Turton's avatar
Neil Turton committed
640

641
    return NULL;
Neil Turton's avatar
Neil Turton committed
642 643 644 645 646 647 648
}

/*
     skip a general file
*/
static void skip_file( files_header *fh )
{
649 650
    if ( fh == NULL )
        return;
Neil Turton's avatar
Neil Turton committed
651

652
    remove_fh( fh );
Neil Turton's avatar
Neil Turton committed
653 654 655 656 657 658 659 660
}

/*
     algorithms to grow and shrink block size on time out
*/

static int grow( int block )
{
661 662 663 664 665 666 667 668 669 670 671 672
    if ( block < 4096 )
    {
        block = (block + 1024) & ~1023;
    }
    else if ( block < 16384 )
    {
        block = (block + 4096) & ~4095;
    }
    else
    {
        block = (block + (block>>1)) & ~4095;
    }
Neil Turton's avatar
Neil Turton committed
673

674
    debugmem(( "grow: to %d\n", block ));
Neil Turton's avatar
Neil Turton committed
675

676
    return block;
Neil Turton's avatar
Neil Turton committed
677 678 679 680
}

static int shrink( int block )
{
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
    if ( block < 4096 )
    {
        block = (block - 256) & ~255;
    }
    else if ( block < 16384 )
    {
        block = (block - 1024) & ~1023;
    }
    else
    {
        block = (block - (block>>2)) & ~1023;
    }

    if ( block < minimum_block_size )
        block = minimum_block_size;
Neil Turton's avatar
Neil Turton committed
696

697
    debugmem(( "shrink: to %d\n", block ));
Neil Turton's avatar
Neil Turton committed
698

699
    return block;
Neil Turton's avatar
Neil Turton committed
700 701 702
}

/*
703 704
    Go through all readable files and discard their buffers if they overhang
    the top of the buffer
Neil Turton's avatar
Neil Turton committed
705 706 707
*/
static void truncate_overhanging_files( void )
{
708 709
    chain_link *link;
    files_header *fh;
Neil Turton's avatar
Neil Turton committed
710

711 712 713
    for ( link = files_chain.forwards; link->forwards; link = link->forwards )
    {
        fh = chain_link_Wrapper( link );
Neil Turton's avatar
Neil Turton committed
714

715
        if ( fh->objecttype != object_directory )
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
        {
            if ( fh->start_of_buffer >= buffer_top )
            {
                /*
                    file buffer starts beyond end of buffer
                */
                fh->start_of_buffer = 0;
                fh->read_to = fh->written_to;
            }
            else if ( fh->start_of_buffer + fh->read_to - fh->written_to > buffer_top )
            {
                /*
                    file buffer hangs over end of buffer
                */
                fh->read_to = buffer_top - (fh->start_of_buffer - fh->written_to);
            }
Neil Turton's avatar
Neil Turton committed
732
        }
733
    }
Neil Turton's avatar
Neil Turton committed
734 735 736 737
}

static int memmanage_slotextend( int n, void **p )
{
738 739
    int size_grown;
    int movement;
Neil Turton's avatar
Neil Turton committed
740

741 742 743 744 745 746 747 748
    size_grown = MinimumWorkingSize - (buffer_end + buffer_offset - n);
    if ( size_grown > 0 )
    {
        /*
            Not enough room in current slot, so try to extend
            If fail to extend then can't satisfy request
        */
        buffer_end += extend_application_slot( size_grown );
Neil Turton's avatar
Neil Turton committed
749

750 751
        if ( buffer_end + buffer_offset - n < MinimumWorkingSize )
            return 0;
Neil Turton's avatar
Neil Turton committed
752

753 754 755
        if ( application_max_size < (int)(buffer_base_address + buffer_offset + buffer_end) - 0x8000 )
            application_max_size = (int)(buffer_base_address + buffer_offset + buffer_end) - 0x8000;
    }
Neil Turton's avatar
Neil Turton committed
756

757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
    if ( buffer_bottom + buffer_offset >= n )
    {
        /*
            Already enough room below used section - no action necessary
        */
    }
    else if ( buffer_bottom + buffer_offset + buffer_end - buffer_top >= n )
    {
        /*
            Enough room in unused space, but not enough room below
            used section so move used section of buffer up
            to make room.
        */
        memmove( buffer_base_address + n,
            buffer_base_address + buffer_bottom + buffer_offset,
            buffer_top - buffer_bottom );

        movement = n - (buffer_offset + buffer_bottom);
        buffer_end -= movement;
        buffer_offset += movement;
    }
    else
    {
        /*
            Not enough room in the available slot - try to extend
        */
        size_grown = n - (buffer_bottom + buffer_offset + buffer_end - buffer_top);
Neil Turton's avatar
Neil Turton committed
784

785
        (void)grow_buffer( &size_grown );
Neil Turton's avatar
Neil Turton committed
786

787 788 789
        memmove( buffer_base_address + n,
            buffer_base_address + buffer_bottom + buffer_offset,
            buffer_end + buffer_offset - n );
Neil Turton's avatar
Neil Turton committed
790

791 792 793
        movement = n - (buffer_offset + buffer_bottom);
        buffer_end -= movement;
        buffer_offset += movement;
Neil Turton's avatar
Neil Turton committed
794

795 796 797 798
        if ( buffer_top > buffer_end )
        {
            buffer_top = buffer_end;
            truncate_overhanging_files();
Neil Turton's avatar
Neil Turton committed
799
        }
800
    }
Neil Turton's avatar
Neil Turton committed
801

802 803 804
    *p = buffer_base_address;
    buffer_base_address += n;
    buffer_offset -= n;
Neil Turton's avatar
Neil Turton committed
805

806
    return n;
Neil Turton's avatar
Neil Turton committed
807 808 809 810 811 812 813 814 815 816 817
}

/*****************************************************************

Below here is the external interface stuff

*****************************************************************/

/*
     Set the slot size to that passed.
     Return an error if the wimp_slotsize failed or the slot size
818
      obtained is less than that requested.
Neil Turton's avatar
Neil Turton committed
819 820 821
*/
void action_slot( int size )
{
822
    int size_change = size - application_current_size();
Neil Turton's avatar
Neil Turton committed
823

824
    debugmem(( "action_slot: by %d to %d\n", size_change, size ));
Neil Turton's avatar
Neil Turton committed
825

826 827 828 829 830 831 832 833 834 835 836 837
    if ( size_change > 0 )
    {
        /*
            Grow slot
        */
        buffer_end += extend_application_slot( size_change );
    }
    else if ( size_change < 0 )
    {
        /*
            Shrink slot
        */
Neil Turton's avatar
Neil Turton committed
838

839 840 841 842 843
        /*
            Lower-bound size change to ensure we still have MinimumWorkingSize
        */
        if ( buffer_end + buffer_offset + size_change < MinimumWorkingSize )
            size_change = MinimumWorkingSize - buffer_end - buffer_offset;
Neil Turton's avatar
Neil Turton committed
844

845 846 847 848 849
        if ( buffer_top > buffer_end + size_change )
        {
            /*
                Ensure the data is at the bottom of the buffer area
            */
850
            debugmem(( "action_slot: Base=%#010x, offset=%d, bottom=%d, top=%d, end=%d\n", (int)buffer_base_address, buffer_offset, buffer_bottom, buffer_top, buffer_end ));
851 852 853 854
            memmove(
                buffer_base_address,
                buffer_base_address + buffer_bottom + buffer_offset,
                buffer_top - buffer_bottom );
Neil Turton's avatar
Neil Turton committed
855

856 857 858
            buffer_end += buffer_bottom + buffer_offset;
            buffer_offset -= buffer_bottom + buffer_offset;
        }
Neil Turton's avatar
Neil Turton committed
859

860
        size_change = extend_application_slot( size_change );
Neil Turton's avatar
Neil Turton committed
861

862 863 864 865
        /*
            Adjust buffer_end by actual amount changed by
        */
        buffer_end += size_change;
Neil Turton's avatar
Neil Turton committed
866

867 868 869 870 871 872
        /*
            Upper bound buffer_top by buffer_end
        */
        if ( buffer_top > buffer_end )
            buffer_top = buffer_end;
    }
Neil Turton's avatar
Neil Turton committed
873

874
    truncate_overhanging_files();
Neil Turton's avatar
Neil Turton committed
875

876
    application_max_size = (int)(buffer_base_address + buffer_offset + buffer_end) - 0x8000;
877 878
    debugmem(( "action_slot: app max size %d\n", application_max_size ));
    debugmem(( "action_slot: Base=%#010x, offset=%d, bottom=%d, top=%d, end=%d\n", (int)buffer_base_address, buffer_offset, buffer_bottom, buffer_top, buffer_end ));
Neil Turton's avatar
Neil Turton committed
879 880 881 882 883 884 885
}

/*
     Initialise to end in suitable place for closedown
*/
os_error *init_memmanagement( void )
{
886 887 888 889 890 891
    /*
        Things that use real memory ( base = null )
    */
    chain_initialise_header( &files_chain );

    /*
Robert Sprowson's avatar
Robert Sprowson committed
892
        This determines the maximum size we will allow buffer
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
        allocation to grow to. This starts at the
        current next slot size, then gets adjusted when the
        user drags the memory slider
    */
    application_max_size = current_next_slot();

    /*
        The buffer area
    */
    buffer_base_address = (char *)(application_current_size() + 0x8000);
    buffer_offset = 0;
    buffer_bottom = 0;
    buffer_top = 0;
    buffer_end = 0;

908
    finished_obj_source_name = NULL;
909 910 911 912

    (void)_kernel_register_slotextend( memmanage_slotextend );

    return NULL;
Neil Turton's avatar
Neil Turton committed
913 914 915 916 917 918 919 920 921 922 923 924 925 926
}

/*
     Get into a fit state for copying. The chains and area
     pointers should already been zeroed out by calling
     init_memmangement. This routine starts off flex and heap,
     which grab more wimp space, which is why this part of the
     initialisation is left out of the general initialisation
     sequence. The routine also tries to grab a buffer, just to
     make sure this is feasible. No buffer => No copying!
*/
os_error *init_for_copying( void )
{
#if Timing
927
    copytime = clock();
Neil Turton's avatar
Neil Turton committed
928
#endif
929 930 931
    src_block_size = InitialBlockSize;
    dest_block_size = InitialBlockSize;
    timing_init();
Neil Turton's avatar
Neil Turton committed
932

933 934 935 936
    if ( buffer_ensure( MinimumWorkingSize ) )
    {
        if ( application_max_size < application_current_size() )
            application_max_size = application_current_size();
Neil Turton's avatar
Neil Turton committed
937

938 939 940 941 942 943
        return NULL;
    }
    else
    {
        return error( mb_slotsize_too_small ); /* was mb_malloc_failed */
    }
Neil Turton's avatar
Neil Turton committed
944 945 946 947
}

/*
     Misc. ESSENTIAL tidying up:
948
      Close down any open files
Neil Turton's avatar
Neil Turton committed
949 950 951
*/
void closedown_memmanagement( void )
{
952 953
    chain_link *link;
    files_header *fh;
Neil Turton's avatar
Neil Turton committed
954

955 956 957
    for ( link = files_chain.forwards; link->forwards; link = link->forwards )
    {
        fh = chain_link_Wrapper( link );
Neil Turton's avatar
Neil Turton committed
958

959 960
        ensure_files_closed( fh );
    }
Neil Turton's avatar
Neil Turton committed
961
#if Timing
962 963
    copytime = clock() - copytime;
    werr(0, "time taken for file copy = %d.%02ds", copytime/100, copytime%100);
Neil Turton's avatar
Neil Turton committed
964 965 966
#endif
#ifdef debug
#ifdef debugfile
967
    if ( debugf != NULL ) fclose( debugf );
Neil Turton's avatar
Neil Turton committed
968 969 970 971 972 973 974
#endif
#endif
}

/*
     Add a file to the end of the files chain
*/
975
os_error *add_file_to_chain( char *destination, char *source,
976
                             uint32_t size, int reload, int execute, int attributes, int objecttype,
977 978
                             BOOL forceit, BOOL *i_am_full
                             #ifdef USE_PROGRESS_BAR
Robert Sprowson's avatar
Robert Sprowson committed
979
                             , uint32_t progress, void **ref
980 981 982
                             #endif
                           )
{
983 984 985 986 987 988
    files_header *fh;

    fh = overflowing_malloc( sizeof( files_header ));
    if ( fh )
    {
        fh->source_filename = overflowing_malloc( strlen( source ) + 1 );
Neil Turton's avatar
Neil Turton committed
989

990
        if ( fh->source_filename )
Neil Turton's avatar
Neil Turton committed
991
        {
992
            fh->destination_filename = overflowing_malloc( strlen( destination ) + 1 );
Neil Turton's avatar
Neil Turton committed
993

994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
            if ( fh->destination_filename )
            {
                /*
                     Initialise the structure
                */
                strcpy( fh->source_filename, source );
                fh->source_file_handle = 0;
                strcpy( fh->destination_filename, destination );
                fh->destination_file_handle = 0;
                fh->size = size;
                fh->reload = reload;
                fh->execute = execute;
                fh->attributes = attributes;
                fh->objecttype = objecttype;
                fh->forceit = forceit;
                fh->read_to = 0;
                fh->written_to = 0;
                fh->write_state = write_not_started;
                fh->start_of_buffer = 0;

1014
                #ifdef USE_PROGRESS_BAR
1015 1016 1017
                fh->total_progress = progress / 2;
                fh->write_progress = fh->read_progress = fh->total_progress;
                if (ref != NULL) *ref = fh;
1018
                debugmem(( "add_file_to_chain: %s type %d progress %08x\n", destination, objecttype, fh->total_progress ));
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
                #endif

                /*
                     link to backwards end of files chain
                */
                chain_insert_before_header( &fh->link, fh, &files_chain );

                *i_am_full = No;

                return NULL;
            }
Neil Turton's avatar
Neil Turton committed
1030

1031
            overflowing_free( fh->source_filename );
Neil Turton's avatar
Neil Turton committed
1032 1033
        }

1034 1035
        overflowing_free( fh );
    }
Neil Turton's avatar
Neil Turton committed
1036

1037 1038 1039 1040 1041 1042
    *i_am_full = Yes;

    /*
         one of the heap_allocs failed
    */
    return error( mb_malloc_failed );
Neil Turton's avatar
Neil Turton committed
1043 1044
}

1045

1046
#ifdef USE_PROGRESS_BAR
Robert Sprowson's avatar
Robert Sprowson committed
1047
void modify_chain_file_progress(void *ref, uint32_t progress)
1048
{
1049 1050
  files_header *fh = (files_header *)ref;

1051 1052
  if (fh != NULL)
  {
1053
    debugmem(( "\nmodify chain: %s %x\n", fh->destination_filename, progress ));
1054 1055 1056 1057 1058 1059 1060
    fh->write_progress = fh->total_progress = progress;
  }
}
#endif


/* Modified to return the progress value of the finished file	*/
Robert Sprowson's avatar
Robert Sprowson committed
1061
os_error *read_a_block( BOOL *i_am_full, BOOL *need_another_file, BOOL *that_finished_a_file, uint32_t *progress )
Neil Turton's avatar
Neil Turton committed
1062
{
1063
    files_header *fh;
Robert Sprowson's avatar
Robert Sprowson committed
1064 1065 1066 1067 1068 1069
    os_regset    r;
    os_error     *err;
    os_gbpbstr   gbpbplace;
    int          size_needed;
    uint32_t     start_read_to;
    clock_t      t;
Neil Turton's avatar
Neil Turton committed
1070

1071 1072
    *i_am_full = No;
    *that_finished_a_file = No;
Neil Turton's avatar
Neil Turton committed
1073

1074
    fh = next_fh_to_read();
Neil Turton's avatar
Neil Turton committed
1075

1076 1077 1078
    if ( !fh )
    {
        *need_another_file = Yes;
Neil Turton's avatar
Neil Turton committed
1079

1080 1081
        return NULL;
    }
Neil Turton's avatar
Neil Turton committed
1082

1083
    *need_another_file = No;
Neil Turton's avatar
Neil Turton committed
1084

1085
    debugmem(( "read_a_block: from %s\n", fh->source_filename ));
Neil Turton's avatar
Neil Turton committed
1086

1087 1088 1089 1090 1091 1092 1093
    start_read_to = fh->read_to;

    /*
         If we have to do some reading
    */
    if ( fh->read_to < fh->size )
    {
1094
        debugmem(( "R" ));
1095 1096 1097
        /*
            Work out how much buffer is needed...
        */
Neil Turton's avatar
Neil Turton committed
1098

1099 1100 1101 1102 1103 1104 1105 1106 1107
        /*
            Upper bound to src_block_size and amount to read in file
        */
        if ( fh->size - fh->read_to > src_block_size )
        {
            size_needed = src_block_size;
        }
        else
        {
Robert Sprowson's avatar
Robert Sprowson committed
1108
            /* This calculation fits in a signed integer provided src_block_size < 2G */
1109 1110
            size_needed = fh->size - fh->read_to;
        }
Neil Turton's avatar
Neil Turton committed
1111

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
        /*
             Ensure word-alignment of file ptr and buffer address
             equal, to ensure DMA operation on DMA-capable disc
             systems (eg Iyonix ADFS).
        */
        if ( fh->read_to == fh->written_to &&
             (buffer_top & 3) != (fh->read_to & 3) )
        {
            int pad = ((fh->read_to & 3) - (buffer_top & 3)) & 3;
            *i_am_full = grow_buffer( &pad );
            if ( pad <= 0)
                return NULL;
            buffer_top += pad;
        }
        /*
             Enlarge the buffer (if possible)
        */
        *i_am_full = grow_buffer( &size_needed );
Neil Turton's avatar
Neil Turton committed
1130

1131 1132
        if ( size_needed <= 0 )
            return NULL;
Neil Turton's avatar
Neil Turton committed
1133

1134 1135 1136 1137 1138 1139 1140
        /*
            Set up new buffer
        */
        if ( fh->read_to == fh->written_to )
        {
            fh->start_of_buffer = buffer_top;
        }
Neil Turton's avatar
Neil Turton committed
1141

1142
        /*
Robert Sprowson's avatar
Robert Sprowson committed
1143 1144 1145
             Sub-optimal case: read file in chunks

             If the file isn't open yet, then open it
1146
        */
Robert Sprowson's avatar
Robert Sprowson committed
1147
        if ( fh->source_file_handle == 0 )
1148
        {
Robert Sprowson's avatar
Robert Sprowson committed
1149 1150 1151
            debugmem(( "O" ));
            r.r[0] = open_read | open_nopath | open_nodir | open_mustopen;
            r.r[1] = (int)fh->source_filename;
1152

Robert Sprowson's avatar
Robert Sprowson committed
1153
            err = os_find( &r );
1154 1155 1156 1157

            if ( err )
                return err;

Robert Sprowson's avatar
Robert Sprowson committed
1158
            fh->source_file_handle = r.r[0];
1159
        }
Neil Turton's avatar
Neil Turton committed
1160

Robert Sprowson's avatar
Robert Sprowson committed
1161
        t = clock();
Neil Turton's avatar
Neil Turton committed
1162

Robert Sprowson's avatar
Robert Sprowson committed
1163 1164 1165 1166 1167 1168 1169 1170
        /*
            Read a block into the buffer.
        */
        gbpbplace.action = OSGBPB_ReadFromGiven;
        gbpbplace.file_handle = fh->source_file_handle;
        gbpbplace.data_addr = buffer_base_address + buffer_offset + buffer_top;
        gbpbplace.number = size_needed;
        gbpbplace.seq_point = fh->read_to;
Neil Turton's avatar
Neil Turton committed
1171

Robert Sprowson's avatar
Robert Sprowson committed
1172 1173 1174
        debugmem(( "read_a_block: buf %x + %u [%u-%u]\n", buffer_base_address, buffer_offset,
                                                          buffer_bottom, buffer_top ));
        debugmem(( "read_a_block: (%d,%d<-%d)\n", gbpbplace.data_addr, gbpbplace.number, gbpbplace.seq_point ));
Neil Turton's avatar
Neil Turton committed
1175

Robert Sprowson's avatar
Robert Sprowson committed
1176 1177 1178
        err = os_gbpb( &gbpbplace );
        if ( err )
            return err;
1179

Robert Sprowson's avatar
Robert Sprowson committed
1180 1181
        fh->read_to += size_needed;
        buffer_top += size_needed;
1182

Robert Sprowson's avatar
Robert Sprowson committed
1183 1184 1185 1186 1187 1188 1189
        if (src_block_size == size_needed)
        {  /* only time full block transfers */
            t = clock() - t;
            if ( timing_accumulating(timing_Reading) )
            { /* accumulating initial timings */
                assert(src_block_size == InitialBlockSize);
                timing_accumulate(timing_Reading, t, src_block_size);
1190
            }
Robert Sprowson's avatar
Robert Sprowson committed
1191
            else if (time_quanta < MaxTimeQuanta)
1192
            {
Robert Sprowson's avatar
Robert Sprowson committed
1193 1194 1195 1196 1197 1198 1199 1200 1201
                if ( (src_block_size <= SmallBlockSize)
                  && timing_poorrate(timing_Reading, t, src_block_size) )
                { /* if the transfer rate is suffering at this small blocksize,
                   * increase time_quanta to cause the blocksize to grow */
                    if (time_quanta < MaxTimeQuanta) time_quanta *= 2;
                    debugmem(( "read_a_block: time_quanta to %d\n", time_quanta));
                }
                if ( t < time_quanta - TimeQuantaHysteresis) src_block_size = grow( src_block_size );
                if ( t > time_quanta + TimeQuantaHysteresis) src_block_size = shrink( src_block_size );
1202
            }
Neil Turton's avatar
Neil Turton committed
1203
        }
Robert Sprowson's avatar
Robert Sprowson committed
1204 1205 1206 1207 1208 1209 1210 1211

        if ( fh->read_to >= fh->size )
        {
            debugmem(( "C" ));
            err = close_file( &fh->source_file_handle );
            if ( err )
                return err;
        }
1212 1213 1214 1215 1216 1217 1218 1219 1220
    }

    /*
         If we've finished reading the file
    */
    if ( fh->read_to >= fh->size )
    {
        *that_finished_a_file = Yes;

1221
        #ifdef USE_PROGRESS_BAR
1222
        *progress = fh->read_progress;
1223 1224
        #else
        IGNORE(progress);
1225
        #endif
Neil Turton's avatar
Neil Turton committed
1226 1227

        /*
1228 1229
            If there isn't another file to read, tell the client about
            it now.
Neil Turton's avatar
Neil Turton committed
1230
        */
1231 1232 1233
        if ( next_fh_to_read() == NULL )
            *need_another_file = Yes;

1234
    #ifdef USE_PROGRESS_BAR
1235 1236 1237
    }
    else
    {
1238 1239
        uint64_t p;
        uint32_t b;
1240

1241
        b = fh->read_to - start_read_to; /* bytes read this time */
1242
        p = (fh->total_progress * (uint64_t)b) / fh->size;
1243

Robert Sprowson's avatar
Robert Sprowson committed
1244 1245
        fh->read_progress -= (uint32_t) p;
        *progress = (uint32_t) p;
1246

1247
        debugmem(( "partial read: %u bytes / %u = %08x progress\n", b, fh->size, *progress ));
1248 1249
    #endif
    }
Neil Turton's avatar
Neil Turton committed
1250

1251
    debugmem(( "X\n" ));
Neil Turton's avatar
Neil Turton committed
1252

1253
    return NULL;
Neil Turton's avatar
Neil Turton committed
1254 1255 1256 1257
}

void skip_file_read( void )
{
1258
    skip_file( next_fh_to_read() );
Neil Turton's avatar
Neil Turton committed
1259 1260
}

Robert Sprowson's avatar
Robert Sprowson committed
1261
static os_error *int_write_a_block( BOOL *i_am_empty, BOOL *that_finished_a_file, uint32_t *progress )
Neil Turton's avatar
Neil Turton committed
1262
{
Robert Sprowson's avatar
Robert Sprowson committed
1263 1264
    os_gbpbstr   gbpbplace;
    os_filestr   fileplace;
1265
    files_header *fh;
Robert Sprowson's avatar
Robert Sprowson committed
1266 1267 1268 1269 1270 1271
    os_regset    r;
    os_error     *err = NULL;
    int          amount_to_write;
    uint32_t     start_written_to;
    clock_t      t;
    static BOOL  dont_set_dir_info = No;
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281

    *i_am_empty = No;
    *that_finished_a_file = No;

    /*
        no more files to write?
    */
    if ( !files_chain.forwards->forwards )
    {
        *i_am_empty = Yes;
Neil Turton's avatar
Neil Turton committed
1282

1283 1284
        return NULL;
    }
Neil Turton's avatar
Neil Turton committed
1285

1286
    fh = chain_link_Wrapper( files_chain.forwards );
Neil Turton's avatar
Neil Turton committed
1287

1288
    debugmem(( "int_write_a_block to %s\n", fh->destination_filename ));
Neil Turton's avatar
Neil Turton committed
1289

1290 1291 1292
    /*
        no more buffer to write when there's more file to write?
    */
1293
    if ( fh->objecttype != object_directory &&
1294 1295 1296 1297
        fh->read_to - fh->written_to == 0 &&
        fh->written_to < fh->size )
    {
        *i_am_empty = Yes;
Neil Turton's avatar
Neil Turton committed
1298

1299 1300 1301 1302
        return NULL;
    }

    start_written_to = fh->written_to;
Neil Turton's avatar
Neil Turton committed
1303

1304
    debugmem(( "W" ));
Neil Turton's avatar
Neil Turton committed
1305

1306 1307 1308 1309 1310 1311 1312 1313 1314
    /*
        amount_to_write is lesser of a dest_block_size and the amount buffered of the file
    */
    if ( fh->read_to - fh->written_to > dest_block_size )
    {
        amount_to_write = dest_block_size;
    }
    else
    {
Robert Sprowson's avatar
Robert Sprowson committed
1315
        /* This calculation fits in a signed integer provided dest_block_size < 2G */
1316 1317 1318 1319 1320 1321 1322 1323 1324
        amount_to_write = fh->read_to - fh->written_to;
    }

    while ( !err &&
           ( fh->write_state == write_not_started ||
         fh->write_state == write_f_tried_unlock ||
         fh->write_state == write_f_tried_cdirs ||
         fh->write_state == write_f_tried_both ))
    {
1325
        if ( fh->objecttype == object_directory )
Neil Turton's avatar
Neil Turton committed
1326
        {
1327
            debugmem(( "D" ));
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
            /*
                Start up directory
            */
            err = create_directory( fh->destination_filename );

            if ( !err )
                fh->write_state = dont_set_dir_info ?
                            write_adjust_access :
                            write_adjust_info;
        }
        else
        {
            /*
                Start up file (or partition)
            */
            if ( fh->size <= amount_to_write )
            {
1345
                debugmem(( "S" ));
1346 1347 1348
                /*
                    Optimised case - try save
                */
1349
                fileplace.action = OSFile_Save;
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
                fileplace.name = fh->destination_filename;
                fileplace.loadaddr = fh->reload;
                fileplace.execaddr = fh->execute;
                fileplace.start = (int)(buffer_base_address + buffer_offset + fh->start_of_buffer);
                fileplace.end = fileplace.start + fh->size;

                err = os_file( &fileplace );

                if ( !err )
                {
                    fh->written_to = fh->size;
                    fh->start_of_buffer += fh->size;
                    buffer_bottom += fh->size;

                    fh->write_state = write_adjust_access;
                }
            }
            else
            {
1369
                debugmem(( "c" ));
1370 1371 1372 1373
                /*
                    Sub-optimal case - open, GBPB, close
                */
                err = create_file_dead( fh->destination_filename, fh->size );
Neil Turton's avatar
Neil Turton committed
1374

1375
                if ( !err )
Neil Turton's avatar
Neil Turton committed
1376
                {
1377
                    fh->write_state = write_openit;
Neil Turton's avatar
Neil Turton committed
1378
                }
1379
            }
Neil Turton's avatar
Neil Turton committed
1380 1381
        }

1382
        if ( err )
Neil Turton's avatar
Neil Turton committed
1383
        {
1384 1385 1386 1387 1388
            if ( (err->errnum & FileError_Mask) == ErrorNumber_Locked &&
                fh->forceit &&
                fh->write_state != write_f_tried_unlock &&
                fh->write_state != write_f_tried_both )
            {
Neil Turton's avatar
Neil Turton committed
1389
                /*
1390
                    We haven't tried unlocking yet - give it a go
Neil Turton's avatar
Neil Turton committed
1391
                */
1392
                fileplace.action = OSFile_WriteAttr;
1393
                fileplace.name = fh->destination_filename;
1394
                fileplace.end = read_attribute | write_attribute;
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413

                os_file( &fileplace );  /* ignore any error back */

                err = NULL;

                if ( fh->write_state == write_not_started )
                    fh->write_state = write_f_tried_unlock;
                else if ( fh->write_state == write_f_tried_cdirs )
                    fh->write_state = write_f_tried_both;
            }
            else if ( ((err->errnum & FileError_Mask) == ErrorNumber_NotFound ||
                   err->errnum == ErrorNumber_CantOpenFile) &&
                   fh->write_state != write_f_tried_cdirs &&
                   fh->write_state != write_f_tried_both )
            {
                /*
                    We haven't tried cdirs yet - give it a go
                */
                err = ensure_file_directories( fh->destination_filename );
Neil Turton's avatar
Neil Turton committed
1414 1415 1416

                if ( !err )
                {
1417
                    /*
1418
                        no error - try opening/saving again, but don't retry cdirs on fail
1419 1420 1421 1422 1423
                    */
                    if ( fh->write_state == write_not_started )
                        fh->write_state = write_f_tried_cdirs;
                    else if ( fh->write_state == write_f_tried_unlock )
                        fh->write_state = write_f_tried_both;
Neil Turton's avatar
Neil Turton committed
1424
                }
1425
            }
Neil Turton's avatar
Neil Turton committed
1426
        }
1427
    }
Neil Turton's avatar
Neil Turton committed
1428

1429 1430
    if ( !err && fh->write_state == write_openit )
    {
1431
        debugmem(( "O" ));
1432 1433 1434 1435
        /*
            Open it up. The file has been successfully created if we're here, so
            any failures at this point are bad news.
        */
1436
        r.r[0] = open_update | open_nopath | open_nodir | open_mustopen;
1437 1438 1439 1440 1441
        r.r[1] = (int)fh->destination_filename;

        err = os_find( &r );

        if ( !err )
Neil Turton's avatar
Neil Turton committed
1442
        {
1443 1444 1445 1446 1447 1448 1449
            fh->write_state = write_truncateopen;
            fh->destination_file_handle = r.r[0];
        }
    }

    if ( !err && fh->write_state == write_truncateopen )
    {
1450
        debugmem(( "T" ));
1451 1452 1453 1454 1455
        /*
            Once the file's open set its extent to 0. This
            prevents FileSwitch loading before update on
            blocks of the file.
        */
1456
        r.r[0] = OSArgs_SetEXT;
1457 1458
        r.r[1] = (int)fh->destination_file_handle;
        r.r[2] = 0; /* to 0 */
Neil Turton's avatar
Neil Turton committed
1459

1460
        err = os_args( &r );
Neil Turton's avatar
Neil Turton committed
1461

1462 1463 1464
        if ( !err )
        {
            fh->write_state = write_blocks;
Neil Turton's avatar
Neil Turton committed
1465
        }
1466
    }
Neil Turton's avatar
Neil Turton committed
1467

1468 1469 1470 1471 1472
    if ( !err && fh->write_state == write_blocks )
    {
        /*
            Write a block out
        */
1473
        gbpbplace.action = OSGBPB_WriteAtGiven;
1474 1475 1476 1477
        gbpbplace.file_handle = fh->destination_file_handle;
        gbpbplace.data_addr = buffer_base_address + buffer_offset + fh->start_of_buffer;
        gbpbplace.number = amount_to_write;
        gbpbplace.seq_point = fh->written_to;
Neil Turton's avatar
Neil Turton committed
1478

Robert Sprowson's avatar
Robert Sprowson committed
1479
        debugmem(( "int_write_a_block: buf %x + %d [%d-%d]\n", buffer_base_address, buffer_offset,
1480
                                                               buffer_bottom, buffer_top ));
Robert Sprowson's avatar
Robert Sprowson committed
1481
        debugmem(( "int_write_a_block: w(%d,%u->%u)", (int)gbpbplace.data_addr, gbpbplace.number, gbpbplace.seq_point ));
Neil Turton's avatar
Neil Turton committed
1482

1483
        t = clock();
Neil Turton's avatar
Neil Turton committed
1484

1485
        err = os_gbpb( &gbpbplace );
Neil Turton's avatar
Neil Turton committed
1486

1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497
        if ( !err )
        {
            if (dest_block_size == amount_to_write)
            {  /* only time full block transfers */
                t = clock() - t;
                if ( timing_accumulating(timing_Writing) )
                { /* accumulating initial timings */
                    assert(dest_block_size == InitialBlockSize);
                    timing_accumulate(timing_Writing, t, dest_block_size);
                }
                else
Neil Turton's avatar
Neil Turton committed
1498
                {
1499 1500 1501 1502 1503
                    if ( (dest_block_size <= SmallBlockSize)
                      && timing_poorrate(timing_Writing, t, dest_block_size) )
                    { /* if the transfer rate is suffering at this small blocksize,
                       * increase time_quanta to cause the blocksize to grow */
                        if (time_quanta < MaxTimeQuanta) time_quanta *= 2;
1504
                        debugmem(( "int_write_a_block: time_quanta to %d\n", time_quanta ));
1505 1506 1507
                    }
                    if ( t < time_quanta - TimeQuantaHysteresis) dest_block_size = grow( dest_block_size );
                    if ( t > time_quanta + TimeQuantaHysteresis) dest_block_size = shrink( dest_block_size );
Neil Turton's avatar
Neil Turton committed
1508
                }
1509 1510 1511 1512 1513 1514 1515
            }
            fh->written_to += amount_to_write;
            fh->start_of_buffer += amount_to_write;
            buffer_bottom += amount_to_write;

            if ( fh->written_to >= fh->size )
                fh->write_state = write_closeit;
Neil Turton's avatar
Neil Turton committed
1516
        }
1517
    }
Neil Turton's avatar
Neil Turton committed
1518

1519 1520
    if ( !err && fh->write_state == write_closeit )
    {
1521
        debugmem(( "C" ));
1522
        err = close_file( &fh->destination_file_handle );
Neil Turton's avatar
Neil Turton committed
1523

1524 1525 1526 1527 1528 1529
        /*
            Regardless of whether the close worked, the file will be closed,
            so always move onto adjusting the file's info
        */
        fh->write_state = write_adjust_info;
    }
Neil Turton's avatar
Neil Turton committed
1530

1531 1532
    if ( !err && fh->write_state == write_adjust_info )
    {
1533
        debugmem(( "I" ));
1534
        /*
1535
            Always adjust the info as it's almost always wrong
1536 1537 1538 1539 1540 1541
        */
        err = write_catalogue_information(
              fh->destination_filename,
              fh->reload,
              fh->execute,
              fh->attributes );
Neil Turton's avatar
Neil Turton committed
1542

1543
        if ( err && fh->objecttype == object_directory)
1544 1545 1546 1547 1548
        {
            /* Some FS's can't do this for a directory - back
               off to just doing the access permission.
             */
            err = NULL;
1549
            dont_set_dir_info = Yes;
1550
            fh->write_state = write_adjust_access;
Neil Turton's avatar
Neil Turton committed
1551
        }
1552 1553 1554
        else if ( !err )
            fh->write_state = write_all_done;
    }
Neil Turton's avatar
Neil Turton committed
1555

1556 1557
    if ( !err && fh->write_state == write_adjust_access )
    {
1558
        debugmem(( "A" ));
1559 1560 1561 1562 1563 1564
        /*
            Only adjust attributes if they're non-default ones
        */
        err = write_attributes(
              fh->destination_filename,
              fh->attributes );
Neil Turton's avatar
Neil Turton committed
1565

1566 1567 1568
        if ( !err )
            fh->write_state = write_all_done;
    }
Neil Turton's avatar
Neil Turton committed
1569

1570 1571 1572
    if ( !err && fh->write_state == write_all_done )
    {
        *that_finished_a_file = Yes;
1573
        #ifdef USE_PROGRESS_BAR
1574
        *progress = fh->write_progress;
1575 1576
        #else
        IGNORE(progress);
1577
        #endif
Neil Turton's avatar
Neil Turton committed
1578

1579 1580
        remove_file_from_chain();

1581
    #ifdef USE_PROGRESS_BAR
1582 1583 1584
    }
    else
    {
1585 1586
      uint64_t p;
      uint32_t b;
1587 1588

      b = fh->written_to - start_written_to;
1589
      p = (fh->total_progress * (uint64_t)b) / fh->size;
1590

Robert Sprowson's avatar
Robert Sprowson committed
1591 1592
      fh->write_progress -= (uint32_t) p;
      *progress = (uint32_t) p;
1593

1594
      debugmem(( "partial write: %u bytes / %u = %08x progress\n", b, fh->size, *progress ));
1595
    #endif
1596
    }
Neil Turton's avatar
Neil Turton committed
1597

1598
    debugmem(( "X\n" ));
Neil Turton's avatar
Neil Turton committed
1599

1600
    return err;
Neil Turton's avatar
Neil Turton committed
1601 1602
}

1603

Robert Sprowson's avatar
Robert Sprowson committed
1604
os_error *write_a_block( BOOL *i_am_empty, BOOL *that_finished_a_file, uint32_t *progress )
Neil Turton's avatar
Neil Turton committed
1605
{
1606
    os_error *err = int_write_a_block( i_am_empty, that_finished_a_file, progress );
Neil Turton's avatar
Neil Turton committed
1607

1608 1609 1610 1611 1612
    if ( *i_am_empty )
    {
        buffer_end += buffer_offset;
        buffer_offset = buffer_bottom = buffer_top = 0;
    }
Neil Turton's avatar
Neil Turton committed
1613

1614
    return err;
Neil Turton's avatar
Neil Turton committed
1615 1616 1617 1618
}

void skip_file_write( void )
{
1619 1620 1621 1622
    if ( files_chain.forwards->forwards )
    {
        skip_file( chain_link_Wrapper( files_chain.forwards ));
    }
Neil Turton's avatar
Neil Turton committed
1623 1624 1625 1626
}

char *next_file_to_be_written( void )
{
1627
    files_header *fh;
Neil Turton's avatar
Neil Turton committed
1628

1629 1630 1631
    if ( files_chain.forwards->forwards )
    {
        fh = chain_link_Wrapper( files_chain.forwards );
Neil Turton's avatar
Neil Turton committed
1632

1633 1634 1635 1636 1637 1638
        return fh->destination_filename;
    }
    else
    {
        return NULL;
    }
Neil Turton's avatar
Neil Turton committed
1639 1640 1641 1642
}

char *next_file_to_be_read( void )
{
1643
    files_header *fh = next_fh_to_read();
Neil Turton's avatar
Neil Turton committed
1644

1645 1646 1647 1648
    if ( fh != NULL )
        return fh->source_filename;
    else
        return NULL;
Neil Turton's avatar
Neil Turton committed
1649 1650 1651 1652
}

void restart_file_read( void )
{
1653
    files_header *fh = next_fh_to_read();
Neil Turton's avatar
Neil Turton committed
1654

1655
    debugmem(( "restart_file_read: fh=&08X\n", (int)fh ));
Neil Turton's avatar
Neil Turton committed
1656

1657 1658 1659 1660 1661 1662 1663 1664 1665
    if ( fh != NULL )
    {
        ensure_files_closed( fh );
        buffer_top -= fh->read_to;
        if ( buffer_top < 0 ) buffer_bottom = buffer_top = buffer_offset = 0;
        fh->read_to = 0;
        fh->written_to = 0;
        fh->write_state = write_not_started;
    }
Neil Turton's avatar
Neil Turton committed
1666 1667 1668 1669
}

void restart_file_write( void )
{
1670
    files_header *fh = chain_link_Wrapper( files_chain.forwards );
Neil Turton's avatar
Neil Turton committed
1671

1672
    debugmem(( "restart_file_write: fh=&08X\n", (int)fh ));
Neil Turton's avatar
Neil Turton committed
1673

1674 1675 1676 1677 1678 1679 1680 1681
    if ( fh != NULL )
    {
        /* Only restart file if we still have all of its contents so far.
         * If we don't then we can't just read from the start because we might
         * overwrite another file's data so just do nothing (== retry).
         */
        int file_start = fh->start_of_buffer - fh->written_to;
        if ( file_start >= 0 )
Neil Turton's avatar
Neil Turton committed
1682
        {
1683 1684 1685 1686 1687
            ensure_files_closed( fh );
            fh->start_of_buffer = file_start;
            buffer_bottom -= fh->written_to;
            fh->written_to = 0;
            fh->write_state = write_not_started;
1688
            debugmem(( "restart_file_write: start_of_buffer=%08X, buffer_bottom=%08X\n", fh->start_of_buffer, buffer_bottom ));
Neil Turton's avatar
Neil Turton committed
1689
        }
1690
    }
Neil Turton's avatar
Neil Turton committed
1691 1692
}

1693
uint32_t bytes_left_to_read( void )
Neil Turton's avatar
Neil Turton committed
1694
{
1695 1696 1697 1698 1699 1700 1701 1702 1703 1704
    files_header *fh = next_fh_to_read();

    if ( fh )
    {
        return fh->size - fh->read_to;
    }
    else
    {
        return 0;
    }
Neil Turton's avatar
Neil Turton committed
1705 1706
}

1707
uint32_t bytes_left_to_write( void )
Neil Turton's avatar
Neil Turton committed
1708
{
1709
    files_header *fh;
Neil Turton's avatar
Neil Turton committed
1710

1711 1712 1713
    if ( files_chain.forwards->forwards )
    {
        fh = chain_link_Wrapper( files_chain.forwards );
Neil Turton's avatar
Neil Turton committed
1714

1715 1716 1717 1718 1719 1720
        return fh->size - fh->written_to;
    }
    else
    {
        return 0;
    }
Neil Turton's avatar
Neil Turton committed
1721 1722
}

1723
void copy_go_faster( BOOL do_it )
Neil Turton's avatar
Neil Turton committed
1724
{
1725 1726 1727 1728 1729
    if ( do_it )
    {
        /*
            Go faster
        */
Neil Turton's avatar
Neil Turton committed
1730

1731
        time_quanta = MaxTimeQuanta;      /* centi-seconds */
Neil Turton's avatar
Neil Turton committed
1732

1733
        minimum_block_size = 15*1024; /* bytes: optimised for econet */
Neil Turton's avatar
Neil Turton committed
1734

1735 1736 1737 1738 1739 1740 1741 1742 1743 1744
        if ( src_block_size < minimum_block_size )
            src_block_size = minimum_block_size;
        if ( dest_block_size < minimum_block_size )
            dest_block_size = minimum_block_size;
    }
    else
    {
        /*
            Go slower
        */
Neil Turton's avatar
Neil Turton committed
1745

1746
        time_quanta = NominalTimeQuanta;
Neil Turton's avatar
Neil Turton committed
1747

1748 1749
        minimum_block_size = 512;
    }
Neil Turton's avatar
Neil Turton committed
1750
}