root/usr/src/uts/common/fs/smbsrv/smb_thread.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
 * Copyright 2023 RackTop Systems, Inc.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/tzfile.h>
#include <sys/atomic.h>
#include <sys/disp.h>
#include <sys/kidmap.h>
#include <sys/time.h>
#include <sys/spl.h>
#include <sys/random.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <smbsrv/smbinfo.h>
#include <smbsrv/smb_xdr.h>
#include <smbsrv/smb_vops.h>
#include <smbsrv/smb_idmap.h>

#include <sys/sid.h>
#include <sys/priv_names.h>

#ifdef  _FAKE_KERNEL
#define THR_TO_DID(t)   ((kt_did_t)(uintptr_t)t)
#else
#define THR_TO_DID(t)   (t->t_did)
#endif

static boolean_t smb_thread_continue_timedwait_locked(smb_thread_t *, int);

/*
 * smb_thread_entry_point
 *
 * Common entry point for all the threads created through smb_thread_start.
 * The state of the thread is set to "running" at the beginning and moved to
 * "exiting" just before calling thread_exit(). The condition variable is
 *  also signaled.
 */
static void
smb_thread_entry_point(
    smb_thread_t        *thread)
{
        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);
        mutex_enter(&thread->sth_mtx);
        ASSERT(thread->sth_state == SMB_THREAD_STATE_STARTING);

        if (!thread->sth_kill) {
                thread->sth_state = SMB_THREAD_STATE_RUNNING;
                cv_signal(&thread->sth_cv);
                mutex_exit(&thread->sth_mtx);

                /* Run the real thread entry point. */
                thread->sth_ep(thread, thread->sth_ep_arg);

                mutex_enter(&thread->sth_mtx);
        }
        /*
         * It's tempting to clear sth_did here too, but don't.
         * That's needed in thread_join().
         */
        thread->sth_th = NULL;
        thread->sth_state = SMB_THREAD_STATE_EXITING;
        cv_broadcast(&thread->sth_cv);
        mutex_exit(&thread->sth_mtx);
#ifdef  _KERNEL
        if (curthread->t_lwp != NULL) {
                mutex_enter(&curproc->p_lock);
                lwp_exit(); /* noreturn */
        }
#endif  /* _KERNEL */
        thread_exit();
}

/*
 * smb_thread_init
 */
void
smb_thread_init(
    smb_thread_t        *thread,
    char                *name,
    smb_thread_ep_t     ep,
    void                *ep_arg,
    pri_t               pri,
    smb_server_t        *sv)
{
        ASSERT(thread->sth_magic != SMB_THREAD_MAGIC);

        bzero(thread, sizeof (*thread));

        (void) strlcpy(thread->sth_name, name, sizeof (thread->sth_name));
        thread->sth_server = sv;
        thread->sth_ep = ep;
        thread->sth_ep_arg = ep_arg;
        thread->sth_state = SMB_THREAD_STATE_EXITED;
        thread->sth_pri = pri;
        mutex_init(&thread->sth_mtx, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&thread->sth_cv, NULL, CV_DEFAULT, NULL);
        thread->sth_magic = SMB_THREAD_MAGIC;
}

/*
 * smb_thread_destroy
 */
void
smb_thread_destroy(
    smb_thread_t        *thread)
{
        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);
        ASSERT(thread->sth_state == SMB_THREAD_STATE_EXITED);
        thread->sth_magic = 0;
        mutex_destroy(&thread->sth_mtx);
        cv_destroy(&thread->sth_cv);
}

/*
 * smb_thread_start
 *
 * This function starts a thread with the parameters provided. It waits until
 * the state of the thread has been moved to running.
 */
/*ARGSUSED*/
int
smb_thread_start(
    smb_thread_t        *sth)
{
        kthread_t       *t;
        struct proc     *procp;
        smb_server_t    *sv = sth->sth_server;
        int             rc;

        ASSERT(sth->sth_magic == SMB_THREAD_MAGIC);

        procp = (sv->sv_proc_p != NULL) ?
            sv->sv_proc_p : curzone->zone_zsched;

        mutex_enter(&sth->sth_mtx);
        if (sth->sth_state != SMB_THREAD_STATE_EXITED) {
                mutex_exit(&sth->sth_mtx);
                return (-1);
        }
        sth->sth_state = SMB_THREAD_STATE_STARTING;
        mutex_exit(&sth->sth_mtx);

#ifdef  _KERNEL
        if (sth->sth_pri < MINCLSYSPRI) {
                t = lwp_kernel_create(procp, smb_thread_entry_point, sth,
                    TS_RUN, sth->sth_pri);
        } else
#endif  /* _KERNEL */
        {
                t = thread_create(NULL, 0, smb_thread_entry_point, sth,
                    0, procp, TS_RUN, sth->sth_pri);
        }
        ASSERT(t != NULL);

        mutex_enter(&sth->sth_mtx);
        sth->sth_th = t;
        sth->sth_did = THR_TO_DID(t);

        /* rendez-vouz with new thread */
        while (sth->sth_state == SMB_THREAD_STATE_STARTING)
                cv_wait(&sth->sth_cv, &sth->sth_mtx);
        if (sth->sth_state == SMB_THREAD_STATE_RUNNING)
                rc = 0;
        else
                rc = -1;
        mutex_exit(&sth->sth_mtx);

        return (rc);
}

