root/src/add-ons/kernel/file_systems/ntfs/libntfs/object_id.c
/**
 * object_id.c - Processing of object ids
 *
 *      This module is part of ntfs-3g library
 *
 * Copyright (c) 2009-2019 Jean-Pierre Andre
 *
 * This program/include file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program/include file 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif

#include "compat.h"
#include "types.h"
#include "debug.h"
#include "attrib.h"
#include "inode.h"
#include "dir.h"
#include "volume.h"
#include "mft.h"
#include "index.h"
#include "lcnalloc.h"
#include "object_id.h"
#include "logging.h"
#include "misc.h"
#include "xattrs.h"

/*
 *                      Endianness considerations
 *
 *      According to RFC 4122, GUIDs should be printed with the most
 *      significant byte first, and the six fields be compared individually
 *      for ordering. RFC 4122 does not define the internal representation.
 *
 *      Windows apparently stores the first three fields in little endian
 *      order, and the last two fields in big endian order.
 *
 *      Here we always copy disk images with no endianness change,
 *      and, for indexing, GUIDs are compared as if they were a sequence
 *      of four little-endian unsigned 32 bit integers (as Windows
 *      does it that way.)
 *
 * --------------------- begin from RFC 4122 ----------------------
 * Consider each field of the UUID to be an unsigned integer as shown
 * in the table in section Section 4.1.2.  Then, to compare a pair of
 * UUIDs, arithmetically compare the corresponding fields from each
 * UUID in order of significance and according to their data type.
 * Two UUIDs are equal if and only if all the corresponding fields
 * are equal.
 *
 * UUIDs, as defined in this document, can also be ordered
 * lexicographically.  For a pair of UUIDs, the first one follows the
 * second if the most significant field in which the UUIDs differ is
 * greater for the first UUID.  The second precedes the first if the
 * most significant field in which the UUIDs differ is greater for
 * the second UUID.
 *
 * The fields are encoded as 16 octets, with the sizes and order of the
 * fields defined above, and with each field encoded with the Most
 * Significant Byte first (known as network byte order).  Note that the
 * field names, particularly for multiplexed fields, follow historical
 * practice.
 *
 * 0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                          time_low                             |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |       time_mid                |         time_hi_and_version   |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                         node (2-5)                            |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * ---------------------- end from RFC 4122 -----------------------
 */

typedef struct {
        union {
                /* alignment may be needed to evaluate collations */
                u32 alignment;
                GUID guid;
        } object_id;
} OBJECT_ID_INDEX_KEY;

typedef struct {
        le64 file_id;
        GUID birth_volume_id;
        GUID birth_object_id;
        GUID domain_id;
} OBJECT_ID_INDEX_DATA; // known as OBJ_ID_INDEX_DATA

struct OBJECT_ID_INDEX {                /* index entry in $Extend/$ObjId */
        INDEX_ENTRY_HEADER header;
        OBJECT_ID_INDEX_KEY key;
        OBJECT_ID_INDEX_DATA data;
} ;

static ntfschar objid_index_name[] = { const_cpu_to_le16('$'),
                                         const_cpu_to_le16('O') };

/*
 *                      Set the index for a new object id
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 */

static int set_object_id_index(ntfs_inode *ni, ntfs_index_context *xo,
                        const OBJECT_ID_ATTR *object_id)
{
        struct OBJECT_ID_INDEX indx;
        u64 file_id_cpu;
        le64 file_id;
        le16 seqn;

        seqn = ni->mrec->sequence_number;
        file_id_cpu = MK_MREF(ni->mft_no,le16_to_cpu(seqn));
        file_id = cpu_to_le64(file_id_cpu);
        indx.header.data_offset = const_cpu_to_le16(
                                        sizeof(INDEX_ENTRY_HEADER)
                                        + sizeof(OBJECT_ID_INDEX_KEY));
        indx.header.data_length = const_cpu_to_le16(
                                        sizeof(OBJECT_ID_INDEX_DATA));
        indx.header.reservedV = const_cpu_to_le32(0);
        indx.header.length = const_cpu_to_le16(
                                        sizeof(struct OBJECT_ID_INDEX));
        indx.header.key_length = const_cpu_to_le16(
                                        sizeof(OBJECT_ID_INDEX_KEY));
        indx.header.flags = const_cpu_to_le16(0);
        indx.header.reserved = const_cpu_to_le16(0);

        memcpy(&indx.key.object_id,object_id,sizeof(GUID));

        indx.data.file_id = file_id;
        memcpy(&indx.data.birth_volume_id,
                        &object_id->birth_volume_id,sizeof(GUID));
        memcpy(&indx.data.birth_object_id,
                        &object_id->birth_object_id,sizeof(GUID));
        memcpy(&indx.data.domain_id,
                        &object_id->domain_id,sizeof(GUID));
        ntfs_index_ctx_reinit(xo);
        return (ntfs_ie_add(xo,(INDEX_ENTRY*)&indx));
}

