* Fixes for compiling on Haiku, and add Haiku VM for compile-testing
* Update NetBSD VM to version 9.1 * Misc fixes (e.g. categorize some devices) -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEEJ7iIR+7gJQEY8+q5LtnXdP5wLbUFAl+zld8RHHRodXRoQHJl ZGhhdC5jb20ACgkQLtnXdP5wLbWb8Q/+IRvGUGjGcPfbTvwoOqJVy4Hm7huW5i1s wHe/6nitNtpvaAqcxbQHBIvWX9xTzppWcFiEkIs8rPwLOUFKh5xJ+NbEdf4acQaJ m4G2mEY5bYt/o5e6p7ZK1RgS2EjD1eQ6BwMWQKeUHET7MTv0UabKtvWmBWpMqFxA vl/3SbVWsSwGB9gOA5oksYhKY5ZRcVaDxsGk89f7iwgaStcxWNxVFEXddbBmqhfW Q4ZPt0K7yod7NDBOaGEoc2hOjIfr0TvovHojDuAxt+2tKdYi1vwtnwKbFqTWp7Ca 7ttzoQUSsteiOjAhHRpa2PEbfrNs+loIm9fem5fQ9i7POlbS/Ozv2RnPCZm1X8pW n7Jvsh25V066AFnHat7PnjcBVBRFfmR3xtA61PqvAMGEKW8tortbZbpqXO18Pv5p 6P/GG9G3QE0v2rEsU5BNFWp/aD7fiWy/VPu3dGFUkI9/S3biatocldHn/9eyXz94 k75Xzhe5x6n5Jf8QYFQ/6BO0qSoidNbAVg1W8+QyRXIJJhWRnvW9eYa7tSx5ezJg 5+oCo4oh6Qd9nvrl5pIwvX6QMDf2kPxzp7PsHeemqt7+QNmXErAVsIi1HUVsLWRP Qb/BbKyKNeWJwvWWLAm/2kXVmNQfjLVNCwg04xa8tkQemhIDekVrCpMoX3cNHjWf EWa1vEtbq9k= =A3/B -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/huth-gitlab/tags/pull-request-2020-11-17' into staging * Fixes for compiling on Haiku, and add Haiku VM for compile-testing * Update NetBSD VM to version 9.1 * Misc fixes (e.g. categorize some devices) # gpg: Signature made Tue 17 Nov 2020 09:20:31 GMT # gpg: using RSA key 27B88847EEE0250118F3EAB92ED9D774FE702DB5 # gpg: issuer "thuth@redhat.com" # gpg: Good signature from "Thomas Huth <th.huth@gmx.de>" [full] # gpg: aka "Thomas Huth <thuth@redhat.com>" [full] # gpg: aka "Thomas Huth <huth@tuxfamily.org>" [full] # gpg: aka "Thomas Huth <th.huth@posteo.de>" [unknown] # Primary key fingerprint: 27B8 8847 EEE0 2501 18F3 EAB9 2ED9 D774 FE70 2DB5 * remotes/huth-gitlab/tags/pull-request-2020-11-17: max111x: put it into the 'misc' category nand: put it into the 'storage' category ads7846: put it into the 'input' category ssd0323: put it into the 'display' category gitlab-ci: Use $CI_REGISTRY instead of hard-coding registry.gitlab.com target/microblaze: Fix possible array out of bounds in mmu_write() tests/vm: update NetBSD to 9.1 tests/vm: Add Haiku test based on their vagrant images configure: Add a proper check for sys/ioccom.h and use it in tpm_ioctl.h configure: Do not build pc-bios/optionrom on Haiku configure: Fix the _BSD_SOURCE define for the Haiku build qemu/bswap: Remove unused qemu_bswap_len() Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
6b728efcb0
@ -8,7 +8,7 @@
|
||||
- export COMMON_TAG="$CI_REGISTRY/qemu-project/qemu/$NAME:latest"
|
||||
- apk add python3
|
||||
- docker info
|
||||
- docker login registry.gitlab.com -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
|
||||
- docker login $CI_REGISTRY -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
|
||||
script:
|
||||
- echo "TAG:$TAG"
|
||||
- echo "COMMON_TAG:$COMMON_TAG"
|
||||
|
@ -12,6 +12,10 @@
|
||||
#include <sys/uio.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifdef HAVE_SYS_IOCCOM_H
|
||||
#include <sys/ioccom.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Every response from a command involving a TPM command execution must hold
|
||||
* the ptm_res as the first element.
|
||||
|
4
configure
vendored
4
configure
vendored
@ -789,7 +789,7 @@ SunOS)
|
||||
;;
|
||||
Haiku)
|
||||
haiku="yes"
|
||||
QEMU_CFLAGS="-DB_USE_POSITIVE_POSIX_ERRORS -DBSD_SOURCE $QEMU_CFLAGS"
|
||||
QEMU_CFLAGS="-DB_USE_POSITIVE_POSIX_ERRORS -D_BSD_SOURCE $QEMU_CFLAGS"
|
||||
;;
|
||||
Linux)
|
||||
audio_drv_list="try-pa oss"
|
||||
@ -5835,7 +5835,7 @@ fi
|
||||
roms=
|
||||
if { test "$cpu" = "i386" || test "$cpu" = "x86_64"; } && \
|
||||
test "$targetos" != "Darwin" && test "$targetos" != "SunOS" && \
|
||||
test "$softmmu" = yes ; then
|
||||
test "$targetos" != "Haiku" && test "$softmmu" = yes ; then
|
||||
# Different host OS linkers have different ideas about the name of the ELF
|
||||
# emulation. Linux and OpenBSD/amd64 use 'elf_i386'; FreeBSD uses the _fbsd
|
||||
# variant; OpenBSD/i386 uses the _obsd variant; and Windows uses i386pe.
|
||||
|
@ -449,6 +449,7 @@ static void nand_class_init(ObjectClass *klass, void *data)
|
||||
dc->reset = nand_reset;
|
||||
dc->vmsd = &vmstate_nand;
|
||||
device_class_set_props(dc, nand_properties);
|
||||
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo nand_info = {
|
||||
|
@ -163,10 +163,12 @@ static void ads7846_realize(SSISlave *d, Error **errp)
|
||||
|
||||
static void ads7846_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
|
||||
|
||||
k->realize = ads7846_realize;
|
||||
k->transfer = ads7846_transfer;
|
||||
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo ads7846_info = {
|
||||
|
@ -370,6 +370,7 @@ static void ssd0323_class_init(ObjectClass *klass, void *data)
|
||||
k->transfer = ssd0323_transfer;
|
||||
k->cs_polarity = SSI_CS_HIGH;
|
||||
dc->vmsd = &vmstate_ssd0323;
|
||||
set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo ssd0323_info = {
|
||||
|
@ -185,6 +185,7 @@ static void max111x_class_init(ObjectClass *klass, void *data)
|
||||
k->transfer = max111x_transfer;
|
||||
dc->reset = max111x_reset;
|
||||
dc->vmsd = &vmstate_max111x;
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo max111x_info = {
|
||||
|
@ -169,12 +169,6 @@ CPU_CONVERT(le, 16, uint16_t)
|
||||
CPU_CONVERT(le, 32, uint32_t)
|
||||
CPU_CONVERT(le, 64, uint64_t)
|
||||
|
||||
/* len must be one of 1, 2, 4 */
|
||||
static inline uint32_t qemu_bswap_len(uint32_t value, int len)
|
||||
{
|
||||
return bswap32(value) >> (32 - 8 * len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as cpu_to_le{16,32}, except that gcc will figure the result is
|
||||
* a compile-time constant if you pass in a constant. So this can be
|
||||
|
@ -808,6 +808,8 @@ config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0]
|
||||
config_host_data.set('QEMU_VERSION_MINOR', meson.project_version().split('.')[1])
|
||||
config_host_data.set('QEMU_VERSION_MICRO', meson.project_version().split('.')[2])
|
||||
|
||||
config_host_data.set('HAVE_SYS_IOCCOM_H', cc.has_header('sys/ioccom.h'))
|
||||
|
||||
ignored = ['CONFIG_QEMU_INTERP_PREFIX'] # actually per-target
|
||||
arrays = ['CONFIG_AUDIO_DRIVERS', 'CONFIG_BDRV_RW_WHITELIST', 'CONFIG_BDRV_RO_WHITELIST']
|
||||
strings = ['HOST_DSOSUF', 'CONFIG_IASL']
|
||||
|
@ -19,7 +19,7 @@
|
||||
#ifndef _WIN32
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
#if defined(__sun__) || defined(__HAIKU__)
|
||||
#ifdef HAVE_SYS_IOCCOM_H
|
||||
#include <sys/ioccom.h>
|
||||
#endif
|
||||
|
||||
|
@ -234,7 +234,8 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
|
||||
unsigned int i;
|
||||
|
||||
qemu_log_mask(CPU_LOG_MMU,
|
||||
"%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]);
|
||||
"%s rn=%d=%x old=%x\n", __func__, rn, v,
|
||||
rn < 3 ? env->mmu.regs[rn] : env->mmu.regs[MMU_R_TLBX]);
|
||||
|
||||
if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
|
||||
|
27
tests/keys/vagrant
Normal file
27
tests/keys/vagrant
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
|
||||
w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
|
||||
kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
|
||||
hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
|
||||
Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
|
||||
yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
|
||||
ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
|
||||
Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
|
||||
TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
|
||||
iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
|
||||
sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
|
||||
4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
|
||||
cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
|
||||
EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
|
||||
CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
|
||||
3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
|
||||
YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
|
||||
3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
|
||||
dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
|
||||
6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
|
||||
P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
|
||||
llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
|
||||
kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
|
||||
+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
|
||||
NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
|
||||
-----END RSA PRIVATE KEY-----
|
1
tests/keys/vagrant.pub
Normal file
1
tests/keys/vagrant.pub
Normal file
@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== well-known vagrant key for qemu-test, do not use on any machine exposed to an external network
|
@ -4,7 +4,7 @@
|
||||
|
||||
EFI_AARCH64 = $(wildcard $(BUILD_DIR)/pc-bios/edk2-aarch64-code.fd)
|
||||
|
||||
IMAGES := freebsd netbsd openbsd centos fedora
|
||||
IMAGES := freebsd netbsd openbsd centos fedora haiku.x86_64
|
||||
ifneq ($(GENISOIMAGE),)
|
||||
IMAGES += ubuntu.i386 centos
|
||||
ifneq ($(EFI_AARCH64),)
|
||||
@ -41,6 +41,7 @@ endif
|
||||
else
|
||||
@echo " (install genisoimage to build centos/ubuntu images)"
|
||||
endif
|
||||
@echo " vm-build-haiku.x86_64 - Build QEMU in Haiku VM"
|
||||
@echo ""
|
||||
@echo " vm-build-all - Build QEMU in all VMs"
|
||||
@echo " vm-clean-all - Clean up VM images"
|
||||
|
@ -44,6 +44,7 @@ DEFAULT_CONFIG = {
|
||||
'machine' : 'pc',
|
||||
'guest_user' : "qemu",
|
||||
'guest_pass' : "qemupass",
|
||||
'root_user' : "root",
|
||||
'root_pass' : "qemupass",
|
||||
'ssh_key_file' : SSH_KEY_FILE,
|
||||
'ssh_pub_key_file': SSH_PUB_KEY_FILE,
|
||||
@ -245,13 +246,13 @@ class BaseVM(object):
|
||||
return self._ssh_do(self._config["guest_user"], cmd, False)
|
||||
|
||||
def ssh_root(self, *cmd):
|
||||
return self._ssh_do("root", cmd, False)
|
||||
return self._ssh_do(self._config["root_user"], cmd, False)
|
||||
|
||||
def ssh_check(self, *cmd):
|
||||
self._ssh_do(self._config["guest_user"], cmd, True)
|
||||
|
||||
def ssh_root_check(self, *cmd):
|
||||
self._ssh_do("root", cmd, True)
|
||||
self._ssh_do(self._config["root_user"], cmd, True)
|
||||
|
||||
def build_image(self, img):
|
||||
raise NotImplementedError
|
||||
|
119
tests/vm/haiku.x86_64
Executable file
119
tests/vm/haiku.x86_64
Executable file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Haiku VM image
|
||||
#
|
||||
# Copyright 2020 Haiku, Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Alexander von Gluck IV <kallisti5@unixzen.com>
|
||||
#
|
||||
# This code is licensed under the GPL version 2 or later. See
|
||||
# the COPYING file in the top-level directory.
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import subprocess
|
||||
import basevm
|
||||
|
||||
VAGRANT_KEY_FILE = os.path.join(os.path.dirname(__file__),
|
||||
"..", "keys", "vagrant")
|
||||
|
||||
VAGRANT_PUB_KEY_FILE = os.path.join(os.path.dirname(__file__),
|
||||
"..", "keys", "vagrant.pub")
|
||||
|
||||
HAIKU_CONFIG = {
|
||||
'cpu' : "max",
|
||||
'machine' : 'pc',
|
||||
'guest_user' : "vagrant",
|
||||
'guest_pass' : "",
|
||||
'root_user' : "vagrant",
|
||||
'root_pass' : "",
|
||||
'ssh_key_file' : VAGRANT_KEY_FILE,
|
||||
'ssh_pub_key_file': VAGRANT_PUB_KEY_FILE,
|
||||
'memory' : "4G",
|
||||
'extra_args' : [],
|
||||
'qemu_args' : "-device VGA",
|
||||
'dns' : "",
|
||||
'ssh_port' : 0,
|
||||
'install_cmds' : "",
|
||||
'boot_dev_type' : "block",
|
||||
'ssh_timeout' : 1,
|
||||
}
|
||||
|
||||
class HaikuVM(basevm.BaseVM):
|
||||
name = "haiku"
|
||||
arch = "x86_64"
|
||||
|
||||
link = "https://app.vagrantup.com/haiku-os/boxes/r1beta2-x86_64/versions/20200702/providers/libvirt.box"
|
||||
csum = "41c38b316e0cbdbc66b5dbaf3612b866700a4f35807cb1eb266a5bf83e9e68d5"
|
||||
|
||||
poweroff = "shutdown"
|
||||
|
||||
requirements = [
|
||||
"devel:libbz2",
|
||||
"devel:libcapstone",
|
||||
"devel:libcurl",
|
||||
"devel:libfdt",
|
||||
"devel:libgcrypt",
|
||||
"devel:libgl",
|
||||
"devel:libglib_2.0",
|
||||
"devel:libgnutls",
|
||||
"devel:libgpg_error",
|
||||
"devel:libintl",
|
||||
"devel:libjpeg",
|
||||
"devel:liblzo2",
|
||||
"devel:libncursesw",
|
||||
"devel:libnettle",
|
||||
"devel:libpixman_1",
|
||||
"devel:libpng16",
|
||||
"devel:libsdl2_2.0",
|
||||
"devel:libsnappy",
|
||||
"devel:libssh2",
|
||||
"devel:libtasn1",
|
||||
"devel:libusb_1.0",
|
||||
"devel:libz",
|
||||
"ninja",
|
||||
"setuptools_python3"
|
||||
]
|
||||
|
||||
# https://dev.haiku-os.org/ticket/16512 virtio disk1 shows up as 0 (reversed order)
|
||||
BUILD_SCRIPT = """
|
||||
set -e;
|
||||
rm -rf /tmp/qemu-test.*
|
||||
cd $(mktemp -d /tmp/qemu-test.XXXXXX);
|
||||
mkdir src build; cd src;
|
||||
tar -xf /dev/disk/virtual/virtio_block/0/raw;
|
||||
mkdir -p /usr/bin
|
||||
ln -s /boot/system/bin/env /usr/bin/env
|
||||
cd ../build
|
||||
../src/configure --disable-slirp {configure_opts};
|
||||
make --output-sync -j{jobs} {target} {verbose};
|
||||
"""
|
||||
|
||||
def build_image(self, img):
|
||||
self.print_step("Downloading disk image")
|
||||
tarball = self._download_with_cache(self.link, sha256sum=self.csum)
|
||||
|
||||
self.print_step("Extracting disk image")
|
||||
|
||||
subprocess.check_call(["tar", "xzf", tarball, "./box.img", "-O"],
|
||||
stdout=open(img, 'wb'))
|
||||
|
||||
self.print_step("Preparing disk image")
|
||||
self.boot(img)
|
||||
|
||||
# Wait for ssh to be available.
|
||||
self.wait_ssh(wait_root=True, cmd="exit 0")
|
||||
|
||||
# Install packages
|
||||
self.ssh_root("pkgman install -y %s" % " ".join(self.requirements))
|
||||
self.graceful_shutdown()
|
||||
|
||||
self.print_step("All done")
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(basevm.main(HaikuVM, config=HAIKU_CONFIG))
|
@ -22,8 +22,8 @@ class NetBSDVM(basevm.BaseVM):
|
||||
name = "netbsd"
|
||||
arch = "x86_64"
|
||||
|
||||
link = "https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.0/images/NetBSD-9.0-amd64.iso"
|
||||
csum = "34da4882ee61bdbf69f241195a8933dc800949d30b43fc6988da853d57fc2b8cac50cf97a0d2adaf93250b4e329d189c1a8b83c33bd515226f37745d50c33369"
|
||||
link = "https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.1/images/NetBSD-9.1-amd64.iso"
|
||||
csum = "65bddc95945991c3b2021f9c8ded7f34c25f0a7611b7aa15a15fe23399e902307e926ae97fcd01dc1662ac67b5f6e4be643c6a2b581692ddcb616d30125066f9"
|
||||
size = "20G"
|
||||
pkgs = [
|
||||
# tools
|
||||
@ -38,7 +38,7 @@ class NetBSDVM(basevm.BaseVM):
|
||||
"bash",
|
||||
"gmake",
|
||||
"gsed",
|
||||
"gettext",
|
||||
"gettext-tools",
|
||||
|
||||
# libs: crypto
|
||||
"gnutls",
|
||||
|
Loading…
Reference in New Issue
Block a user