123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
/*******************************************************************************

        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Initial release: March 2004

        author:         Kris

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

module tango.io.device.FileMap;

private import tango.sys.Common;

private import tango.io.device.File,
               tango.io.device.Array;

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

        External declarations.

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

version (Win32)
         private extern (Windows)
                        {
                        BOOL   UnmapViewOfFile    (LPCVOID);
                        BOOL   FlushViewOfFile    (LPCVOID, DWORD);
                        LPVOID MapViewOfFile      (HANDLE, DWORD, DWORD, DWORD, DWORD);
                        HANDLE CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCTSTR);
                        }

version (Posix)
         private import tango.stdc.posix.sys.mman;


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

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

class FileMap : Array
{
        private MappedFile file;

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

                Construct a FileMap upon the given path.

                You should use resize() to setup the available
                working space.

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

        this (const(char[]) path, File.Style style = File.ReadWriteOpen)
        {
                file = new MappedFile (path, style);
                super (file.map);
        }

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

                Resize the file and return the remapped content. Usage of
                map() is not required following this call.

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

        final ubyte[] resize (long size)
        {
                auto ret = file.resize (size);
                super.assign (ret);
                return ret;
        }

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

                Release external resources.

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

        override void close ()
        {
                super.close();
                if (file)
                    file.close();
                file = null;
        }
}


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

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

class MappedFile
{
        private File host;

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

                Construct a FileMap upon the given path.

                You should use resize() to setup the available
                working space.

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

        this (const(char[]) path, File.Style style = File.ReadWriteOpen)
        {
                host = new File (path, style);
        }

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

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

        @property final long length ()
        {
                return host.length;
        }

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

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

        @property final const(char)[] path ()
        {
                return host.toString();
        }

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

                Resize the file and return the remapped content. Usage of
                map() is not required following this call.

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

        final ubyte[] resize (long size)
        {
                host.truncate (size);
                return map;
        }

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

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

        version (Win32)
        {
                private void*   base;            // array pointer
                private HANDLE  mmFile;          // mapped file

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

                        Return a slice representing file content as a
                        memory-mapped array.

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

                @property final ubyte[] map ()
                {
                        DWORD flags;

                        // be wary of redundant references
                        if (base)
                            reset();

                        // can only do 32bit mapping on 32bit platform
                        auto size = cast(size_t) host.length;
                        auto access = host.style.access;

                        flags = PAGE_READONLY;
                        if (access & host.Access.Write)
                            flags = PAGE_READWRITE;

                        auto handle = cast(HANDLE) host.fileHandle;
                        mmFile = CreateFileMappingA (handle, null, flags, 0, 0, null);
                        if (mmFile is null)
                            host.error();

                        flags = FILE_MAP_READ;
                        if (access & host.Access.Write)
                            flags |= FILE_MAP_WRITE;

                        base = MapViewOfFile (mmFile, flags, 0, 0, 0);
                        if (base is null)
                            host.error();

                        return (cast(ubyte*) base) [0 .. size];
                }

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

                        Release this mapping without flushing.

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

                final void close ()
                {
                        reset();
                        if (host)
                            host.close();
                        host = null;
                }

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

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

                private void reset ()
                {
                        if (base)
                            UnmapViewOfFile (base);

                        if (mmFile)
                            CloseHandle (mmFile);

                        mmFile = null;
                        base = null;
                }

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

                        Flush dirty content out to the drive. This
                        fails with error 33 if the file content is
                        virgin. Opening a file for ReadWriteExists
                        followed by a flush() will cause this.

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

                MappedFile flush ()
                {
                        // flush all dirty pages
                        if (! FlushViewOfFile (base, 0))
                              host.error();
                        return this;
                }
        }

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

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

        version (Posix)
        {
                // Linux code: not yet tested on other POSIX systems.
                private void*   base;           // array pointer
                private size_t  size;           // length of file

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

                        Return a slice representing file content as a
                        memory-mapped array. Use this to remap content
                        each time the file size is changed.

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

                @property final ubyte[] map ()
                {
                        // be wary of redundant references
                        if (base)
                            reset();

                        // can only do 32bit mapping on 32bit platform
                        size = cast (size_t) host.length;

                        // Make sure the mapping attributes are consistant with
                        // the File attributes.
                        int flags = MAP_SHARED;
                        int protection = PROT_READ;
                        auto access = host.style.access;
                        if (access & host.Access.Write)
                            protection |= PROT_WRITE;

                        base = mmap (null, size, protection, flags, host.fileHandle, 0);
                        if (base is MAP_FAILED)
                           {
                           base = null;
                           host.error();
                           }

                        return (cast(ubyte*) base) [0 .. size];
                }

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

                        Release this mapped buffer without flushing.

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

                final void close ()
                {
                        reset();
                        if (host)
                            host.close();
                        host = null;
                }

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

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

                private void reset ()
                {
                        // NOTE: When a process ends, all mmaps belonging to that process
                        //       are automatically unmapped by system (Linux).
                        //       On the other hand, this is NOT the case when the related
                        //       file descriptor is closed.  This function unmaps explicitly.
                        if (base)
                            if (munmap (base, size))
                                host.error();

                        base = null;
                }

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

                        Flush dirty content out to the drive.

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

                final MappedFile flush ()
                {
                        // MS_ASYNC: delayed flush; equivalent to "add-to-queue"
                        // MS_SYNC: function flushes file immediately; no return until flush complete
                        // MS_INVALIDATE: invalidate all mappings of the same file (shared)

                        if (msync (base, size, MS_SYNC | MS_INVALIDATE))
                            host.error();
                        return this;
                }
        }
}


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

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

debug (FileMap)
{
        import tango.io.Path;

        void main()
        {
                auto file = new MappedFile ("foo.map");
                auto heap = file.resize (1_000_000);

                auto file1 = new MappedFile ("foo1.map");
                auto heap1 = file1.resize (1_000_000);

                file.close();
                remove ("foo.map");

                file1.close();
                remove ("foo1.map");
        }
}