/*
 *              Open the $Extend/$ObjId file and its index
 *
 *      Return the index context if opened
 *              or NULL if an error occurred (errno tells why)
 *
 *      The index has to be freed and inode closed when not needed any more.
 */

static ntfs_index_context *open_object_id_index(ntfs_volume *vol)
{
        u64 inum;
        ntfs_inode *ni;
        ntfs_inode *dir_ni;
        ntfs_index_context *xo;

                /* do not use path_name_to inode - could reopen root */
        dir_ni = ntfs_inode_open(vol, FILE_Extend);
        ni = (ntfs_inode*)NULL;
        if (dir_ni) {
                inum = ntfs_inode_lookup_by_mbsname(dir_ni,"$ObjId");
                if (inum != (u64)-1)
                        ni = ntfs_inode_open(vol, inum);
                ntfs_inode_close(dir_ni);
        }
        if (ni) {
                xo = ntfs_index_ctx_get(ni, objid_index_name, 2);
                if (!xo) {
                        ntfs_inode_close(ni);
                }
        } else
                xo = (ntfs_index_context*)NULL;
        return (xo);
}


/*
 *              Merge object_id data stored in the index into
 *      a full object_id struct.
 *
 *      returns 0 if merging successful
 *              -1 if no data could be merged. This is generally not an error
 */

static int merge_index_data(ntfs_inode *ni,
                        const OBJECT_ID_ATTR *objectid_attr,
                        OBJECT_ID_ATTR *full_objectid)
{
        OBJECT_ID_INDEX_KEY key;
        struct OBJECT_ID_INDEX *entry;
        ntfs_index_context *xo;
        ntfs_inode *xoni;
        int res;

        res = -1;
        xo = open_object_id_index(ni->vol);
        if (xo) {
                memcpy(&key.object_id,objectid_attr,sizeof(GUID));
                if (!ntfs_index_lookup(&key,
                                sizeof(OBJECT_ID_INDEX_KEY), xo)) {
                        entry = (struct OBJECT_ID_INDEX*)xo->entry;
                        /* make sure inode numbers match */
                        if (entry
                            && (MREF(le64_to_cpu(entry->data.file_id))
                                        == ni->mft_no)) {
                                memcpy(&full_objectid->birth_volume_id,
                                                &entry->data.birth_volume_id,
                                                sizeof(GUID));
                                memcpy(&full_objectid->birth_object_id,
                                                &entry->data.birth_object_id,
                                                sizeof(GUID));
                                memcpy(&full_objectid->domain_id,
                                                &entry->data.domain_id,
                                                sizeof(GUID));
                                res = 0;
                        }
                }
                xoni = xo->ni;
                ntfs_index_ctx_put(xo);
                ntfs_inode_close(xoni);
        }
        return (res);
}


/*
 *              Remove an object id index entry if attribute present
 *
 *      Returns the size of existing object id
 *                      (the existing object_d is returned)
 *              -1 if failure, explained by errno
 */

static int remove_object_id_index(ntfs_attr *na, ntfs_index_context *xo,
                                OBJECT_ID_ATTR *old_attr)
{
        OBJECT_ID_INDEX_KEY key;
        struct OBJECT_ID_INDEX *entry;
        s64 size;
        int ret;

        ret = na->data_size;
        if (ret) {
                        /* read the existing object id attribute */
                size = ntfs_attr_pread(na, 0, sizeof(GUID), old_attr);
                if (size >= (s64)sizeof(GUID)) {
                        memcpy(&key.object_id,
                                &old_attr->object_id,sizeof(GUID));
                        if (!ntfs_index_lookup(&key,
                                        sizeof(OBJECT_ID_INDEX_KEY), xo)) {
                                entry = (struct OBJECT_ID_INDEX*)xo->entry;
                                memcpy(&old_attr->birth_volume_id,
                                        &entry->data.birth_volume_id,
                                        sizeof(GUID));
                                memcpy(&old_attr->birth_object_id,
                                        &entry->data.birth_object_id,
                                        sizeof(GUID));
                                memcpy(&old_attr->domain_id,
                                        &entry->data.domain_id,
                                        sizeof(GUID));
                                if (ntfs_index_rm(xo))
                                        ret = -1;
                        }
                } else {
                        ret = -1;
                        errno = ENODATA;
                }
        }
        return (ret);
}


/*
 *              Update the object id and index
 *
 *      The object_id attribute should have been created and the
 *      non-duplication of the GUID should have been checked before.
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 *      If could not remove the existing index, nothing is done,
 *      If could not write the new data, no index entry is inserted
 *      If failed to insert the index, data is removed
 */

