root/tests/fs/puffs/h_dtfs/dtfs_subr.c
/*      $NetBSD: dtfs_subr.c,v 1.5 2021/09/16 22:19:12 andvar Exp $     */

/*
 * Copyright (c) 2006  Antti Kantee.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/time.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <puffs.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

#include "dtfs.h"

void
dtfs_baseattrs(struct vattr *vap, enum vtype type, ino_t id)
{
        struct timeval tv;
        struct timespec ts;

        gettimeofday(&tv, NULL);
        TIMEVAL_TO_TIMESPEC(&tv, &ts);

        vap->va_type = type;
        if (type == VDIR) {
                vap->va_mode = 0777;
                vap->va_nlink = 1;      /* n + 1 after adding dent */
        } else {
                vap->va_mode = 0666;
                vap->va_nlink = 0;      /* n + 1 */
        }
        vap->va_uid = 0;
        vap->va_gid = 0;
        vap->va_fileid = id;
        vap->va_size = 0;
        vap->va_blocksize = getpagesize();
        vap->va_gen = random();
        vap->va_flags = 0;
        vap->va_rdev = PUFFS_VNOVAL;
        vap->va_bytes = 0;
        vap->va_filerev = 1;
        vap->va_vaflags = 0;

        vap->va_atime = vap->va_mtime = vap->va_ctime = vap->va_birthtime = ts;
}

/*
 * Well, as you can probably see, this interface has the slight problem
 * of assuming file creation will always be successful, or at least not
 * giving a reason for the failure.  Be sure to do better when you
 * implement your own fs.
 */
struct puffs_node *
dtfs_genfile(struct puffs_node *dir, const struct puffs_cn *pcn,
        enum vtype type)
{
        struct dtfs_file *dff;
        struct dtfs_dirent *dfd;
        struct dtfs_mount *dtm;
        struct puffs_node *newpn;
        uid_t uid;
        int rv;

        assert(dir->pn_va.va_type == VDIR);
        assert(dir->pn_mnt != NULL);

        uid = 0;
        rv = puffs_cred_getuid(pcn->pcn_cred, &uid);
        assert(rv == 0);

        if (type == VDIR) {
                dff = dtfs_newdir();
                dff->df_dotdot = dir;
        } else
                dff = dtfs_newfile();

        dtm = puffs_pn_getmntspecific(dir);
        newpn = puffs_pn_new(dir->pn_mnt, dff);
        if (newpn == NULL)
                errx(1, "getnewpnode");
        dtfs_baseattrs(&newpn->pn_va, type, dtm->dtm_nextfileid++);

        dfd = emalloc(sizeof(struct dtfs_dirent));
        dfd->dfd_node = newpn;
        dfd->dfd_name = estrndup(pcn->pcn_name, pcn->pcn_namelen);
        dfd->dfd_namelen = strlen(dfd->dfd_name);
        dfd->dfd_parent = dir;
        dtfs_adddent(dir, dfd);

        newpn->pn_va.va_uid = uid;
        newpn->pn_va.va_gid = dir->pn_va.va_gid;

        return newpn;
}

struct dtfs_file *
dtfs_newdir()
{
        struct dtfs_file *dff;

        dff = emalloc(sizeof(struct dtfs_file));
        memset(dff, 0, sizeof(struct dtfs_file));
        LIST_INIT(&dff->df_dirents);

        return dff;
}

struct dtfs_file *
dtfs_newfile()
{
        struct dtfs_file *dff;

        dff = emalloc(sizeof(struct dtfs_file));
        memset(dff, 0, sizeof(struct dtfs_file));

        return dff;
}

struct dtfs_dirent *
dtfs_dirgetnth(struct dtfs_file *searchdir, int n)
{
        struct dtfs_dirent *dirent;
        int i;

        i = 0;
        LIST_FOREACH(dirent, &searchdir->df_dirents, dfd_entries) {
                if (i == n)
                        return dirent;
                i++;
        }

        return NULL;
}

struct dtfs_dirent *
dtfs_dirgetbyname(struct dtfs_file *searchdir, const char *fname, size_t fnlen)
{
        struct dtfs_dirent *dirent;

        LIST_FOREACH(dirent, &searchdir->df_dirents, dfd_entries)
                if (dirent->dfd_namelen == fnlen
                    && strncmp(dirent->dfd_name, fname, fnlen) == 0)
                        return dirent;

        return NULL;
}

/*
 * common nuke, kill dirent from parent node
 */
void
dtfs_nukenode(struct puffs_node *nukeme, struct puffs_node *pn_parent,
        const char *fname, size_t fnlen)
{
        struct dtfs_dirent *dfd;
        struct dtfs_mount *dtm;

        assert(pn_parent->pn_va.va_type == VDIR);

        dfd = dtfs_dirgetbyname(DTFS_PTOF(pn_parent), fname, fnlen);
        assert(dfd);

        dtm = puffs_pn_getmntspecific(nukeme);
        dtm->dtm_nfiles--;
        assert(dtm->dtm_nfiles >= 1);

        dtfs_removedent(pn_parent, dfd);
        free(dfd);
}

