// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename       : UNIX.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Partitioning scheme plugins.
//
// --[ Description ] ----------------------------------------------------------
//
//     Manages UNIX VTOC and disklabels.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library 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
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2017 Natalia Portillo
// ****************************************************************************/

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using DiscImageChef.CommonTypes;
using DiscImageChef.ImagePlugins;

namespace DiscImageChef.PartPlugins
{
    class UNIX : PartPlugin
    {
        public const uint UNIXDiskLabel_MAGIC = 0xCA5E600D;
        public const uint UNIXVTOC_MAGIC = 0x600DDEEE;

        public UNIX()
        {
            Name = "UNIX VTOC and disklabel";
            PluginUUID = new Guid("6D35A66F-8D77-426F-A562-D88F6A1F1702");
        }

        public override bool GetInformation(ImagePlugin imagePlugin, out List<Partition> partitions)
        {
            partitions = new List<Partition>();

            uint magic;
            byte[] unix_dl_sector;

            unix_dl_sector = imagePlugin.ReadSector(0);
            magic = BitConverter.ToUInt32(unix_dl_sector, 4);
            if(magic != UNIXDiskLabel_MAGIC)
            {
                unix_dl_sector = imagePlugin.ReadSector(1);
                magic = BitConverter.ToUInt32(unix_dl_sector, 4);
                if(magic != UNIXDiskLabel_MAGIC)
                {
                    unix_dl_sector = imagePlugin.ReadSector(8);
                    magic = BitConverter.ToUInt32(unix_dl_sector, 4);

                    if(magic != UNIXDiskLabel_MAGIC)
                    {
                        unix_dl_sector = imagePlugin.ReadSector(29);
                        magic = BitConverter.ToUInt32(unix_dl_sector, 4);

                        if(magic != UNIXDiskLabel_MAGIC)
                            return false;
                    }
                }
            }

            UNIXDiskLabel dl = new UNIXDiskLabel();
            UNIXVTOC vtoc = new UNIXVTOC(); // old/new
            bool isNewDL = false;
            int vtocoffset = 0;
            ulong counter = 0;

            vtoc.magic = BitConverter.ToUInt32(unix_dl_sector, 172);
            if(vtoc.magic == UNIXVTOC_MAGIC)
            {
                isNewDL = true;
                vtocoffset = 72;
            }
            else
            {
                vtoc.magic = BitConverter.ToUInt32(unix_dl_sector, 172);
                if(vtoc.magic != UNIXDiskLabel_MAGIC)
                {
                    return false;
                }
            }

            dl.version = BitConverter.ToUInt32(unix_dl_sector, 8); // 8
            byte[] dl_serial = new byte[12];
            Array.Copy(unix_dl_sector, 12, dl_serial, 0, 12);
            dl.serial = StringHandlers.CToString(dl_serial); // 12
            dl.cyls = BitConverter.ToUInt32(unix_dl_sector, 24); // 24
            dl.trks = BitConverter.ToUInt32(unix_dl_sector, 28); // 28
            dl.secs = BitConverter.ToUInt32(unix_dl_sector, 32); // 32
            dl.bps = BitConverter.ToUInt32(unix_dl_sector, 36); // 36
            dl.start = BitConverter.ToUInt32(unix_dl_sector, 40); // 40
            dl.alt_tbl = BitConverter.ToUInt32(unix_dl_sector, 92); // 92
            dl.alt_len = BitConverter.ToUInt32(unix_dl_sector, 96); // 96

            if(isNewDL) // Old version VTOC starts here
            {
                dl.phys_cyl = BitConverter.ToUInt32(unix_dl_sector, 100); // 100
                dl.phys_trk = BitConverter.ToUInt32(unix_dl_sector, 104); // 104
                dl.phys_sec = BitConverter.ToUInt32(unix_dl_sector, 108); // 108
                dl.phys_bytes = BitConverter.ToUInt32(unix_dl_sector, 112); // 112
                dl.unknown2 = BitConverter.ToUInt32(unix_dl_sector, 116); // 116
                dl.unknown3 = BitConverter.ToUInt32(unix_dl_sector, 120); // 120
            }

            if(vtoc.magic == UNIXVTOC_MAGIC)
            {
                vtoc.version = BitConverter.ToUInt32(unix_dl_sector, 104 + vtocoffset); // 104/176
                byte[] vtoc_name = new byte[8];
                Array.Copy(unix_dl_sector, 108 + vtocoffset, vtoc_name, 0, 8);
                vtoc.name = StringHandlers.CToString(vtoc_name); // 108/180
                vtoc.slices = BitConverter.ToUInt16(unix_dl_sector, 116 + vtocoffset); // 116/188
                vtoc.unknown = BitConverter.ToUInt16(unix_dl_sector, 118 + vtocoffset); // 118/190

                // TODO: What if number of slices overlaps sector (>23)?
                for(int j = 0; j < vtoc.slices; j++)
                {
                    UNIXVTOCEntry vtoc_ent = new UNIXVTOCEntry();

                    vtoc_ent.tag = (UNIX_TAG)BitConverter.ToUInt16(unix_dl_sector, 160 + vtocoffset + j * 12 + 0); // 160/232 + j*12
                    vtoc_ent.flags = BitConverter.ToUInt16(unix_dl_sector, 160 + vtocoffset + j * 12 + 2); // 162/234 + j*12
                    vtoc_ent.start = BitConverter.ToUInt32(unix_dl_sector, 160 + vtocoffset + j * 12 + 6); // 166/238 + j*12
                    vtoc_ent.length = BitConverter.ToUInt32(unix_dl_sector, 160 + vtocoffset + j * 12 + 10); // 170/242 + j*12

                    if((vtoc_ent.flags & 0x200) == 0x200 && vtoc_ent.tag != UNIX_TAG.EMPTY && vtoc_ent.tag != UNIX_TAG.WHOLE)
                    {
                        Partition part = new Partition();
                        part.PartitionStartSector = vtoc_ent.start;
                        part.PartitionSectors = vtoc_ent.length;
                        part.PartitionStart = vtoc_ent.start * dl.bps;
                        part.PartitionLength = vtoc_ent.length * dl.bps;
                        part.PartitionSequence = counter;
                        part.PartitionType = string.Format("UNIX: {0}", decodeUNIXTAG(vtoc_ent.tag, isNewDL));

                        string info = "";

                        if((vtoc_ent.flags & 0x01) == 0x01)
                            info += " (do not mount)";
                        if((vtoc_ent.flags & 0x10) == 0x10)
                            info += " (do not mount)";

                        part.PartitionDescription = "UNIX slice" + info + ".";

                        partitions.Add(part);
                        counter++;
                    }
                }
            }

            return true;
        }