static int update_object_id(ntfs_inode *ni, ntfs_index_context *xo,
                        const OBJECT_ID_ATTR *value, size_t size)
{
        OBJECT_ID_ATTR old_attr;
        ntfs_attr *na;
        int oldsize;
        int written;
        int res;

        res = 0;

        na = ntfs_attr_open(ni, AT_OBJECT_ID, AT_UNNAMED, 0);
        if (na) {
                memset(&old_attr, 0, sizeof(OBJECT_ID_ATTR));
                        /* remove the existing index entry */
                oldsize = remove_object_id_index(na,xo,&old_attr);
                if (oldsize < 0)
                        res = -1;
                else {
                        /* resize attribute */
                        res = ntfs_attr_truncate(na, (s64)sizeof(GUID));
                                /* write the object_id in attribute */
                        if (!res && value) {
                                written = (int)ntfs_attr_pwrite(na,
                                        (s64)0, (s64)sizeof(GUID),
                                        &value->object_id);
                                if (written != (s64)sizeof(GUID)) {
                                        ntfs_log_error("Failed to update "
                                                        "object id\n");
                                        errno = EIO;
                                        res = -1;
                                }
                        }
                                /* overwrite index data with new value */
                        memcpy(&old_attr, value,
                                (size < sizeof(OBJECT_ID_ATTR)
                                        ? size : sizeof(OBJECT_ID_ATTR)));
                        if (!res
                            && set_object_id_index(ni,xo,&old_attr)) {
                                /*
                                 * If cannot index, try to remove the object
                                 * id and log the error. There will be an
                                 * inconsistency if removal fails.
                                 */
                                ntfs_attr_rm(na);
                                ntfs_log_error("Failed to index object id."
                                                " Possible corruption.\n");
                        }
                }
                ntfs_attr_close(na);
                NInoSetDirty(ni);
        } else
                res = -1;
        return (res);
}

/*
 *              Add a (dummy) object id to an inode if it does not exist
 *
 *      returns 0 if attribute was inserted (or already present)
 *              -1 if adding failed (explained by errno)
 */

static int add_object_id(ntfs_inode *ni, int flags)
{
        int res;
        u8 dummy;

        res = -1; /* default return */
        if (!ntfs_attr_exist(ni,AT_OBJECT_ID, AT_UNNAMED,0)) {
                if (!(flags & XATTR_REPLACE)) {
                        /*
                         * no object id attribute : add one,
                         * apparently, this does not feed the new value in
                         * Note : NTFS version must be >= 3
                         */
                        if (ni->vol->major_ver >= 3) {
                                res = ntfs_attr_add(ni, AT_OBJECT_ID,
                                                AT_UNNAMED, 0, &dummy, (s64)0);
                                NInoSetDirty(ni);
                        } else
                                errno = EOPNOTSUPP;
                } else
                        errno = ENODATA;
        } else {
                if (flags & XATTR_CREATE)
                        errno = EEXIST;
                else
                        res = 0;
        }
        return (res);
}


/*
 *              Delete an object_id index entry
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 */

int ntfs_delete_object_id_index(ntfs_inode *ni)
{
        ntfs_index_context *xo;
        ntfs_inode *xoni;
        ntfs_attr *na;
        OBJECT_ID_ATTR old_attr;
        int res;

        res = 0;
        na = ntfs_attr_open(ni, AT_OBJECT_ID, AT_UNNAMED, 0);
        if (na) {
                        /*
                         * read the existing object id
                         * and un-index it
                         */
                xo = open_object_id_index(ni->vol);
                if (xo) {
                        if (remove_object_id_index(na,xo,&old_attr) < 0)
                                res = -1;
                        xoni = xo->ni;
                        ntfs_index_entry_mark_dirty(xo);
                        NInoSetDirty(xoni);
                        ntfs_index_ctx_put(xo);
                        ntfs_inode_close(xoni);
                }
                ntfs_attr_close(na);
        }
        return (res);
}


/*
 *              Get the ntfs object id into an extended attribute
 *
 *      If present, the object_id from the attribute and the GUIDs
 *      from the index are returned (formatted as OBJECT_ID_ATTR)
 *
 *      Returns the global size (can be 0, 16 or 64)
 *              and the buffer is updated if it is long enough
 */