/* free lingering information */
void
dtfs_freenode(struct puffs_node *pn)
{
        struct dtfs_file *df = DTFS_PTOF(pn);
        struct dtfs_mount *dtm;
        int i;

        assert(pn->pn_va.va_nlink == 0);
        dtm = puffs_pn_getmntspecific(pn);

        switch (pn->pn_va.va_type) {
        case VREG:
                assert(dtm->dtm_fsizes >= pn->pn_va.va_size);
                dtm->dtm_fsizes -= pn->pn_va.va_size;
                for (i = 0; i < BLOCKNUM(df->df_datalen, DTFS_BLOCKSHIFT); i++)
                        free(df->df_blocks[i]);
                if (df->df_datalen > i << DTFS_BLOCKSHIFT)
                        free(df->df_blocks[i]);
                break;
        case VLNK:
                free(df->df_linktarget);
                break;
        case VCHR:
        case VBLK:
        case VDIR:
        case VSOCK:
        case VFIFO:
                break;
        default:
                assert(0);
                break;
        }

        free(df);
        puffs_pn_put(pn);
}

void
dtfs_setsize(struct puffs_node *pn, off_t newsize)
{
        struct dtfs_file *df = DTFS_PTOF(pn);
        struct dtfs_mount *dtm;
        size_t newblocks;
        int needalloc, shrinks;
        int i;

        needalloc = newsize > ROUNDUP(df->df_datalen, DTFS_BLOCKSIZE);
        shrinks = newsize < pn->pn_va.va_size;

        if (needalloc || shrinks) {
                newblocks = BLOCKNUM(newsize, DTFS_BLOCKSHIFT) + 1;

                if (shrinks)
                        for (i = newblocks; i < df->df_numblocks; i++)
                                free(df->df_blocks[i]);

                df->df_blocks = erealloc(df->df_blocks,
                    newblocks * sizeof(uint8_t *));
                /*
                 * if extended, set storage to zero
                 * to match correct behaviour
                 */ 
                if (!shrinks) {
                        for (i = df->df_numblocks; i < newblocks; i++) {
                                df->df_blocks[i] = emalloc(DTFS_BLOCKSIZE);
                                memset(df->df_blocks[i], 0, DTFS_BLOCKSIZE);
                        }
                }

                df->df_datalen = newsize;
                df->df_numblocks = newblocks;
        }

        dtm = puffs_pn_getmntspecific(pn);
        if (!shrinks) {
                dtm->dtm_fsizes += newsize - pn->pn_va.va_size;
        } else {
                dtm->dtm_fsizes -= pn->pn_va.va_size - newsize;
        }

        pn->pn_va.va_size = newsize;
        pn->pn_va.va_bytes = BLOCKNUM(newsize,DTFS_BLOCKSHIFT)>>DTFS_BLOCKSHIFT;
}

/* add & bump link count */
void
dtfs_adddent(struct puffs_node *pn_dir, struct dtfs_dirent *dent)
{
        struct dtfs_file *dir = DTFS_PTOF(pn_dir);
        struct puffs_node *pn_file = dent->dfd_node;
        struct dtfs_file *file = DTFS_PTOF(pn_file);
        struct dtfs_mount *dtm;

        assert(pn_dir->pn_va.va_type == VDIR);
        LIST_INSERT_HEAD(&dir->df_dirents, dent, dfd_entries);
        pn_file->pn_va.va_nlink++;

        dtm = puffs_pn_getmntspecific(pn_file);
        dtm->dtm_nfiles++;

        dent->dfd_parent = pn_dir;
        if (dent->dfd_node->pn_va.va_type == VDIR) {
                file->df_dotdot = pn_dir;
                pn_dir->pn_va.va_nlink++;
        }

        dtfs_updatetimes(pn_dir, 0, 1, 1);
}

/* remove & lower link count */
void
dtfs_removedent(struct puffs_node *pn_dir, struct dtfs_dirent *dent)
{
        struct puffs_node *pn_file = dent->dfd_node;

        assert(pn_dir->pn_va.va_type == VDIR);
        LIST_REMOVE(dent, dfd_entries);
        if (pn_file->pn_va.va_type == VDIR) {
                struct dtfs_file *df = DTFS_PTOF(pn_file);

                pn_dir->pn_va.va_nlink--;
                df->df_dotdot = NULL;
        }
        pn_file->pn_va.va_nlink--;
        assert(pn_dir->pn_va.va_nlink >= 2);

        dtfs_updatetimes(pn_dir, 0, 1, 1);
}

void
dtfs_updatetimes(struct puffs_node *pn, int doatime, int doctime, int domtime)
{
        struct timeval tv;
        struct timespec ts;

        gettimeofday(&tv, NULL);
        TIMEVAL_TO_TIMESPEC(&tv, &ts);

        if (doatime)
                pn->pn_va.va_atime = ts;
        if (doctime)
                pn->pn_va.va_ctime = ts;
        if (domtime)
                pn->pn_va.va_mtime = ts;
}

bool
dtfs_isunder(struct puffs_node *pn, struct puffs_node *pn_parent)
{
        struct dtfs_file *df;

        while (pn) {
                if (pn == pn_parent)
                        return true;
                df = DTFS_CTOF(pn);
                pn = df->df_dotdot;
        }

        return false;
}