/*
 * Copyright (c) 2020 Red Hat, Inc.
 *
 * This program 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 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/kernel/sysfs.c#4 $
 */

#include "sysfs.h"

#include <linux/module.h>
#include <linux/version.h>

#include "dedupeIndex.h"
#include "dmvdo.h"
#include "logger.h"

extern int defaultMaxRequestsActive;

typedef struct vdoAttribute {
  struct attribute  attr;
  ssize_t (*show)(struct kvdoDevice *d, struct attribute *attr, char *buf);
  ssize_t (*store)(struct kvdoDevice *d, const char *value, size_t count);
  // Location of value, if .show == showInt or showUInt or showBool.
  void     *valuePtr;
} VDOAttribute;

static char *statusStrings[] = {
  "UNINITIALIZED",
  "READY",
  "SHUTTING DOWN",
};

/**********************************************************************/
static ssize_t vdoStatusShow(struct kvdoDevice *device,
                             struct attribute  *attr,
                             char              *buf)
{
  return sprintf(buf, "%s\n", statusStrings[device->status]);
}

/**********************************************************************/
static ssize_t vdoLogLevelShow(struct kvdoDevice *device,
                               struct attribute  *attr,
                               char              *buf)
{
  return sprintf(buf, "%s\n", priorityToString(getLogLevel()));
}

/**********************************************************************/
static ssize_t vdoLogLevelStore(struct kvdoDevice *device,
                                const char *buf, size_t n)
{
  static char internalBuf[11];

  if (n > 10) {
    return -EINVAL;
  }

  memset(internalBuf, '\000', sizeof(internalBuf));
  memcpy(internalBuf, buf, n);
  if (internalBuf[n - 1] == '\n') {
    internalBuf[n - 1] = '\000';
  }
  setLogLevel(stringToPriority(internalBuf));
  return n;
}

/**********************************************************************/
static ssize_t scanInt(const char *buf,
                       size_t      n,
                       int        *valuePtr,
                       int         minimum,
                       int         maximum)
{
  if (n > 12) {
    return -EINVAL;
  }
  unsigned int value;
  if (sscanf(buf, "%d", &value) != 1) {
    return -EINVAL;
  }
  if (value < minimum) {
    value = minimum;
  } else if (value > maximum) {
    value = maximum;
  }
  *valuePtr = value;
  return n;
}

/**********************************************************************/
static ssize_t showInt(struct kvdoDevice *device,
                       struct attribute  *attr,
                       char              *buf)
{
  VDOAttribute *vdoAttr = container_of(attr, VDOAttribute, attr);

  return sprintf(buf, "%d\n", *(int *)vdoAttr->valuePtr);
}

/**********************************************************************/
static ssize_t scanUInt(const char   *buf,
                        size_t        n,
                        unsigned int *valuePtr,
                        unsigned int  minimum,
                        unsigned int  maximum)
{
  if (n > 12) {
    return -EINVAL;
  }
  unsigned int value;
  if (sscanf(buf, "%u", &value) != 1) {
    return -EINVAL;
  }
  if (value < minimum) {
    value = minimum;
  } else if (value > maximum) {
    value = maximum;
  }
  *valuePtr = value;
  return n;
}

/**********************************************************************/
static ssize_t showUInt(struct kvdoDevice *device,
                        struct attribute  *attr,
                        char              *buf)
{
  VDOAttribute *vdoAttr = container_of(attr, VDOAttribute, attr);

  return sprintf(buf, "%u\n", *(unsigned int *)vdoAttr->valuePtr);
}

/**********************************************************************/
static ssize_t scanBool(const char *buf, size_t n, bool *valuePtr)
{
  unsigned int intValue = 0;
  n = scanUInt(buf, n, &intValue, 0, 1);
  if (n > 0) {
    *valuePtr = (intValue != 0);
  }
  return n;
}

/**********************************************************************/
static ssize_t showBool(struct kvdoDevice *device,
                        struct attribute  *attr,
                        char              *buf)
{
  VDOAttribute *vdoAttr = container_of(attr, VDOAttribute, attr);

  return sprintf(buf, "%u\n", *(bool *)vdoAttr->valuePtr ? 1 : 0);
}

/**********************************************************************/
static ssize_t vdoTraceRecordingStore(struct kvdoDevice *device,
                                      const char        *buf,
                                      size_t             n)
{
  return scanBool(buf, n, &traceRecording);
}

/**********************************************************************/
static ssize_t vdoMaxReqActiveStore(struct kvdoDevice *device,
                                    const char        *buf,
                                    size_t             n)
{
  /*
   * The base code has some hardcoded assumptions about the maximum
   * number of requests that can be in progress. Maybe someday we'll
   * do calculations with the actual number; for now, just make sure
   * the assumption holds.
   */
  return scanInt(buf, n, &defaultMaxRequestsActive, 1, MAXIMUM_USER_VIOS);
}