int ntfs_get_ntfs_object_id(ntfs_inode *ni, char *value, size_t size)
{
        OBJECT_ID_ATTR full_objectid;
        OBJECT_ID_ATTR *objectid_attr;
        s64 attr_size;
        int full_size;

        full_size = 0;  /* default to no data and some error to be defined */
        if (ni) {
                objectid_attr = (OBJECT_ID_ATTR*)ntfs_attr_readall(ni,
                        AT_OBJECT_ID,(ntfschar*)NULL, 0, &attr_size);
                if (objectid_attr) {
                                /* restrict to only GUID present in attr */
                        if (attr_size == sizeof(GUID)) {
                                memcpy(&full_objectid.object_id,
                                                objectid_attr,sizeof(GUID));
                                full_size = sizeof(GUID);
                                        /* get data from index, if any */
                                if (!merge_index_data(ni, objectid_attr,
                                                &full_objectid)) {
                                        full_size = sizeof(OBJECT_ID_ATTR);
                                }
                                if (full_size <= (s64)size) {
                                        if (value)
                                                memcpy(value,&full_objectid,
                                                        full_size);
                                        else
                                                errno = EINVAL;
                                }
                        } else {
                        /* unexpected size, better return unsupported */
                                errno = EOPNOTSUPP;
                                full_size = 0;
                        }
                        free(objectid_attr);
                } else
                        errno = ENODATA;
        }
        return (full_size ? (int)full_size : -errno);
}

/*
 *              Set the object id from an extended attribute
 *
 *      The first 16 bytes are the new object id, they can be followed
 *      by the birth volume id, the birth object id and the domain id.
 *      If they are not present, their previous value is kept.
 *      Only the object id is stored into the attribute, all the fields
 *      are stored into the index.
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_set_ntfs_object_id(ntfs_inode *ni,
                        const char *value, size_t size, int flags)
{
        OBJECT_ID_INDEX_KEY key;
        ntfs_inode *xoni;
        ntfs_index_context *xo;
        int res;

        res = 0;
        if (ni && value && (size >= sizeof(GUID))) {
                xo = open_object_id_index(ni->vol);
                if (xo) {
                        /* make sure the GUID was not used elsewhere */
                        memcpy(&key.object_id, value, sizeof(GUID));
                        if ((ntfs_index_lookup(&key,
                                        sizeof(OBJECT_ID_INDEX_KEY), xo))
                            || (MREF_LE(((struct OBJECT_ID_INDEX*)xo->entry)
                                        ->data.file_id) == ni->mft_no)) {
                                ntfs_index_ctx_reinit(xo);
                                res = add_object_id(ni, flags);
                                if (!res) {
                                                /* update value and index */
                                        res = update_object_id(ni,xo,
                                                (const OBJECT_ID_ATTR*)value,
                                                size);
                                }
                        } else {
                                        /* GUID is present elsewhere */
                                res = -1;
                                errno = EEXIST;
                        }
                        xoni = xo->ni;
                        ntfs_index_entry_mark_dirty(xo);
                        NInoSetDirty(xoni);
                        ntfs_index_ctx_put(xo);
                        ntfs_inode_close(xoni);
                } else {
                        res = -1;
                }
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}

/*
 *              Remove the object id
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_remove_ntfs_object_id(ntfs_inode *ni)
{
        int res;
        int olderrno;
        ntfs_attr *na;
        ntfs_inode *xoni;
        ntfs_index_context *xo;
        int oldsize;
        OBJECT_ID_ATTR old_attr;

        res = 0;
        if (ni) {
                /*
                 * open and delete the object id
                 */
                na = ntfs_attr_open(ni, AT_OBJECT_ID,
                        AT_UNNAMED,0);
                if (na) {
                        /* first remove index (old object id needed) */
                        xo = open_object_id_index(ni->vol);
                        if (xo) {
                                oldsize = remove_object_id_index(na,xo,
                                                &old_attr);
                                if (oldsize < 0) {
                                        res = -1;
                                } else {
                                        /* now remove attribute */
                                        res = ntfs_attr_rm(na);
                                        if (res
                                            && (oldsize > (int)sizeof(GUID))) {
                                        /*
                                         * If we could not remove the
                                         * attribute, try to restore the
                                         * index and log the error. There
                                         * will be an inconsistency if
                                         * the reindexing fails.
                                         */
                                                set_object_id_index(ni, xo,
                                                        &old_attr);
                                                ntfs_log_error(
                                                "Failed to remove object id."
                                                " Possible corruption.\n");
                                        }
                                }

                                xoni = xo->ni;
                                ntfs_index_entry_mark_dirty(xo);
                                NInoSetDirty(xoni);
                                ntfs_index_ctx_put(xo);
                                ntfs_inode_close(xoni);
                        }
                        olderrno = errno;
                        ntfs_attr_close(na);
                                        /* avoid errno pollution */
                        if (errno == ENOENT)
                                errno = olderrno;
                } else {
                        errno = ENODATA;
                        res = -1;
                }
                NInoSetDirty(ni);
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}