        // Same as Solaris VTOC
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct UNIXDiskLabel
        {
            /// <summary>Drive type, seems always 0</summary>
            public uint type;
            /// <summary>UNIXDiskLabel_MAGIC</summary>
            public uint magic;
            /// <summary>Only seen 1</summary>
            public uint version;
            /// <summary>12 bytes, serial number of the device</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
            public string serial;
            /// <summary>data cylinders per device</summary>
            public uint cyls;
            /// <summary>data tracks per cylinder</summary>
            public uint trks;
            /// <summary>data sectors per track</summary>
            public uint secs;
            /// <summary>data bytes per sector</summary>
            public uint bps;
            /// <summary>first sector of this partition</summary>
            public uint start;
            /// <summary>48 bytes</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
            public byte[] unknown1;
            /// <summary>byte offset of alternate table</summary>
            public uint alt_tbl;
            /// <summary>byte length of alternate table</summary>
            public uint alt_len;
            // From onward here, is not on old version
            /// <summary>physical cylinders per device</summary>
            public uint phys_cyl;
            /// <summary>physical tracks per cylinder</summary>
            public uint phys_trk;
            /// <summary>physical sectors per track</summary>
            public uint phys_sec;
            /// <summary>physical bytes per sector</summary>
            public uint phys_bytes;
            /// <summary></summary>
            public uint unknown2;
            /// <summary></summary>
            public uint unknown3;
            /// <summary>32bytes</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] pad;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct UNIXVTOC
        {
            /// <summary>UNIXVTOC_MAGIC</summary>
            public uint magic;
            /// <summary>1</summary>
            public uint version;
            /// <summary>8 bytes</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public string name;
            /// <summary># of slices</summary>
            public ushort slices;
            /// <summary></summary>
            public ushort unknown;
            /// <summary>40 bytes</summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
            public byte[] reserved;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct UNIXVTOCEntry
        {
            /// <summary>TAG</summary>
            public UNIX_TAG tag;
            public ushort flags;
            /// <summary>Flags (see below)</summary>
            public uint start;
            /// <summary>Start sector</summary>
            public uint length;
            /// <summary>Length of slice in sectors</summary>
        }

        public enum UNIX_TAG : ushort
        {
            /// <summary>empty</summary>
            EMPTY = 0x0000,
            /// <summary>boot</summary>
            BOOT = 0x0001,
            /// <summary>root</summary>
            ROOT = 0x0002,
            /// <summary>swap</summary>
            SWAP = 0x0003,
            /// <summary>/usr</summary>
            USER = 0x0004,
            /// <summary>whole disk</summary>
            WHOLE = 0x0005,
            /// <summary>stand partition ??</summary>
            STAND = 0x0006,
            /// <summary>alternate sector space</summary>
            ALT_S = 0x0006,
            /// <summary>/var</summary>
            VAR = 0x0007,
            /// <summary>non UNIX</summary>
            OTHER = 0x0007,
            /// <summary>/home</summary>
            HOME = 0x0008,
            /// <summary>alternate track space</summary>
            ALT_T = 0x0008,
            /// <summary>alternate sector track</summary>
            ALT_ST = 0x0009,
            /// <summary>stand partition ??</summary>
            NEW_STAND = 0x0009,
            /// <summary>cache</summary>
            CACHE = 0x000A,
            /// <summary>/var</summary>
            NEW_VAR = 0x000A,
            /// <summary>reserved</summary>
            RESERVED = 0x000B,
            /// <summary>/home</summary>
            NEW_HOME = 0x000B,
            /// <summary>dump partition</summary>
            DUMP = 0x000C,
            /// <summary>alternate sector track</summary>
            NEW_ALT_ST = 0x000D,
            /// <summary>volume mgt public partition</summary>
            VM_PUBLIC = 0x000E,
            /// <summary>volume mgt private partition</summary>
            VM_PRIVATE = 0x000F
        }

        public static string decodeUNIXTAG(UNIX_TAG type, bool isNew)
        {
            switch(type)
            {
                case UNIX_TAG.EMPTY:
                    return "Unused";
                case UNIX_TAG.BOOT:
                    return "Boot";
                case UNIX_TAG.ROOT:
                    return "/";
                case UNIX_TAG.SWAP:
                    return "Swap";
                case UNIX_TAG.USER:
                    return "/usr";
                case UNIX_TAG.WHOLE:
                    return "Whole disk";
                case UNIX_TAG.STAND:
                    return isNew ? "Stand" : "Alternate sector space";
                case UNIX_TAG.VAR:
                    return isNew ? "/var" : "non UNIX";
                case UNIX_TAG.HOME:
                    return isNew ? "/home" : "Alternate track space";
                case UNIX_TAG.ALT_ST:
                    return isNew ? "Alternate sector track" : "Stand";
                case UNIX_TAG.CACHE:
                    return isNew ? "Cache" : "/var";
                case UNIX_TAG.RESERVED:
                    return isNew ? "Reserved" : "/home";
                case UNIX_TAG.DUMP:
                    return "dump";
                case UNIX_TAG.NEW_ALT_ST:
                    return "Alternate sector track";
                case UNIX_TAG.VM_PUBLIC:
                    return "volume mgt public partition";
                case UNIX_TAG.VM_PRIVATE:
                    return "volume mgt private partition";
                default:
                    return string.Format("Unknown TAG: 0x{0:X4}", type);
            }
        }

    }
}