/*
 * smb_thread_stop
 *
 * This function signals a thread to kill itself and waits until the "exiting"
 * state has been reached.
 */
void
smb_thread_stop(smb_thread_t *thread)
{
        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);

        mutex_enter(&thread->sth_mtx);
        switch (thread->sth_state) {
        case SMB_THREAD_STATE_RUNNING:
        case SMB_THREAD_STATE_STARTING:
                if (!thread->sth_kill) {
                        thread->sth_kill = B_TRUE;
                        cv_broadcast(&thread->sth_cv);
                        while (thread->sth_state != SMB_THREAD_STATE_EXITING)
                                cv_wait(&thread->sth_cv, &thread->sth_mtx);
                        mutex_exit(&thread->sth_mtx);
                        thread_join(thread->sth_did);
                        mutex_enter(&thread->sth_mtx);
                        thread->sth_state = SMB_THREAD_STATE_EXITED;
                        thread->sth_did = 0;
                        thread->sth_kill = B_FALSE;
                        cv_broadcast(&thread->sth_cv);
                        break;
                }
                /* FALLTHROUGH */

        case SMB_THREAD_STATE_EXITING:
                if (thread->sth_kill) {
                        while (thread->sth_state != SMB_THREAD_STATE_EXITED)
                                cv_wait(&thread->sth_cv, &thread->sth_mtx);
                } else {
                        thread->sth_state = SMB_THREAD_STATE_EXITED;
                        thread->sth_did = 0;
                }
                break;

        case SMB_THREAD_STATE_EXITED:
                break;

        default:
                ASSERT(0);
                break;
        }
        mutex_exit(&thread->sth_mtx);
}

/*
 * smb_thread_signal
 *
 * This function signals a thread.
 */
void
smb_thread_signal(smb_thread_t *thread)
{
        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);

        mutex_enter(&thread->sth_mtx);
        switch (thread->sth_state) {
        case SMB_THREAD_STATE_RUNNING:
                cv_signal(&thread->sth_cv);
                break;

        default:
                break;
        }
        mutex_exit(&thread->sth_mtx);
}

boolean_t
smb_thread_continue(smb_thread_t *thread)
{
        boolean_t result;

        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);

        mutex_enter(&thread->sth_mtx);
        result = smb_thread_continue_timedwait_locked(thread, 0);
        mutex_exit(&thread->sth_mtx);

        return (result);
}

boolean_t
smb_thread_continue_nowait(smb_thread_t *thread)
{
        boolean_t result;

        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);

        mutex_enter(&thread->sth_mtx);
        /*
         * Setting ticks=-1 requests a non-blocking check.  We will
         * still block if the thread is in "suspend" state.
         */
        result = smb_thread_continue_timedwait_locked(thread, -1);
        mutex_exit(&thread->sth_mtx);

        return (result);
}

boolean_t
smb_thread_continue_timedwait(smb_thread_t *thread, int seconds)
{
        boolean_t result;

        ASSERT(thread->sth_magic == SMB_THREAD_MAGIC);

        mutex_enter(&thread->sth_mtx);
        result = smb_thread_continue_timedwait_locked(thread,
            SEC_TO_TICK(seconds));
        mutex_exit(&thread->sth_mtx);

        return (result);
}

/*
 * smb_thread_continue_timedwait_locked
 *
 * Internal only.  Ticks==-1 means don't block, Ticks == 0 means wait
 * indefinitely
 */
static boolean_t
smb_thread_continue_timedwait_locked(smb_thread_t *thread, int ticks)
{
        boolean_t       result;

        /* -1 means don't block */
        if (ticks != -1 && !thread->sth_kill) {
                if (ticks == 0) {
                        cv_wait(&thread->sth_cv, &thread->sth_mtx);
                } else {
                        (void) cv_reltimedwait(&thread->sth_cv,
                            &thread->sth_mtx, (clock_t)ticks, TR_CLOCK_TICK);
                }
        }
        result = (thread->sth_kill == 0);

        return (result);
}