root/src/add-ons/kernel/file_systems/ntfs/libntfs/ea.c
/**
 * ea.c - Processing of EA's
 *
 *      This module is part of ntfs-3g library
 *
 * Copyright (c) 2014-2021 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_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif

#include "types.h"
#include "param.h"
#include "layout.h"
#include "attrib.h"
#include "index.h"
#include "dir.h"
#include "ea.h"
#include "misc.h"
#include "logging.h"
#include "xattrs.h"

static const char lxdev[] = "$LXDEV";
static const char lxmod[] = "$LXMOD";


/*
 *              Create a needed attribute (EA or EA_INFORMATION)
 *
 *      Returns 0 if successful,
 *              -1 otherwise, with errno indicating why it failed.
 */

static int ntfs_need_ea(ntfs_inode *ni, ATTR_TYPES type, int size, int flags)
{
        u8 dummy;
        int res;

        res = 0;
        if (!ntfs_attr_exist(ni,type, AT_UNNAMED,0)) {
                if (!(flags & XATTR_REPLACE)) {
                        /*
                         * no needed 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, type,
                                        AT_UNNAMED,0,&dummy,(s64)size);
                                if (!res) {
                                            NInoFileNameSetDirty(ni);
                                }
                                NInoSetDirty(ni);
                        } else {
                                errno = EOPNOTSUPP;
                                res = -1;
                        }
                } else {
                        errno = ENODATA;
                        res = -1;
                }
        }
        return (res);
}

/*
 *              Restore the old EA_INFORMATION or delete the current one,
 *       when EA cannot be updated.
 *
 *      As this is used in the context of some other error, the caller
 *      is responsible for returning the proper error, and errno is
 *      left unchanged.
 *      Only double errors are logged here.
 */

static void restore_ea_info(ntfs_attr *nai, const EA_INFORMATION *old_ea_info)
{
        s64 written;
        int olderrno;

        olderrno = errno;
        if (old_ea_info) {
                written = ntfs_attr_pwrite(nai, 0, sizeof(EA_INFORMATION),
                                old_ea_info);
                if ((size_t)written != sizeof(EA_INFORMATION)) {
                        ntfs_log_error("Could not restore the EA_INFORMATION,"
                                " possible inconsistency in inode %lld\n",
                                (long long)nai->ni->mft_no);
                }
        } else {
                if (ntfs_attr_rm(nai)) {
                        ntfs_log_error("Could not delete the EA_INFORMATION,"
                                " possible inconsistency in inode %lld\n",
                                (long long)nai->ni->mft_no);
                }
        }
        errno = olderrno;
}

/*
 *              Update both EA and EA_INFORMATION
 */

static int ntfs_update_ea(ntfs_inode *ni, const char *value, size_t size,
                        const EA_INFORMATION *ea_info,
                        const EA_INFORMATION *old_ea_info)
{
        ntfs_attr *na;
        ntfs_attr *nai;
        int res;

        res = 0;
        nai = ntfs_attr_open(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
        if (nai) {
                na = ntfs_attr_open(ni, AT_EA, AT_UNNAMED, 0);
                if (na) {
                                /*
                                 * Set EA_INFORMATION first, it is easier to
                                 * restore the old value, if setting EA fails.
                                 */
                        if (ntfs_attr_pwrite(nai, 0, sizeof(EA_INFORMATION),
                                                ea_info)
                                        != (s64)sizeof(EA_INFORMATION)) {
                                res = -errno;
                        } else {
                                if (((na->data_size > (s64)size)
                                        && ntfs_attr_truncate(na, size))
                                    || (ntfs_attr_pwrite(na, 0, size, value)
                                                        != (s64)size)) {
                                        res = -errno;
                                        if (old_ea_info)
                                                restore_ea_info(nai,
                                                        old_ea_info);
                                }
                        }
                        ntfs_attr_close(na);
                }
                ntfs_attr_close(nai);
        } else {
                res = -errno;
        }
        return (res);
}

/*
 *              Return the existing EA
 *
 *      The EA_INFORMATION is not examined and the consistency of the
 *      existing EA is not checked.
 *
 *      If successful, the full attribute is returned unchanged
 *              and its size is returned.
 *      If the designated buffer is too small, the needed size is
 *              returned, and the buffer is left unchanged.
 *      If there is an error, a negative value is returned and errno
 *              is set according to the error.
 */

int ntfs_get_ntfs_ea(ntfs_inode *ni, char *value, size_t size)
{
        s64 ea_size;
        void *ea_buf;
        int res = 0;

        if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
                ea_buf = ntfs_attr_readall(ni, AT_EA, (ntfschar*)NULL, 0,
                                        &ea_size);
                if (ea_buf) {
                        if (value && (ea_size <= (s64)size))
                                memcpy(value, ea_buf, ea_size);
                        free(ea_buf);
                        res = ea_size;
                } else {
                        ntfs_log_error("Failed to read EA from inode %lld\n",
                                        (long long)ni->mft_no);
                        errno = ENODATA;
                        res = -errno;
                }
        } else {
                errno = ENODATA;
                res = -errno;
        }
        return (res);
}

/*
 *              Set a new EA, and set EA_INFORMATION accordingly
 *
 *      This is roughly the same as ZwSetEaFile() on Windows, however
 *      the "offset to next" of the last EA should not be cleared.
 *
 *      Consistency of the new EA is first checked.
 *
 *      EA_INFORMATION is set first, and it is restored to its former
 *      state if setting EA fails.
 *
 *      Returns 0 if successful
 *              a negative value if an error occurred.
 */

int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, size_t size, int flags)
{
        EA_INFORMATION ea_info;
        EA_INFORMATION *old_ea_info;
        s64 old_ea_size;
        int res;
        size_t offs;
        size_t nextoffs;
        BOOL ok;
        int ea_count;
        int ea_packed;
        const EA_ATTR *p_ea;

        res = -1;
        if (value && (size > 0)) {
                                        /* do consistency checks */
                offs = 0;
                ok = TRUE;
                ea_count = 0;
                ea_packed = 0;
                nextoffs = 0;
                while (ok && (offs < size)) {
                        p_ea = (const EA_ATTR*)&value[offs];
                        nextoffs = offs + le32_to_cpu(p_ea->next_entry_offset);
                                /* null offset to next not allowed */
                        ok = (nextoffs > offs)
                            && (nextoffs <= size)
                            && !(nextoffs & 3)
                            && p_ea->name_length
                                /* zero sized value are allowed */
                            && ((offs + offsetof(EA_ATTR,name)
                                + p_ea->name_length + 1
                                + le16_to_cpu(p_ea->value_length))
                                    <= nextoffs)
                            && ((offs + offsetof(EA_ATTR,name)
                                + p_ea->name_length + 1
                                + le16_to_cpu(p_ea->value_length))
                                    >= (nextoffs - 3))
                            && !p_ea->name[p_ea->name_length];
                        /* name not checked, as chkdsk accepts any chars */
                        if (ok) {
                                if (p_ea->flags & NEED_EA)
                                        ea_count++;
                                /*
                                 * Assume ea_packed includes :
                                 * 4 bytes for header (flags and lengths)
                                 * + name length + 1
                                 * + value length
                                 */
                                ea_packed += 5 + p_ea->name_length
                                        + le16_to_cpu(p_ea->value_length);
                                offs = nextoffs;
                        }
                }
                /*
                 * EA and REPARSE_POINT compatibility not checked any more,
                 * required by Windows 10, but having both may lead to
                 * problems with earlier versions.
                 */
                if (ok) {
                        ea_info.ea_length = cpu_to_le16(ea_packed);
                        ea_info.need_ea_count = cpu_to_le16(ea_count);
                        ea_info.ea_query_length = cpu_to_le32(nextoffs);

                        old_ea_size = 0;
                        old_ea_info = NULL;
                                /* Try to save the old EA_INFORMATION */
                        if (ntfs_attr_exist(ni, AT_EA_INFORMATION,
                                                        AT_UNNAMED, 0)) {
                                old_ea_info = ntfs_attr_readall(ni,
                                        AT_EA_INFORMATION,
                                        (ntfschar*)NULL, 0, &old_ea_size);
                        }
                        /*
                         * no EA or EA_INFORMATION : add them
                         */
                        if (!ntfs_need_ea(ni, AT_EA_INFORMATION,
                                        sizeof(EA_INFORMATION), flags)
                            && !ntfs_need_ea(ni, AT_EA, 0, flags)) {
                                res = ntfs_update_ea(ni, value, size,
                                                &ea_info, old_ea_info);
                        } else {
                                res = -errno;
                        }
                        if (old_ea_info)
                                free(old_ea_info);
                } else {
                        errno = EINVAL;
                        res = -errno;
                }
        } else {
                errno = EINVAL;
                res = -errno;
        }
        return (res);
}

/*
 *              Remove the EA (including EA_INFORMATION)
 *
 *      EA_INFORMATION is removed first, and it is restored to its former
 *      state if removing EA fails.
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_remove_ntfs_ea(ntfs_inode *ni)
{
        EA_INFORMATION *old_ea_info;
        s64 old_ea_size;
        int res;
        ntfs_attr *na;
        ntfs_attr *nai;

        res = 0;
        if (ni) {
                /*
                 * open and delete the EA_INFORMATION and the EA
                 */
                nai = ntfs_attr_open(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
                if (nai) {
                        na = ntfs_attr_open(ni, AT_EA, AT_UNNAMED, 0);
                        if (na) {
                                /* Try to save the old EA_INFORMATION */
                                old_ea_info = ntfs_attr_readall(ni,
                                         AT_EA_INFORMATION,
                                         (ntfschar*)NULL, 0, &old_ea_size);
                                res = ntfs_attr_rm(na);
                                NInoFileNameSetDirty(ni);
                                if (!res) {
                                        res = ntfs_attr_rm(nai);
                                        if (res && old_ea_info) {
                                        /*
                                         * Failed to remove the EA, try to
                                         * restore the EA_INFORMATION
                                         */
                                                restore_ea_info(nai,
                                                        old_ea_info);
                                        }
                                } else {
                                        ntfs_log_error("Failed to remove the"
                                                " EA_INFORMATION from inode %lld\n",
                                                (long long)ni->mft_no);
                                }
                                free(old_ea_info);
                                ntfs_attr_close(na);
                        } else {
                                /* EA_INFORMATION present, but no EA */
                                res = ntfs_attr_rm(nai);
                                NInoFileNameSetDirty(ni);
                        }
                        ntfs_attr_close(nai);
                } else {
                        errno = ENODATA;
                        res = -1;
                }
                NInoSetDirty(ni);
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}

/*
 *              Check for the presence of an EA "$LXDEV" (used by WSL)
 *      and return its value as a device address
 *
 *      Returns zero if successful
 *              -1 if failed, with errno set
 */

int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp)
{
        const EA_ATTR *p_ea;
        int bufsize;
        char *buf;
        int lth;
        int res;
        int offset;
        int next;
        BOOL found;
        struct {
                le32 major;
                le32 minor;
        } device;

        res = -EOPNOTSUPP;
        bufsize = 256; /* expected to be enough */
        buf = (char*)malloc(bufsize);
        if (buf) {
                lth = ntfs_get_ntfs_ea(ni, buf, bufsize);
                        /* retry if short buf */
                if (lth > bufsize) {
                        free(buf);
                        bufsize = lth;
                        buf = (char*)malloc(bufsize);
                        if (buf)
                                lth = ntfs_get_ntfs_ea(ni, buf, bufsize);
                }
        }
        if (buf && (lth > 0) && (lth <= bufsize)) {
                offset = 0;
                found = FALSE;
                do {
                        p_ea = (const EA_ATTR*)&buf[offset];
                        next = le32_to_cpu(p_ea->next_entry_offset);
                        found = ((next > (int)(sizeof(lxdev) + sizeof(device)))
                                && (p_ea->name_length == (sizeof(lxdev) - 1))
                                && (p_ea->value_length
                                        == const_cpu_to_le16(sizeof(device)))
                                && !memcmp(p_ea->name, lxdev, sizeof(lxdev)));
                        if (!found)
                                offset += next;
                } while (!found && (next > 0) && (offset < lth));
                if (found) {
                                /* beware of alignment */
                        memcpy(&device, &p_ea->name[p_ea->name_length + 1],
                                        sizeof(device));
                        *rdevp = makedev(le32_to_cpu(device.major),
                                        le32_to_cpu(device.minor));
                        res = 0;
                }
        }
        free(buf);
        return (res);
}

int ntfs_ea_set_wsl_not_symlink(ntfs_inode *ni, mode_t type, dev_t dev)
{
        le32 mode;
        struct {
                le32 major;
                le32 minor;
        } device;
        struct EA_WSL {
                struct EA_LXMOD {       /* always inserted */
                        EA_ATTR base;
                        char name[sizeof(lxmod)];
                        char value[sizeof(mode)];
                        char stuff[3 & -(sizeof(lxmod) + sizeof(mode))];
                } mod;
                struct EA_LXDEV {       /* char or block devices only */
                        EA_ATTR base;
                        char name[sizeof(lxdev)];
                        char value[sizeof(device)];
                        char stuff[3 & -(sizeof(lxdev) + sizeof(device))];
                } dev;
        } attr;
        int len;
        int res;

        memset(&attr, 0, sizeof(attr));
        mode = cpu_to_le32((u32)(type | 0644));
        attr.mod.base.next_entry_offset
                        = const_cpu_to_le32(sizeof(attr.mod));
        attr.mod.base.flags = 0;
        attr.mod.base.name_length = sizeof(lxmod) - 1;
        attr.mod.base.value_length = const_cpu_to_le16(sizeof(mode));
        memcpy(attr.mod.name, lxmod, sizeof(lxmod));
        memcpy(attr.mod.value, &mode, sizeof(mode));
        len = sizeof(attr.mod);

        if (S_ISCHR(type) || S_ISBLK(type)) {
                device.major = cpu_to_le32(major(dev));
                device.minor = cpu_to_le32(minor(dev));
                attr.dev.base.next_entry_offset
                        = const_cpu_to_le32(sizeof(attr.dev));
                attr.dev.base.flags = 0;
                attr.dev.base.name_length = sizeof(lxdev) - 1;
                attr.dev.base.value_length = const_cpu_to_le16(sizeof(device));
                memcpy(attr.dev.name, lxdev, sizeof(lxdev));
                memcpy(attr.dev.value, &device, sizeof(device));
                len += sizeof(attr.dev);
                }
        res = ntfs_set_ntfs_ea(ni, (char*)&attr, len, 0);
        return (res);
}