/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.nio.fs;

import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.*;
import java.util.*;
import java.io.IOException;

import static sun.nio.fs.WindowsNativeDispatcher.*;
import static sun.nio.fs.WindowsConstants.*;

/**
 * Windows implementation of AclFileAttributeView.
 */

class WindowsAclFileAttributeView
    extends AbstractAclFileAttributeView
{
    /**
     * typedef struct _SECURITY_DESCRIPTOR {
     *     BYTE  Revision;
     *     BYTE  Sbz1;
     *     SECURITY_DESCRIPTOR_CONTROL Control;
     *     PSID Owner;
     *     PSID Group;
     *     PACL Sacl;
     *     PACL Dacl;
     * } SECURITY_DESCRIPTOR;
     */
    private static final short SIZEOF_SECURITY_DESCRIPTOR   = 20;

    private final WindowsPath file;
    private final boolean followLinks;

    WindowsAclFileAttributeView(WindowsPath file, boolean followLinks) {
        this.file = file;
        this.followLinks = followLinks;
    }

    // permision check
    private void checkAccess(WindowsPath file,
                             boolean checkRead,
                             boolean checkWrite)
    {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            if (checkRead)
                sm.checkRead(file.getPathForPermissionCheck());
            if (checkWrite)
                sm.checkWrite(file.getPathForPermissionCheck());
            sm.checkPermission(new RuntimePermission("accessUserInformation"));
        }
    }

    // invokes GetFileSecurity to get requested security information
    static NativeBuffer getFileSecurity(String path, int request)
        throws IOException
    {
        // invoke get to buffer size
        int size = 0;
        try {
            size = GetFileSecurity(path, request, 0L, 0);
        } catch (WindowsException x) {
            x.rethrowAsIOException(path);
        }
        assert size > 0;

        // allocate buffer and re-invoke to get security information
        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
        try {
            for (;;) {
                int newSize = GetFileSecurity(path, request, buffer.address(), size);
                if (newSize <= size)
                    return buffer;

                // buffer was insufficient
                buffer.release();
                buffer = NativeBuffers.getNativeBuffer(newSize);
                size = newSize;
            }
        } catch (WindowsException x) {
            buffer.release();
            x.rethrowAsIOException(path);
            return null;
        }
    }


    public UserPrincipal getOwner()
        throws IOException
    {
        checkAccess(file, true, false);

        // GetFileSecurity does not follow links so when following links we
        // need the final target
        String path = WindowsLinkSupport.getFinalPath(file, followLinks);
        NativeBuffer buffer = getFileSecurity(path, OWNER_SECURITY_INFORMATION);
        try {
            // get the address of the SID
            long sidAddress = GetSecurityDescriptorOwner(buffer.address());
            if (sidAddress == 0L)
                throw new IOException("no owner");
            return WindowsUserPrincipals.fromSid(sidAddress);
        } catch (WindowsException x) {
            x.rethrowAsIOException(file);
            return null;
        } finally {
            buffer.release();
        }
    }


    public List<AclEntry> getAcl()
        throws IOException
    {
        checkAccess(file, true, false);

        // GetFileSecurity does not follow links so when following links we
        // need the final target
        String path = WindowsLinkSupport.getFinalPath(file, followLinks);

        // ALLOW and DENY entries in DACL;
        // AUDIT entries in SACL (ignore for now as it requires privileges)
        NativeBuffer buffer = getFileSecurity(path, DACL_SECURITY_INFORMATION);
        try {
            return WindowsSecurityDescriptor.getAcl(buffer.address());
        } finally {
            buffer.release();
        }
    }


    public void setOwner(UserPrincipal obj)
        throws IOException
    {
        if (obj == null)
            throw new NullPointerException("'owner' is null");
        if (!(obj instanceof WindowsUserPrincipals.User))
            throw new ProviderMismatchException();
        WindowsUserPrincipals.User owner = (WindowsUserPrincipals.User)obj;

        // permission check
        checkAccess(file, false, true);

        // SetFileSecurity does not follow links so when following links we
        // need the final target
        String path = WindowsLinkSupport.getFinalPath(file, followLinks);

        // ConvertStringSidToSid allocates memory for SID so must invoke
        // LocalFree to free it when we are done
        long pOwner = 0L;
        try {
            pOwner = ConvertStringSidToSid(owner.sidString());
        } catch (WindowsException x) {
            throw new IOException("Failed to get SID for " + owner.getName()
                + ": " + x.errorString());
        }

        // Allocate buffer for security descriptor, initialize it, set
        // owner information and update the file.
        try {
            NativeBuffer buffer = NativeBuffers.getNativeBuffer(SIZEOF_SECURITY_DESCRIPTOR);
            try {
                InitializeSecurityDescriptor(buffer.address());
                SetSecurityDescriptorOwner(buffer.address(), pOwner);
                // may need SeRestorePrivilege to set the owner
                WindowsSecurity.Privilege priv =
                    WindowsSecurity.enablePrivilege("SeRestorePrivilege");
                try {
                    SetFileSecurity(path,
                                    OWNER_SECURITY_INFORMATION,
                                    buffer.address());
                } finally {
                    priv.drop();
                }
            } catch (WindowsException x) {
                x.rethrowAsIOException(file);
            } finally {
                buffer.release();
            }
        } finally {
            LocalFree(pOwner);
        }
    }


    public void setAcl(List<AclEntry> acl) throws IOException {
        checkAccess(file, false, true);

        // SetFileSecurity does not follow links so when following links we
        // need the final target
        String path = WindowsLinkSupport.getFinalPath(file, followLinks);
        WindowsSecurityDescriptor sd = WindowsSecurityDescriptor.create(acl);
        try {
            SetFileSecurity(path, DACL_SECURITY_INFORMATION, sd.address());
        } catch (WindowsException x) {
             x.rethrowAsIOException(file);
        } finally {
            sd.release();
        }
    }
}
