# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# Copyright 2018 Michael Still and Aptira
#
#    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.

# This module is utility methods that privsep depends on. Privsep isn't allowed
# to depend on anything outside the privsep directory, so these need to be
# here. That said, other parts of nova can call into these utilities if
# needed.

import errno
import mmap
import os
import random
import sys

from oslo_log import log as logging
from oslo_utils import excutils

# NOTE(mriedem): Avoid importing nova.utils since that can cause a circular
# import with the privsep code. In fact, avoid importing anything outside
# of nova/privsep/ if possible.

LOG = logging.getLogger(__name__)


def generate_random_string():
    return str(random.randint(0, sys.maxsize))


def supports_direct_io(dirpath):

    if not hasattr(os, 'O_DIRECT'):
        LOG.debug("This python runtime does not support direct I/O")
        return False

    # Use a random filename to avoid issues with $dirpath being on shared
    # storage.
    file_name = "%s.%s" % (".directio.test", generate_random_string())
    testfile = os.path.join(dirpath, file_name)

    hasDirectIO = True
    fd = None
    try:
        fd = os.open(testfile, os.O_CREAT | os.O_WRONLY | os.O_DIRECT)
        # Check is the write allowed with 4096 byte alignment
        align_size = 4096
        m = mmap.mmap(-1, align_size)
        m.write(b"x" * align_size)
        os.write(fd, m)
        LOG.debug("Path '%(path)s' supports direct I/O",
                  {'path': dirpath})
    except OSError as e:
        if e.errno in (errno.EINVAL, errno.ENOENT):
            LOG.debug("Path '%(path)s' does not support direct I/O: "
                      "'%(ex)s'", {'path': dirpath, 'ex': e})
            hasDirectIO = False
        else:
            with excutils.save_and_reraise_exception():
                LOG.error("Error on '%(path)s' while checking "
                          "direct I/O: '%(ex)s'",
                          {'path': dirpath, 'ex': e})
    except Exception as e:
        with excutils.save_and_reraise_exception():
            LOG.error("Error on '%(path)s' while checking direct I/O: "
                      "'%(ex)s'", {'path': dirpath, 'ex': e})
    finally:
        # ensure unlink(filepath) will actually remove the file by deleting
        # the remaining link to it in close(fd)
        if fd is not None:
            os.close(fd)

        try:
            os.unlink(testfile)
        except Exception:
            pass

    return hasDirectIO