/**********************************************************************/
static ssize_t vdoAlbireoTimeoutIntervalStore(struct kvdoDevice *device,
                                              const char        *buf,
                                              size_t             n)
{
  unsigned int value;
  ssize_t result = scanUInt(buf, n, &value, 0, UINT_MAX);
  if (result > 0) {
    setAlbireoTimeoutInterval(value);
  }
  return result;
}

/**********************************************************************/
static ssize_t vdoMinAlbireoTimerIntervalStore(struct kvdoDevice *device,
                                               const char        *buf,
                                               size_t             n)
{
  unsigned int value;
  ssize_t result = scanUInt(buf, n, &value, 0, UINT_MAX);
  if (result > 0) {
    setMinAlbireoTimerInterval(value);
  }
  return result;
}

/**********************************************************************/
static ssize_t vdoVersionShow(struct kvdoDevice *device,
                              struct attribute  *attr,
                              char              *buf)
{
  return sprintf(buf, "%s\n", CURRENT_VERSION);
}

/**********************************************************************/
static ssize_t vdoAttrShow(struct kobject   *kobj,
                           struct attribute *attr,
                           char             *buf)
{
  VDOAttribute *vdoAttr = container_of(attr, VDOAttribute, attr);
  if (vdoAttr->show == NULL) {
    return -EINVAL;
  }

  struct kvdoDevice *device = container_of(kobj, struct kvdoDevice, kobj);
  return (*vdoAttr->show)(device, attr, buf);
}

/**********************************************************************/
static ssize_t vdoAttrStore(struct kobject   *kobj,
                            struct attribute *attr,
                            const char       *buf,
                            size_t            length)
{
  VDOAttribute *vdoAttr = container_of(attr, VDOAttribute, attr);
  if (vdoAttr->store == NULL) {
    return -EINVAL;
  }

  struct kvdoDevice *device = container_of(kobj, struct kvdoDevice, kobj);
  return (*vdoAttr->store)(device, buf, length);
}

static VDOAttribute vdoStatusAttr = {
  .attr  = { .name = "status", .mode = 0444, },
  .show  = vdoStatusShow,
};

static VDOAttribute vdoLogLevelAttr = {
  .attr  = {.name = "log_level", .mode = 0644, },
  .show  = vdoLogLevelShow,
  .store = vdoLogLevelStore,
};

static VDOAttribute vdoMaxReqActiveAttr = {
  .attr     = {.name = "max_requests_active", .mode = 0644, },
  .show     = showInt,
  .store    = vdoMaxReqActiveStore,
  .valuePtr = &defaultMaxRequestsActive,
};

static VDOAttribute vdoAlbireoTimeoutInterval = {
  .attr     = {.name = "deduplication_timeout_interval", .mode = 0644, },
  .show     = showUInt,
  .store    = vdoAlbireoTimeoutIntervalStore,
  .valuePtr = &albireoTimeoutInterval,
};

static VDOAttribute vdoMinAlbireoTimerInterval = {
  .attr     = {.name = "min_deduplication_timer_interval", .mode = 0644, },
  .show     = showUInt,
  .store    = vdoMinAlbireoTimerIntervalStore,
  .valuePtr = &minAlbireoTimerInterval,
};

static VDOAttribute vdoTraceRecording = {
  .attr     = {.name = "trace_recording", .mode = 0644, },
  .show     = showBool,
  .store    = vdoTraceRecordingStore,
  .valuePtr = &traceRecording,
};

static VDOAttribute vdoVersionAttr = {
  .attr  = { .name = "version", .mode = 0444, },
  .show  = vdoVersionShow,
};

static struct attribute *defaultAttrs[] = {
  &vdoStatusAttr.attr,
  &vdoLogLevelAttr.attr,
  &vdoMaxReqActiveAttr.attr,
  &vdoAlbireoTimeoutInterval.attr,
  &vdoMinAlbireoTimerInterval.attr,
  &vdoTraceRecording.attr,
  &vdoVersionAttr.attr,
  NULL
};

static struct sysfs_ops vdoSysfsOps = {
  .show  = vdoAttrShow,
  .store = vdoAttrStore,
};

/**********************************************************************/
static void vdoRelease(struct kobject *kobj)
{
  return;
}

struct kobj_type vdo_ktype = {
  .release   = vdoRelease,
  .sysfs_ops = &vdoSysfsOps,
  .default_attrs = defaultAttrs,
};

/**********************************************************************/
int vdoInitSysfs(struct kobject *deviceObject)
{
  kobject_init(deviceObject, &vdo_ktype);
  int result = kobject_add(deviceObject, NULL, THIS_MODULE->name);
  if (result < 0) {
    logError("kobject_add failed with status %d", -result);
    kobject_put(deviceObject);
  }
  logDebug("added sysfs objects");
  return result;
};

/**********************************************************************/
void vdoPutSysfs(struct kobject *deviceObject)
{
  kobject_put(deviceObject);
}
