grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) {
    if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true;
    self->last_scrolled_by = scrolled_by;
    if (!self->layers_dirty) return false;
    self->layers_dirty = false;
    size_t i, j;
    self->num_of_below_refs = 0;
    self->num_of_negative_refs = 0;
    self->num_of_positive_refs = 0;
    Image *img; ImageRef *ref;
    ImageRect r;
    float screen_width = dx * num_cols, screen_height = dy * num_rows;
    float screen_bottom = screen_top - screen_height;
    float screen_width_px = num_cols * cell.width;
    float screen_height_px = num_rows * cell.height;
    float y0 = screen_top - dy * scrolled_by;

    // Iterate over all visible refs and create render data
    self->count = 0;
    for (i = 0; i < self->image_count; i++) { img = self->images + i; for (j = 0; j < img->refcnt; j++) { ref = img->refs + j;
        r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height;
        if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy;
        else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px;
        if (r.top <= screen_bottom || r.bottom >= screen_top) continue;  // not visible

        r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width;
        if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx;
        else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px;

        if (ref->z_index < ((int32_t)INT32_MIN/2))
            self->num_of_below_refs++;
        else if (ref->z_index < 0)
            self->num_of_negative_refs++;
        else
            self->num_of_positive_refs++;
        ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true);
        ImageRenderData *rd = self->render_data + self->count;
        zero_at_ptr(rd);
        set_vertex_data(rd, ref, &r);
        self->count++;
        rd->z_index = ref->z_index; rd->image_id = img->internal_id;
        rd->texture_id = img->texture_id;
    }}
    if (!self->count) return false;
    // Sort visible refs in draw order (z-index, img)
    qsort(self->render_data, self->count, sizeof(self->render_data[0]), cmp_by_zindex_and_image);
    // Calculate the group counts
    i = 0;
    while (i < self->count) {
        id_type image_id = self->render_data[i].image_id, start = i;
        if (start == self->count - 1) i = self->count;
        else {
            while (i < self->count - 1 && self->render_data[++i].image_id == image_id) {}
        }
        self->render_data[start].group_count = i - start;
    }
    return true;
}

static bool send_to_gpu = true;

static id_type internal_id_counter = 1;

static bool has_add_respose = false;

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};


free_load_data(LoadData *ld) {
    free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0;
    ld->buf = NULL;

    if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz);
    ld->mapped_file = NULL; ld->mapped_file_sz = 0;
}

Graphics protocol: Dont return filename in the error message when opening file fails, since filenames can contain control characters

Fixes #3128

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
    has_add_respose = false;
    if (img == NULL) img = img_by_client_id(self, g->id);
    if (img == NULL) { set_add_response("ENOENT", "Put command refers to non-existent image with id: %u", g->id); return; }
    if (!img->data_loaded) { set_add_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return; }
    ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
    *is_dirty = true;
    self->layers_dirty = true;
    ImageRef *ref = NULL;
    for (size_t i=0; i < img->refcnt; i++) {
        if ((unsigned)img->refs[i].start_row == c->x && (unsigned)img->refs[i].start_column == c->y) {
            ref = img->refs + i;
            break;
        }
    }
    if (ref == NULL) {
        ref = img->refs + img->refcnt++;
        zero_at_ptr(ref);
    }
    img->atime = monotonic();
    ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height;
    ref->src_width = MIN(ref->src_width, img->width - (img->width > ref->src_x ? ref->src_x : img->width));
    ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height));
    ref->z_index = g->z_index;
    ref->start_row = c->y; ref->start_column = c->x;
    ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1);
    ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1);
    ref->num_cols = g->num_cells; ref->num_rows = g->num_lines;
    update_src_rect(ref, img);
    update_dest_rect(ref, g->num_cells, g->num_lines, cell);
    // Move the cursor, the screen will take care of ensuring it is in bounds
    c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
}

static bool send_to_gpu = true;

static bool has_add_respose = false;

#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); goto err; }

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
    if (id) {
        for (size_t i = 0; i < self->image_count; i++) {
            if (self->images[i].client_id == id) {
                *existing = true;
                return self->images + i;
            }
        }
    }
    *existing = false;
    ensure_space_for(self, images, Image, self->image_count + 1, images_capacity, 64, true);
    Image *ans = self->images + self->image_count++;
    zero_at_ptr(ans);
    return ans;
}

static bool send_to_gpu = true;

static bool has_add_respose = false;

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};


clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
    return ref->start_row + (int32_t)ref->effective_num_rows > 0;
}

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;


png_error_handler(const char *code, const char *msg) {
    set_add_response(code, "%s", msg);
}

static char add_response[512] = {0};

#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); goto err; }


img_by_internal_id(GraphicsManager *self, id_type id) {
    for (size_t i = 0; i < self->image_count; i++) {
        if (self->images[i].internal_id == id) return self->images + i;
    }
    return NULL;
}

static id_type internal_id_counter = 1;

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


W(update_layers) {
    unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy;
    CellPixelSize cell;
    PA("IffffIIII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height);
    grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy, cell);
    PyObject *ans = PyTuple_New(self->count);
    for (size_t i = 0; i < self->count; i++) {
        ImageRenderData *r = self->render_data + i;
#define R(offset) Py_BuildValue("{sf sf sf sf}", "left", r->vertices[offset + 8], "top", r->vertices[offset + 1], "right", r->vertices[offset], "bottom", r->vertices[offset + 5])
        PyTuple_SET_ITEM(ans, i,
            Py_BuildValue("{sN sN sI si sK}", "src_rect", R(0), "dest_rect", R(2), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id)
        );
#undef R
    }
    return ans;
}

#define STORAGE_LIMIT (320u * (1024u * 1024u))

#define REPORT_ERROR(...) { log_error(__VA_ARGS__); }

#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); goto err; }

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

#define M(x, va) {#x, (PyCFunction)py##x, va, ""}

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};


img_by_client_id(GraphicsManager *self, uint32_t id) {
    for (size_t i = 0; i < self->image_count; i++) {
        if (self->images[i].client_id == id) return self->images + i;
    }
    return NULL;
}

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) {
    // First remove unreferenced images, even if they have an id
    remove_images(self, trim_predicate, currently_added_image_internal_id);
    if (self->used_storage < storage_limit) return;

    qsort(self->images, self->image_count, sizeof(self->images[0]), oldest_last);
    while (self->used_storage > storage_limit && self->image_count > 0) {
        remove_image(self, self->image_count - 1);
    }
    if (!self->image_count) self->used_storage = 0;  // sanity check
}

static id_type internal_id_counter = 1;

#define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};


scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) {
    ScrollData *d = (ScrollData*)data;
    if (ref_within_region(ref, d->margin_top, d->margin_bottom)) {
        ref->start_row += d->amt;
        if (ref_outside_region(ref, d->margin_top, d->margin_bottom)) return true;
        // Clip the image if scrolling has resulted in part of it being outside the page area
        uint32_t clip_amt, clipped_rows;
        if (ref->start_row < (int32_t)d->margin_top) {
            // image moved up
            clipped_rows = d->margin_top - ref->start_row;
            clip_amt = cell.height * clipped_rows;
            if (ref->src_height <= clip_amt) return true;
            ref->src_y += clip_amt; ref->src_height -= clip_amt;
            ref->effective_num_rows -= clipped_rows;
            update_src_rect(ref, img);
            ref->start_row += clipped_rows;
        } else if (ref->start_row + ref->effective_num_rows > d->margin_bottom) {
            // image moved down
            clipped_rows = ref->start_row + ref->effective_num_rows - d->margin_bottom;
            clip_amt = cell.height * clipped_rows;
            if (ref->src_height <= clip_amt) return true;
            ref->src_height -= clip_amt;
            ref->effective_num_rows -= clipped_rows;
            update_src_rect(ref, img);
        }
        return ref_outside_region(ref, d->margin_top, d->margin_bottom);
    }
    return false;
}

static bool send_to_gpu = true;

static bool has_add_respose = false;

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;


png_path_to_bitmap(const char* path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) {
    FILE* fp = fopen(path, "r");
    if (fp == NULL) {
        log_error("The PNG image: %s could not be opened with error: %s", path, strerror(errno));
        return false;
    }
    size_t capacity = 16*1024, pos = 0;
    unsigned char *buf = malloc(capacity);
    if (!buf) { log_error("Out of memory reading PNG file at: %s", path); fclose(fp); return false; }
    while (!feof(fp)) {
        if (pos - capacity < 1024) {
            capacity *= 2;
            unsigned char *new_buf = realloc(buf, capacity);
            if (!new_buf) {
                free(buf);
                log_error("Out of memory reading PNG file at: %s", path); fclose(fp); return false;
            }
            buf = new_buf;
        }
        pos += fread(buf + pos, sizeof(char), capacity - pos, fp);
        int saved_errno = errno;
        if (ferror(fp) && saved_errno != EINTR) {
            log_error("Failed while reading from file: %s with error: %s", path, strerror(saved_errno));
            fclose(fp);
            free(buf);
            return false;
        }
    }
    fclose(fp); fp = NULL;
    png_read_data d = {0};
    inflate_png_inner(&d, buf, pos);
    free(buf);
    if (!d.ok) {
        log_error("Failed to decode PNG image at: %s", path);
        return false;
    }
    *data = d.decompressed;
    *sz = d.sz;
    *height = d.height; *width = d.width;
    return true;
}

#define REPORT_ERROR(...) { log_error(__VA_ARGS__); }

static bool send_to_gpu = true;

static char add_response[512] = {0};

static bool has_add_respose = false;

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


W(shm_write) {
    const char *name, *data;
    Py_ssize_t sz;
    PA("ss#", &name, &data, &sz);
    int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
    int ret = ftruncate(fd, sz);
    if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
    void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
    memcpy(addr, data, sz);
    if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
    safe_close(fd, __FILE__, __LINE__);
    Py_RETURN_NONE;
}

static char add_response[512] = {0};

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;

static PyMethodDef methods[] = {
    M(image_for_client_id, METH_O),
    M(update_layers, METH_VARARGS),
    {NULL}  /* Sentinel */
};

static PyMemberDef members[] = {
    {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), 0, "image_count"},
    {NULL},
};

PyTypeObject GraphicsManager_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "fast_data_types.GraphicsManager",
    .tp_basicsize = sizeof(GraphicsManager),
    .tp_dealloc = (destructor)dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "GraphicsManager",
    .tp_new = new,
    .tp_methods = methods,
    .tp_members = members,
};

static PyMethodDef module_methods[] = {
    M(shm_write, METH_VARARGS),
    M(shm_unlink, METH_VARARGS),
    M(set_send_to_gpu, METH_O),
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


add_trim_predicate(Image *img) {
    return !img->data_loaded || (!img->client_id && !img->refcnt);
}

#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;


	bool UserPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser = NULL) {
		CSmartPtr<CWebSession> spSession = WebSock.GetSession();
		Tmpl.SetFile("add_edit_user.tmpl");

		if (!WebSock.GetParam("submitted").ToUInt()) {
			CString sAllowedHosts, sServers, sChans, sCTCPReplies;

			if (pUser) {
				Tmpl["Action"] = "edituser";
				Tmpl["Title"] = "Edit User [" + pUser->GetUserName() + "]";
				Tmpl["Edit"] = "true";
			} else {
				CString sUsername = WebSock.GetParam("clone", false);
				pUser = CZNC::Get().FindUser(sUsername);

				if (pUser) {
					Tmpl["Title"] = "Clone User [" + pUser->GetUserName() + "]";
					Tmpl["Clone"] = "true";
					Tmpl["CloneUsername"] = pUser->GetUserName();
				}
			}

			Tmpl["ImAdmin"] = CString(spSession->IsAdmin());

			if (pUser) {
				Tmpl["Username"] = pUser->GetUserName();
				Tmpl["Nick"] = pUser->GetNick();
				Tmpl["AltNick"] = pUser->GetAltNick();
				Tmpl["StatusPrefix"] = pUser->GetStatusPrefix();
				Tmpl["Ident"] = pUser->GetIdent();
				Tmpl["RealName"] = pUser->GetRealName();
				Tmpl["QuitMsg"] = pUser->GetQuitMsg();
				Tmpl["DefaultChanModes"] = pUser->GetDefaultChanModes();
				Tmpl["BufferCount"] = CString(pUser->GetBufferCount());
				Tmpl["TimestampFormat"] = pUser->GetTimestampFormat();
				Tmpl["Timezone"] = pUser->GetTimezone();
				Tmpl["JoinTries"] = CString(pUser->JoinTries());
				Tmpl["MaxNetworks"] = CString(pUser->MaxNetworks());

				const set<CString>& ssAllowedHosts = pUser->GetAllowedHosts();
				for (set<CString>::const_iterator it = ssAllowedHosts.begin(); it != ssAllowedHosts.end(); ++it) {
					CTemplate& l = Tmpl.AddRow("AllowedHostLoop");
					l["Host"] = *it;
				}

				const vector<CIRCNetwork*>& vNetworks = pUser->GetNetworks();
				for (unsigned int a = 0; a < vNetworks.size(); a++) {
					CTemplate& l = Tmpl.AddRow("NetworkLoop");
					l["Name"] = vNetworks[a]->GetName();
					l["Username"] = pUser->GetUserName();
					l["Clients"] = CString(vNetworks[a]->GetClients().size());
					l["IRCNick"] = vNetworks[a]->GetIRCNick().GetNick();
					CServer* pServer = vNetworks[a]->GetCurrentServer();
					if (pServer) {
						l["Server"] = pServer->GetName();
					}
				}

				const MCString& msCTCPReplies = pUser->GetCTCPReplies();
				for (MCString::const_iterator it2 = msCTCPReplies.begin(); it2 != msCTCPReplies.end(); ++it2) {
					CTemplate& l = Tmpl.AddRow("CTCPLoop");
					l["CTCP"] = it2->first + " " + it2->second;
				}
			} else {
				Tmpl["Action"] = "adduser";
				Tmpl["Title"] = "Add User";
				Tmpl["StatusPrefix"] = "*";
			}

			SCString ssTimezones = CUtils::GetTimezones();
			for (SCString::iterator i = ssTimezones.begin(); i != ssTimezones.end(); ++i) {
				CTemplate& l = Tmpl.AddRow("TZLoop");
				l["TZ"] = *i;
			}

			// To change BindHosts be admin or don't have DenySetBindHost
			if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
				Tmpl["BindHostEdit"] = "true";
				const VCString& vsBindHosts = CZNC::Get().GetBindHosts();
				if (vsBindHosts.empty()) {
					if (pUser) {
						Tmpl["BindHost"] = pUser->GetBindHost();
						Tmpl["DCCBindHost"] = pUser->GetDCCBindHost();
					}
				} else {
					bool bFoundBindHost = false;
					bool bFoundDCCBindHost = false;
					for (unsigned int b = 0; b < vsBindHosts.size(); b++) {
						const CString& sBindHost = vsBindHosts[b];
						CTemplate& l = Tmpl.AddRow("BindHostLoop");
						CTemplate& k = Tmpl.AddRow("DCCBindHostLoop");

						l["BindHost"] = sBindHost;
						k["BindHost"] = sBindHost;

						if (pUser && pUser->GetBindHost() == sBindHost) {
							l["Checked"] = "true";
							bFoundBindHost = true;
						}

						if (pUser && pUser->GetDCCBindHost() == sBindHost) {
							k["Checked"] = "true";
							bFoundDCCBindHost = true;
						}
					}

					// If our current bindhost is not in the global list...
					if (pUser && !bFoundBindHost && !pUser->GetBindHost().empty()) {
						CTemplate& l = Tmpl.AddRow("BindHostLoop");

						l["BindHost"] = pUser->GetBindHost();
						l["Checked"] = "true";
					}
					if (pUser && !bFoundDCCBindHost && !pUser->GetDCCBindHost().empty()) {
						CTemplate& l = Tmpl.AddRow("DCCBindHostLoop");

						l["BindHost"] = pUser->GetDCCBindHost();
						l["Checked"] = "true";
					}
				}
			}

			vector<CString> vDirs;
			WebSock.GetAvailSkins(vDirs);

			for (unsigned int d = 0; d < vDirs.size(); d++) {
				const CString& SubDir = vDirs[d];
				CTemplate& l = Tmpl.AddRow("SkinLoop");
				l["Name"] = SubDir;

				if (pUser && SubDir == pUser->GetSkinName()) {
					l["Checked"] = "true";
				}
			}

			set<CModInfo> ssUserMods;
			CZNC::Get().GetModules().GetAvailableMods(ssUserMods);

			for (set<CModInfo>::iterator it = ssUserMods.begin(); it != ssUserMods.end(); ++it) {
				const CModInfo& Info = *it;
				CTemplate& l = Tmpl.AddRow("ModuleLoop");

				l["Name"] = Info.GetName();
				l["Description"] = Info.GetDescription();
				l["Wiki"] = Info.GetWikiPage();
				l["HasArgs"] = CString(Info.GetHasArgs());
				l["ArgsHelpText"] = Info.GetArgsHelpText();

				CModule *pModule = NULL;
				if (pUser)
					pModule = pUser->GetModules().FindModule(Info.GetName());
				if (pModule) {
					l["Checked"] = "true";
					l["Args"] = pModule->GetArgs();
					if (CModInfo::UserModule == GetType() && Info.GetName() == GetModName()) {
						l["Disabled"] = "true";
					}
				}

				if (!spSession->IsAdmin() && pUser && pUser->DenyLoadMod()) {
					l["Disabled"] = "true";
				}
			}

			CTemplate& o1 = Tmpl.AddRow("OptionLoop");
			o1["Name"] = "autoclearchanbuffer";
			o1["DisplayName"] = "Auto Clear Chan Buffer";
			o1["Tooltip"] = "Automatically Clear Channel Buffer After Playback (the default value for new channels)";
			if (!pUser || pUser->AutoClearChanBuffer()) { o1["Checked"] = "true"; }

			/* o2 used to be auto cycle which was removed */

			CTemplate& o4 = Tmpl.AddRow("OptionLoop");
			o4["Name"] = "multiclients";
			o4["DisplayName"] = "Multi Clients";
			if (!pUser || pUser->MultiClients()) { o4["Checked"] = "true"; }

			CTemplate& o7 = Tmpl.AddRow("OptionLoop");
			o7["Name"] = "appendtimestamp";
			o7["DisplayName"] = "Append Timestamps";
			if (pUser && pUser->GetTimestampAppend()) { o7["Checked"] = "true"; }

			CTemplate& o8 = Tmpl.AddRow("OptionLoop");
			o8["Name"] = "prependtimestamp";
			o8["DisplayName"] = "Prepend Timestamps";
			if (pUser && pUser->GetTimestampPrepend()) { o8["Checked"] = "true"; }

			if (spSession->IsAdmin()) {
				CTemplate& o9 = Tmpl.AddRow("OptionLoop");
				o9["Name"] = "denyloadmod";
				o9["DisplayName"] = "Deny LoadMod";
				if (pUser && pUser->DenyLoadMod()) { o9["Checked"] = "true"; }

				CTemplate& o10 = Tmpl.AddRow("OptionLoop");
				o10["Name"] = "isadmin";
				o10["DisplayName"] = "Admin";
				if (pUser && pUser->IsAdmin()) { o10["Checked"] = "true"; }
				if (pUser && pUser == CZNC::Get().FindUser(WebSock.GetUser())) { o10["Disabled"] = "true"; }

				CTemplate& o11 = Tmpl.AddRow("OptionLoop");
				o11["Name"] = "denysetbindhost";
				o11["DisplayName"] = "Deny SetBindHost";
				if (pUser && pUser->DenySetBindHost()) { o11["Checked"] = "true"; }
			}

			FOR_EACH_MODULE(i, pUser) {
				CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop");
				mod.insert(Tmpl.begin(), Tmpl.end());
				mod["WebadminAction"] = "display";
				if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/user", mod)) {
					mod["Embed"] = WebSock.FindTmpl(*i, "WebadminUser.tmpl");
					mod["ModName"] = (*i)->GetModName();
				}
			}

			return true;
		}

		/* If pUser is NULL, we are adding a user, else we are editing this one */

		CString sUsername = WebSock.GetParam("user");
		if (!pUser && CZNC::Get().FindUser(sUsername)) {
			WebSock.PrintErrorPage("Invalid Submission [User " + sUsername + " already exists]");
			return true;
		}

		CUser* pNewUser = GetNewUser(WebSock, pUser);
		if (!pNewUser) {
			WebSock.PrintErrorPage("Invalid user settings");
			return true;
		}

		CString sErr;
		CString sAction;

		if (!pUser) {
			CString sClone = WebSock.GetParam("clone");
			if (CUser *pCloneUser = CZNC::Get().FindUser(sClone)) {
				pNewUser->CloneNetworks(*pCloneUser);
			}

			// Add User Submission
			if (!CZNC::Get().AddUser(pNewUser, sErr)) {
				delete pNewUser;
				WebSock.PrintErrorPage("Invalid submission [" + sErr + "]");
				return true;
			}

			pUser = pNewUser;
			sAction = "added";
		} else {
			// Edit User Submission
			if (!pUser->Clone(*pNewUser, sErr, false)) {
				delete pNewUser;
				WebSock.PrintErrorPage("Invalid Submission [" + sErr + "]");
				return true;
			}

			delete pNewUser;
			sAction = "edited";
		}

		CTemplate TmplMod;
		TmplMod["Username"] = sUsername;
		TmplMod["WebadminAction"] = "change";
		FOR_EACH_MODULE(it, pUser) {
			(*it)->OnEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod);
		}

		if (!CZNC::Get().WriteConfig()) {
			WebSock.PrintErrorPage("User " + sAction + ", but config was not written");
			return true;
		}

		if (!spSession->IsAdmin()) {
			WebSock.Redirect("edituser");
		} else {
			WebSock.Redirect("listusers");
		}

		/* we don't want the template to be printed while we redirect */
		return false;
	}

Fix NULL pointer dereference in webadmin.

Triggerable by any non-admin, if webadmin is loaded.

The only affected version is 1.0

Thanks to ChauffeR (Simone Esposito) for reporting this.

struct FOR_EACH_MODULE_Type {
	enum {
		AtGlobal,
		AtUser,
		AtNetwork,
	} where;
	CModules CMtemp;
	CModules& CMuser;
	CModules& CMnet;
	FOR_EACH_MODULE_Type(CUser* pUser) : CMuser(pUser ? pUser->GetModules() : CMtemp), CMnet(CMtemp) {
		where = AtGlobal;
	}
	FOR_EACH_MODULE_Type(CIRCNetwork* pNetwork) : CMuser(pNetwork ? pNetwork->GetUser()->GetModules() : CMtemp), CMnet(pNetwork ? pNetwork->GetModules() : CMtemp) {
		where = AtGlobal;
	}
	FOR_EACH_MODULE_Type(std::pair<CUser*, CIRCNetwork*> arg) : CMuser(arg.first ? arg.first->GetModules() : CMtemp), CMnet(arg.second ? arg.second->GetModules() : CMtemp) {
		where = AtGlobal;
	}
	operator bool() { return false; }
}

#define FOR_EACH_MODULE(I, pUserOrNetwork)\
	if (FOR_EACH_MODULE_Type FOR_EACH_MODULE_Var = pUserOrNetwork) {} else\
	for (CModules::iterator I = CZNC::Get().GetModules().begin(); FOR_EACH_MODULE_CanContinue(FOR_EACH_MODULE_Var, I); ++I)


	CUser* GetNewUser(CWebSock& WebSock, CUser* pUser) {
		CSmartPtr<CWebSession> spSession = WebSock.GetSession();
		CString sUsername = WebSock.GetParam("newuser");

		if (sUsername.empty()) {
			sUsername = WebSock.GetParam("user");
		}

		if (sUsername.empty()) {
			WebSock.PrintErrorPage("Invalid Submission [Username is required]");
			return NULL;
		}

		if (pUser) {
			/* If we are editing a user we must not change the user name */
			sUsername = pUser->GetUserName();
		}

		CString sArg = WebSock.GetParam("password");

		if (sArg != WebSock.GetParam("password2")) {
			WebSock.PrintErrorPage("Invalid Submission [Passwords do not match]");
			return NULL;
		}

		CUser* pNewUser = new CUser(sUsername);

		if (!sArg.empty()) {
			CString sSalt = CUtils::GetSalt();
			CString sHash = CUser::SaltedHash(sArg, sSalt);
			pNewUser->SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
		}

		VCString vsArgs;
		unsigned int a = 0;

		WebSock.GetRawParam("allowedips").Split("\n", vsArgs);
		if (vsArgs.size()) {
			for (a = 0; a < vsArgs.size(); a++) {
				pNewUser->AddAllowedHost(vsArgs[a].Trim_n());
			}
		} else {
			pNewUser->AddAllowedHost("*");
		}

		WebSock.GetRawParam("ctcpreplies").Split("\n", vsArgs);
		for (a = 0; a < vsArgs.size(); a++) {
			CString sReply = vsArgs[a].TrimRight_n("\r");
			pNewUser->AddCTCPReply(sReply.Token(0).Trim_n(), sReply.Token(1, true).Trim_n());
		}

		sArg = WebSock.GetParam("nick"); if (!sArg.empty()) { pNewUser->SetNick(sArg); }
		sArg = WebSock.GetParam("altnick"); if (!sArg.empty()) { pNewUser->SetAltNick(sArg); }
		sArg = WebSock.GetParam("statusprefix"); if (!sArg.empty()) { pNewUser->SetStatusPrefix(sArg); }
		sArg = WebSock.GetParam("ident"); if (!sArg.empty()) { pNewUser->SetIdent(sArg); }
		sArg = WebSock.GetParam("skin"); if (!sArg.empty()) { pNewUser->SetSkinName(sArg); }
		sArg = WebSock.GetParam("realname"); if (!sArg.empty()) { pNewUser->SetRealName(sArg); }
		sArg = WebSock.GetParam("quitmsg"); if (!sArg.empty()) { pNewUser->SetQuitMsg(sArg); }
		sArg = WebSock.GetParam("chanmodes"); if (!sArg.empty()) { pNewUser->SetDefaultChanModes(sArg); }
		sArg = WebSock.GetParam("timestampformat"); if (!sArg.empty()) { pNewUser->SetTimestampFormat(sArg); }

		sArg = WebSock.GetParam("bindhost");
		// To change BindHosts be admin or don't have DenySetBindHost
		if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
			CString sArg2 = WebSock.GetParam("dccbindhost");
			if (!sArg.empty()) {
				pNewUser->SetBindHost(sArg);
			}
			if (!sArg2.empty()) {
				pNewUser->SetDCCBindHost(sArg2);
			}

			const VCString& vsHosts = CZNC::Get().GetBindHosts();
			if (!spSession->IsAdmin() && !vsHosts.empty()) {
				VCString::const_iterator it;
				bool bFound = false;
				bool bFoundDCC = false;

				for (it = vsHosts.begin(); it != vsHosts.end(); ++it) {
					if (sArg.Equals(*it)) {
						bFound = true;
					}
					if (sArg2.Equals(*it)) {
						bFoundDCC = true;
					}
				}

				if (!bFound) {
					pNewUser->SetBindHost(pUser ? pUser->GetBindHost() : "");
				}
				if (!bFoundDCC) {
					pNewUser->SetDCCBindHost(pUser ? pUser->GetDCCBindHost() : "");
				}
			}
		} else if (pUser){
			pNewUser->SetBindHost(pUser->GetBindHost());
			pNewUser->SetDCCBindHost(pUser->GetDCCBindHost());
		}

		sArg = WebSock.GetParam("bufsize"); if (!sArg.empty()) pNewUser->SetBufferCount(sArg.ToUInt(), spSession->IsAdmin());
		if (!sArg.empty()) {
			// First apply the old limit in case the new one is too high
			if (pUser)
				pNewUser->SetBufferCount(pUser->GetBufferCount(), true);
			pNewUser->SetBufferCount(sArg.ToUInt(), spSession->IsAdmin());
		}

		pNewUser->SetSkinName(WebSock.GetParam("skin"));
		pNewUser->SetAutoClearChanBuffer(WebSock.GetParam("autoclearchanbuffer").ToBool());
		pNewUser->SetMultiClients(WebSock.GetParam("multiclients").ToBool());
		pNewUser->SetTimestampAppend(WebSock.GetParam("appendtimestamp").ToBool());
		pNewUser->SetTimestampPrepend(WebSock.GetParam("prependtimestamp").ToBool());
		pNewUser->SetTimezone(WebSock.GetParam("timezone"));
		pNewUser->SetJoinTries(WebSock.GetParam("jointries").ToUInt());

		if (spSession->IsAdmin()) {
			pNewUser->SetDenyLoadMod(WebSock.GetParam("denyloadmod").ToBool());
			pNewUser->SetDenySetBindHost(WebSock.GetParam("denysetbindhost").ToBool());
			sArg = WebSock.GetParam("maxnetworks"); if (!sArg.empty()) pNewUser->SetMaxNetworks(sArg.ToUInt());
		} else if (pUser) {
			pNewUser->SetDenyLoadMod(pUser->DenyLoadMod());
			pNewUser->SetDenySetBindHost(pUser->DenySetBindHost());
			pNewUser->SetMaxNetworks(pUser->MaxNetworks());
		}

		// If pUser is not NULL, we are editing an existing user.
		// Users must not be able to change their own admin flag.
		if (pUser != CZNC::Get().FindUser(WebSock.GetUser())) {
			pNewUser->SetAdmin(WebSock.GetParam("isadmin").ToBool());
		} else if (pUser) {
			pNewUser->SetAdmin(pUser->IsAdmin());
		}

		if (spSession->IsAdmin() || (pUser && !pUser->DenyLoadMod())) {
			WebSock.GetParamValues("loadmod", vsArgs);

			// disallow unload webadmin from itself
			if (CModInfo::UserModule == GetType() && pUser == CZNC::Get().FindUser(WebSock.GetUser())) {
				bool bLoadedWebadmin = false;
				for (a = 0; a < vsArgs.size(); ++a) {
					CString sModName = vsArgs[a].TrimRight_n("\r");
					if (sModName == GetModName()) {
						bLoadedWebadmin = true;
						break;
					}
				}
				if (!bLoadedWebadmin) {
					vsArgs.push_back(GetModName());
				}
			}

			for (a = 0; a < vsArgs.size(); a++) {
				CString sModRet;
				CString sModName = vsArgs[a].TrimRight_n("\r");
				CString sModLoadError;

				if (!sModName.empty()) {
					CString sArgs = WebSock.GetParam("modargs_" + sModName);

					try {
						if (!pNewUser->GetModules().LoadModule(sModName, sArgs, CModInfo::UserModule, pNewUser, NULL, sModRet)) {
							sModLoadError = "Unable to load module [" + sModName + "] [" + sModRet + "]";
						}
					} catch (...) {
						sModLoadError = "Unable to load module [" + sModName + "] [" + sArgs + "]";
					}

					if (!sModLoadError.empty()) {
						DEBUG(sModLoadError);
						spSession->AddError(sModLoadError);
					}
				}
			}
		} else if (pUser) {
			CModules& Modules = pUser->GetModules();

			for (a = 0; a < Modules.size(); a++) {
				CString sModName = Modules[a]->GetModName();
				CString sArgs = Modules[a]->GetArgs();
				CString sModRet;
				CString sModLoadError;

				try {
					if (!pNewUser->GetModules().LoadModule(sModName, sArgs, CModInfo::UserModule, pNewUser, NULL, sModRet)) {
						sModLoadError = "Unable to load module [" + sModName + "] [" + sModRet + "]";
					}
				} catch (...) {
					sModLoadError = "Unable to load module [" + sModName + "]";
				}

				if (!sModLoadError.empty()) {
					DEBUG(sModLoadError);
					spSession->AddError(sModLoadError);
				}
			}
		}

		return pNewUser;
	}


	bool NetworkPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser, CIRCNetwork* pNetwork = NULL) {
		CSmartPtr<CWebSession> spSession = WebSock.GetSession();
		Tmpl.SetFile("add_edit_network.tmpl");

		if (!WebSock.GetParam("submitted").ToUInt()) {
			Tmpl["Username"] = pUser->GetUserName();

			set<CModInfo> ssNetworkMods;
			CZNC::Get().GetModules().GetAvailableMods(ssNetworkMods, CModInfo::NetworkModule);
			for (set<CModInfo>::iterator it = ssNetworkMods.begin(); it != ssNetworkMods.end(); ++it) {
				const CModInfo& Info = *it;
				CTemplate& l = Tmpl.AddRow("ModuleLoop");

				l["Name"] = Info.GetName();
				l["Description"] = Info.GetDescription();
				l["Wiki"] = Info.GetWikiPage();
				l["HasArgs"] = CString(Info.GetHasArgs());
				l["ArgsHelpText"] = Info.GetArgsHelpText();

				if (pNetwork) {
					CModule *pModule = pNetwork->GetModules().FindModule(Info.GetName());
					if (pModule) {
						l["Checked"] = "true";
						l["Args"] = pModule->GetArgs();
					}
				}

				if (!spSession->IsAdmin() && pUser->DenyLoadMod()) {
					l["Disabled"] = "true";
				}
			}

			// To change BindHosts be admin or don't have DenySetBindHost
			if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
				Tmpl["BindHostEdit"] = "true";
				const VCString& vsBindHosts = CZNC::Get().GetBindHosts();
				if (vsBindHosts.empty()) {
					if (pNetwork) {
						Tmpl["BindHost"] = pNetwork->GetBindHost();
					}
				} else {
					bool bFoundBindHost = false;
					for (unsigned int b = 0; b < vsBindHosts.size(); b++) {
						const CString& sBindHost = vsBindHosts[b];
						CTemplate& l = Tmpl.AddRow("BindHostLoop");

						l["BindHost"] = sBindHost;

						if (pNetwork && pNetwork->GetBindHost() == sBindHost) {
							l["Checked"] = "true";
							bFoundBindHost = true;
						}
					}

					// If our current bindhost is not in the global list...
					if (pNetwork && !bFoundBindHost && !pNetwork->GetBindHost().empty()) {
						CTemplate& l = Tmpl.AddRow("BindHostLoop");

						l["BindHost"] = pNetwork->GetBindHost();
						l["Checked"] = "true";
					}
				}
			}

			if (pNetwork) {
				Tmpl["Action"] = "editnetwork";
				Tmpl["Edit"] = "true";
				Tmpl["Title"] = "Edit Network" + CString(" [" + pNetwork->GetName() + "]") + " of User [" + pUser->GetUserName() + "]";
				Tmpl["Name"] = pNetwork->GetName();

				Tmpl["Nick"] = pNetwork->GetNick();
				Tmpl["AltNick"] = pNetwork->GetAltNick();
				Tmpl["Ident"] = pNetwork->GetIdent();
				Tmpl["RealName"] = pNetwork->GetRealName();

				Tmpl["FloodProtection"] = CString(CIRCSock::IsFloodProtected(pNetwork->GetFloodRate()));
				Tmpl["FloodRate"] = CString(pNetwork->GetFloodRate());
				Tmpl["FloodBurst"] = CString(pNetwork->GetFloodBurst());

				Tmpl["IRCConnectEnabled"] = CString(pNetwork->GetIRCConnectEnabled());

				const vector<CServer*>& vServers = pNetwork->GetServers();
				for (unsigned int a = 0; a < vServers.size(); a++) {
					CTemplate& l = Tmpl.AddRow("ServerLoop");
					l["Server"] = vServers[a]->GetString();
				}

				const vector<CChan*>& Channels = pNetwork->GetChans();
				for (unsigned int c = 0; c < Channels.size(); c++) {
					CChan* pChan = Channels[c];
					CTemplate& l = Tmpl.AddRow("ChannelLoop");

					l["Network"] = pNetwork->GetName();
					l["Username"] = pUser->GetUserName();
					l["Name"] = pChan->GetName();
					l["Perms"] = pChan->GetPermStr();
					l["CurModes"] = pChan->GetModeString();
					l["DefModes"] = pChan->GetDefaultModes();
					l["BufferCount"] = CString(pChan->GetBufferCount());
					l["Options"] = pChan->GetOptions();

					if (pChan->InConfig()) {
						l["InConfig"] = "true";
					}
				}
			} else {
				if (!spSession->IsAdmin() && !pUser->HasSpaceForNewNetwork()) {
					WebSock.PrintErrorPage("Network number limit reached. Ask an admin to increase the limit for you, or delete few old ones from Your Settings");
					return true;
				}

				Tmpl["Action"] = "addnetwork";
				Tmpl["Title"] = "Add Network for User [" + pUser->GetUserName() + "]";
				Tmpl["IRCConnectEnabled"] = "true";
				Tmpl["FloodProtection"] = "true";
				Tmpl["FloodRate"] = "1.0";
				Tmpl["FloodBurst"] = "4";
			}

			FOR_EACH_MODULE(i, make_pair(pUser, pNetwork)) {
				CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop");
				mod.insert(Tmpl.begin(), Tmpl.end());
				mod["WebadminAction"] = "display";
				if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/network", mod)) {
					mod["Embed"] = WebSock.FindTmpl(*i, "WebadminNetwork.tmpl");
					mod["ModName"] = (*i)->GetModName();
				}
			}

			return true;
		}

		CString sName = WebSock.GetParam("network").Trim_n();
		if (sName.empty()) {
			WebSock.PrintErrorPage("Network name is a required argument");
			return true;
		}

		if (!pNetwork) {
			if (!spSession->IsAdmin() && !pUser->HasSpaceForNewNetwork()) {
				WebSock.PrintErrorPage("Network number limit reached. Ask an admin to increase the limit for you, or delete few old ones from Your Settings");
				return true;
			}
			if (!CIRCNetwork::IsValidNetwork(sName)) {
				WebSock.PrintErrorPage("Network name should be alphanumeric");
				return true;
			}
			pNetwork = pUser->AddNetwork(sName);
			if (!pNetwork) {
				WebSock.PrintErrorPage("Network [" + sName.Token(0) + "] already exists");
				return true;
			}
		}

		CString sArg;

		pNetwork->SetNick(WebSock.GetParam("nick"));
		pNetwork->SetAltNick(WebSock.GetParam("altnick"));
		pNetwork->SetIdent(WebSock.GetParam("ident"));
		pNetwork->SetRealName(WebSock.GetParam("realname"));

		pNetwork->SetIRCConnectEnabled(WebSock.GetParam("doconnect").ToBool());

		sArg = WebSock.GetParam("bindhost");
		// To change BindHosts be admin or don't have DenySetBindHost
		if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
			CString sHost = WebSock.GetParam("bindhost");
			const VCString& vsHosts = CZNC::Get().GetBindHosts();
			if (!spSession->IsAdmin() && !vsHosts.empty()) {
				VCString::const_iterator it;
				bool bFound = false;

				for (it = vsHosts.begin(); it != vsHosts.end(); ++it) {
					if (sHost.Equals(*it)) {
						bFound = true;
						break;
					}
				}

				if (!bFound) {
					sHost = pNetwork->GetBindHost();
				}
			}
			pNetwork->SetBindHost(sHost);
		}

		if (WebSock.GetParam("floodprotection").ToBool()) {
			pNetwork->SetFloodRate(WebSock.GetParam("floodrate").ToDouble());
			pNetwork->SetFloodBurst(WebSock.GetParam("floodburst").ToUShort());
		} else {
			pNetwork->SetFloodRate(-1);
		}

		VCString vsArgs;

		pNetwork->DelServers();
		WebSock.GetRawParam("servers").Split("\n", vsArgs);
		for (unsigned int a = 0; a < vsArgs.size(); a++) {
			pNetwork->AddServer(vsArgs[a].Trim_n());
		}

		WebSock.GetParamValues("channel", vsArgs);
		for (unsigned int a = 0; a < vsArgs.size(); a++) {
			const CString& sChan = vsArgs[a];
			CChan *pChan = pNetwork->FindChan(sChan.TrimRight_n("\r"));
			if (pChan) {
				pChan->SetInConfig(WebSock.GetParam("save_" + sChan).ToBool());
			}
		}

		set<CString> ssArgs;
		WebSock.GetParamValues("loadmod", ssArgs);
		if (spSession->IsAdmin() || !pUser->DenyLoadMod()) {
			for (set<CString>::iterator it = ssArgs.begin(); it != ssArgs.end(); ++it) {
				CString sModRet;
				CString sModName = (*it).TrimRight_n("\r");
				CString sModLoadError;

				if (!sModName.empty()) {
					CString sArgs = WebSock.GetParam("modargs_" + sModName);

					CModule *pMod = pNetwork->GetModules().FindModule(sModName);

					if (!pMod) {
						if (!pNetwork->GetModules().LoadModule(sModName, sArgs, CModInfo::NetworkModule, pUser, pNetwork, sModRet)) {
							sModLoadError = "Unable to load module [" + sModName + "] [" + sModRet + "]";
						}
					} else if (pMod->GetArgs() != sArgs) {
						if (!pNetwork->GetModules().ReloadModule(sModName, sArgs, pUser, pNetwork, sModRet)) {
							sModLoadError = "Unable to reload module [" + sModName + "] [" + sModRet + "]";
						}
					}

					if (!sModLoadError.empty()) {
						DEBUG(sModLoadError);
						WebSock.GetSession()->AddError(sModLoadError);
					}
				}
			}
		}

		const CModules& vCurMods = pNetwork->GetModules();
		set<CString> ssUnloadMods;

		for (unsigned int a = 0; a < vCurMods.size(); a++) {
			CModule* pCurMod = vCurMods[a];

			if (ssArgs.find(pCurMod->GetModName()) == ssArgs.end() && pCurMod->GetModName() != GetModName()) {
				ssUnloadMods.insert(pCurMod->GetModName());
			}
		}

		for (set<CString>::iterator it2 = ssUnloadMods.begin(); it2 != ssUnloadMods.end(); ++it2) {
			pNetwork->GetModules().UnloadModule(*it2);
		}

		CTemplate TmplMod;
		TmplMod["Username"] = pUser->GetUserName();
		TmplMod["Name"] = pNetwork->GetName();
		TmplMod["WebadminAction"] = "change";
		FOR_EACH_MODULE(it, make_pair(pUser, pNetwork)) {
			(*it)->OnEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod);
		}

		if (!CZNC::Get().WriteConfig()) {
			WebSock.PrintErrorPage("Network added/modified, but config was not written");
			return true;
		}

		WebSock.Redirect("edituser?user=" + pUser->GetUserName().Escape_n(CString::EURL));
		return true;
	}


alloc_sglist(int nents, int max, int vary, struct usbtest_dev *dev, int pipe)
{
	struct scatterlist	*sg;
	unsigned int		n_size = 0;
	unsigned		i;
	unsigned		size = max;
	unsigned		maxpacket =
		get_maxpacket(interface_to_usbdev(dev->intf), pipe);

	if (max == 0)
		return NULL;

	sg = kmalloc_array(nents, sizeof(*sg), GFP_KERNEL);
	if (!sg)
		return NULL;
	sg_init_table(sg, nents);

	for (i = 0; i < nents; i++) {
		char		*buf;
		unsigned	j;

		buf = kzalloc(size, GFP_KERNEL);
		if (!buf) {
			free_sglist(sg, i);
			return NULL;
		}

		/* kmalloc pages are always physically contiguous! */
		sg_set_buf(&sg[i], buf, size);

		switch (pattern) {
		case 0:
			/* already zeroed */
			break;
		case 1:
			for (j = 0; j < size; j++)
				*buf++ = (u8) (((j + n_size) % maxpacket) % 63);
			n_size += size;
			break;
		}

		if (vary) {
			size += vary;
			size %= max;
			if (size == 0)
				size = (vary < max) ? vary : max;
		}
	}

	return sg;
}

usb: usbtest: fix missing kfree(dev->buf) in usbtest_disconnect

BUG: memory leak
unreferenced object 0xffff888055046e00 (size 256):
  comm "kworker/2:9", pid 2570, jiffies 4294942129 (age 1095.500s)
  hex dump (first 32 bytes):
    00 70 04 55 80 88 ff ff 18 bb 5a 81 ff ff ff ff  .p.U......Z.....
    f5 96 78 81 ff ff ff ff 37 de 8e 81 ff ff ff ff  ..x.....7.......
  backtrace:
    [<00000000d121dccf>] kmemleak_alloc_recursive
include/linux/kmemleak.h:43 [inline]
    [<00000000d121dccf>] slab_post_alloc_hook mm/slab.h:586 [inline]
    [<00000000d121dccf>] slab_alloc_node mm/slub.c:2786 [inline]
    [<00000000d121dccf>] slab_alloc mm/slub.c:2794 [inline]
    [<00000000d121dccf>] kmem_cache_alloc_trace+0x15e/0x2d0 mm/slub.c:2811
    [<000000005c3c3381>] kmalloc include/linux/slab.h:555 [inline]
    [<000000005c3c3381>] usbtest_probe+0x286/0x19d0
drivers/usb/misc/usbtest.c:2790
    [<000000001cec6910>] usb_probe_interface+0x2bd/0x870
drivers/usb/core/driver.c:361
    [<000000007806c118>] really_probe+0x48d/0x8f0 drivers/base/dd.c:551
    [<00000000a3308c3e>] driver_probe_device+0xfc/0x2a0 drivers/base/dd.c:724
    [<000000003ef66004>] __device_attach_driver+0x1b6/0x240
drivers/base/dd.c:831
    [<00000000eee53e97>] bus_for_each_drv+0x14e/0x1e0 drivers/base/bus.c:431
    [<00000000bb0648d0>] __device_attach+0x1f9/0x350 drivers/base/dd.c:897
    [<00000000838b324a>] device_initial_probe+0x1a/0x20 drivers/base/dd.c:944
    [<0000000030d501c1>] bus_probe_device+0x1e1/0x280 drivers/base/bus.c:491
    [<000000005bd7adef>] device_add+0x131d/0x1c40 drivers/base/core.c:2504
    [<00000000a0937814>] usb_set_configuration+0xe84/0x1ab0
drivers/usb/core/message.c:2030
    [<00000000e3934741>] generic_probe+0x6a/0xe0 drivers/usb/core/generic.c:210
    [<0000000098ade0f1>] usb_probe_device+0x90/0xd0
drivers/usb/core/driver.c:266
    [<000000007806c118>] really_probe+0x48d/0x8f0 drivers/base/dd.c:551
    [<00000000a3308c3e>] driver_probe_device+0xfc/0x2a0 drivers/base/dd.c:724

Acked-by: Alan Stern <stern@rowland.harvard.edu>
Reported-by: Kyungtae Kim <kt0755@gmail.com>
Signed-off-by: Zqiang <qiang.zhang@windriver.com>
Link: https://lore.kernel.org/r/20200612035210.20494-1-qiang.zhang@windriver.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>


usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
{

	struct usbtest_dev	*dev = usb_get_intfdata(intf);
	struct usbtest_param_64 *param_64 = buf;
	struct usbtest_param_32 temp;
	struct usbtest_param_32 *param_32 = buf;
	struct timespec64 start;
	struct timespec64 end;
	struct timespec64 duration;
	int retval = -EOPNOTSUPP;

	/* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */

	pattern = mod_pattern;

	if (mutex_lock_interruptible(&dev->lock))
		return -ERESTARTSYS;

	/* FIXME: What if a system sleep starts while a test is running? */

	/* some devices, like ez-usb default devices, need a non-default
	 * altsetting to have any active endpoints.  some tests change
	 * altsettings; force a default so most tests don't need to check.
	 */
	if (dev->info->alt >= 0) {
		if (intf->altsetting->desc.bInterfaceNumber) {
			retval = -ENODEV;
			goto free_mutex;
		}
		retval = set_altsetting(dev, dev->info->alt);
		if (retval) {
			dev_err(&intf->dev,
					"set altsetting to %d failed, %d\n",
					dev->info->alt, retval);
			goto free_mutex;
		}
	}

	switch (code) {
	case USBTEST_REQUEST_64:
		temp.test_num = param_64->test_num;
		temp.iterations = param_64->iterations;
		temp.length = param_64->length;
		temp.sglen = param_64->sglen;
		temp.vary = param_64->vary;
		param_32 = &temp;
		break;

	case USBTEST_REQUEST_32:
		break;

	default:
		retval = -EOPNOTSUPP;
		goto free_mutex;
	}

	ktime_get_ts64(&start);

	retval = usbtest_do_ioctl(intf, param_32);
	if (retval < 0)
		goto free_mutex;

	ktime_get_ts64(&end);

	duration = timespec64_sub(end, start);

	temp.duration_sec = duration.tv_sec;
	temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC;

	switch (code) {
	case USBTEST_REQUEST_32:
		param_32->duration_sec = temp.duration_sec;
		param_32->duration_usec = temp.duration_usec;
		break;

	case USBTEST_REQUEST_64:
		param_64->duration_sec = temp.duration_sec;
		param_64->duration_usec = temp.duration_usec;
		break;
	}

free_mutex:
	mutex_unlock(&dev->lock);
	return retval;
}


static int toggle_sync_simple(struct usbtest_dev *dev)
{
	int			ep;
	int			retval = 0;
	struct urb		*urb;
	struct usb_device	*udev = testdev_to_usbdev(dev);
	unsigned		maxp = get_maxpacket(udev, dev->out_pipe);

	/*
	 * Create a URB that causes a transfer of uneven amount of data packets
	 * This way the clear toggle has an impact on the data toggle sequence.
	 * Use 2 maxpacket length packets and one zero packet.
	 */
	urb = simple_alloc_urb(udev, 0,  2 * maxp, 0);
	if (urb == NULL)
		return -ENOMEM;

	urb->transfer_flags |= URB_ZERO_PACKET;

	ep = usb_pipeendpoint(dev->out_pipe);
	urb->pipe = dev->out_pipe;
	retval = test_toggle_sync(dev, ep, urb);

	simple_free_urb(urb);
	return retval;
}

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

#define	INTERRUPT_RATE		1	/* msec/transfer */

#define ERROR(tdev, fmt, args...) \
	dev_err(&(tdev)->intf->dev , fmt , ## args)

#define WARNING(tdev, fmt, args...) \
	dev_warn(&(tdev)->intf->dev , fmt , ## args)

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};


static int __init usbtest_init(void)
{
#ifdef GENERIC
	if (vendor)
		pr_debug("params: vend=0x%04x prod=0x%04x\n", vendor, product);
#endif
	return usb_register(&usbtest_driver);
}


static int simple_io(
	struct usbtest_dev	*tdev,
	struct urb		*urb,
	int			iterations,
	int			vary,
	int			expected,
	const char		*label
)
{
	struct usb_device	*udev = urb->dev;
	int			max = urb->transfer_buffer_length;
	struct completion	completion;
	int			retval = 0;
	unsigned long		expire;

	urb->context = &completion;
	while (retval == 0 && iterations-- > 0) {
		init_completion(&completion);
		if (usb_pipeout(urb->pipe)) {
			simple_fill_buf(urb);
			urb->transfer_flags |= URB_ZERO_PACKET;
		}
		retval = usb_submit_urb(urb, GFP_KERNEL);
		if (retval != 0)
			break;

		expire = msecs_to_jiffies(SIMPLE_IO_TIMEOUT);
		if (!wait_for_completion_timeout(&completion, expire)) {
			usb_kill_urb(urb);
			retval = (urb->status == -ENOENT ?
				  -ETIMEDOUT : urb->status);
		} else {
			retval = urb->status;
		}

		urb->dev = udev;
		if (retval == 0 && usb_pipein(urb->pipe))
			retval = simple_check_buf(tdev, urb);

		if (vary) {
			int	len = urb->transfer_buffer_length;

			len += vary;
			len %= max;
			if (len == 0)
				len = (vary < max) ? vary : max;
			urb->transfer_buffer_length = len;
		}

		/* FIXME if endpoint halted, clear halt (and log) */
	}
	urb->transfer_buffer_length = max;

	if (expected != retval)
		dev_err(&udev->dev,
			"%s failed, iterations left %d, status %d (not %d)\n",
				label, iterations, retval, expected);
	return retval;
}


static void sg_timeout(struct timer_list *t)
{
	struct sg_timeout *timeout = from_timer(timeout, t, timer);

	usb_sg_cancel(timeout->req);
}

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};

static struct usbtest_info ez2_info = {
	.name		= "FX2 device",
	.ep_in		= 6,
	.ep_out		= 2,
	.alt		= 1,
};


static struct usb_device *testdev_to_usbdev(struct usbtest_dev *test)
{
	return interface_to_usbdev(test->intf);
}


static void simple_fill_buf(struct urb *urb)
{
	unsigned	i;
	u8		*buf = urb->transfer_buffer;
	unsigned	len = urb->transfer_buffer_length;
	unsigned	maxpacket;

	switch (pattern) {
	default:
		/* FALLTHROUGH */
	case 0:
		memset(buf, 0, len);
		break;
	case 1:			/* mod63 */
		maxpacket = get_maxpacket(urb->dev, urb->pipe);
		for (i = 0; i < len; i++)
			*buf++ = (u8) ((i % maxpacket) % 63);
		break;
	}
}


static int simple_check_buf(struct usbtest_dev *tdev, struct urb *urb)
{
	unsigned	i;
	u8		expected;
	u8		*buf = urb->transfer_buffer;
	unsigned	len = urb->actual_length;
	unsigned	maxpacket = get_maxpacket(urb->dev, urb->pipe);

	int ret = check_guard_bytes(tdev, urb);
	if (ret)
		return ret;

	for (i = 0; i < len; i++, buf++) {
		switch (pattern) {
		/* all-zeroes has no synchronization issues */
		case 0:
			expected = 0;
			break;
		/* mod63 stays in sync with short-terminated transfers,
		 * or otherwise when host and gadget agree on how large
		 * each usb transfer request should be.  resync is done
		 * with set_interface or set_config.
		 */
		case 1:			/* mod63 */
			expected = (i % maxpacket) % 63;
			break;
		/* always fail unsupported patterns */
		default:
			expected = !*buf;
			break;
		}
		if (*buf == expected)
			continue;
		ERROR(tdev, "buf[%d] = %d (not %d)\n", i, *buf, expected);
		return -EINVAL;
	}
	return 0;
}


static int is_good_con_id(struct usbtest_dev *tdev, u8 *buf)
{
	struct usb_ss_container_id_descriptor *con_id;

	con_id = (struct usb_ss_container_id_descriptor *) buf;

	if (con_id->bLength != USB_DT_USB_SS_CONTN_ID_SIZE) {
		ERROR(tdev, "bogus container id descriptor length\n");
		return 0;
	}

	if (con_id->bReserved) {	/* reserved == 0 */
		ERROR(tdev, "reserved bits set\n");
		return 0;
	}

	return 1;
}


static void simple_free_urb(struct urb *urb)
{
	unsigned long offset = buffer_offset(urb->transfer_buffer);

	if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
		usb_free_coherent(
			urb->dev,
			urb->transfer_buffer_length + offset,
			urb->transfer_buffer - offset,
			urb->transfer_dma - offset);
	else
		kfree(urb->transfer_buffer - offset);
	usb_free_urb(urb);
}


static int halt_simple(struct usbtest_dev *dev)
{
	int			ep;
	int			retval = 0;
	struct urb		*urb;
	struct usb_device	*udev = testdev_to_usbdev(dev);

	if (udev->speed == USB_SPEED_SUPER)
		urb = simple_alloc_urb(udev, 0, 1024, 0);
	else
		urb = simple_alloc_urb(udev, 0, 512, 0);
	if (urb == NULL)
		return -ENOMEM;

	if (dev->in_pipe) {
		ep = usb_pipeendpoint(dev->in_pipe) | USB_DIR_IN;
		urb->pipe = dev->in_pipe;
		retval = test_halt(dev, ep, urb);
		if (retval < 0)
			goto done;
	}

	if (dev->out_pipe) {
		ep = usb_pipeendpoint(dev->out_pipe);
		urb->pipe = dev->out_pipe;
		retval = test_halt(dev, ep, urb);
	}
done:
	simple_free_urb(urb);
	return retval;
}


static int usbtest_resume(struct usb_interface *intf)
{
	return 0;
}


static void unlink_queued_callback(struct urb *urb)
{
	int			status = urb->status;
	struct queued_ctx	*ctx = urb->context;

	if (ctx->status)
		goto done;
	if (urb == ctx->urbs[ctx->num - 4] || urb == ctx->urbs[ctx->num - 2]) {
		if (status == -ECONNRESET)
			goto done;
		/* What error should we report if the URB completed normally? */
	}
	if (status != 0)
		ctx->status = status;

 done:
	if (atomic_dec_and_test(&ctx->pending))
		complete(&ctx->complete);
}


static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async)
{
	struct urb		*urb;
	struct completion	completion;
	int			retval = 0;

	init_completion(&completion);
	urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size, 0);
	if (!urb)
		return -ENOMEM;
	urb->context = &completion;
	urb->complete = unlink1_callback;

	if (usb_pipeout(urb->pipe)) {
		simple_fill_buf(urb);
		urb->transfer_flags |= URB_ZERO_PACKET;
	}

	/* keep the endpoint busy.  there are lots of hc/hcd-internal
	 * states, and testing should get to all of them over time.
	 *
	 * FIXME want additional tests for when endpoint is STALLing
	 * due to errors, or is just NAKing requests.
	 */
	retval = usb_submit_urb(urb, GFP_KERNEL);
	if (retval != 0) {
		dev_err(&dev->intf->dev, "submit fail %d\n", retval);
		return retval;
	}

	/* unlinking that should always work.  variable delay tests more
	 * hcd states and code paths, even with little other system load.
	 */
	msleep(jiffies % (2 * INTERRUPT_RATE));
	if (async) {
		while (!completion_done(&completion)) {
			retval = usb_unlink_urb(urb);

			if (retval == 0 && usb_pipein(urb->pipe))
				retval = simple_check_buf(dev, urb);

			switch (retval) {
			case -EBUSY:
			case -EIDRM:
				/* we can't unlink urbs while they're completing
				 * or if they've completed, and we haven't
				 * resubmitted. "normal" drivers would prevent
				 * resubmission, but since we're testing unlink
				 * paths, we can't.
				 */
				ERROR(dev, "unlink retry\n");
				continue;
			case 0:
			case -EINPROGRESS:
				break;

			default:
				dev_err(&dev->intf->dev,
					"unlink fail %d\n", retval);
				return retval;
			}

			break;
		}
	} else
		usb_kill_urb(urb);

	wait_for_completion(&completion);
	retval = urb->status;
	simple_free_urb(urb);

	if (async)
		return (retval == -ECONNRESET) ? 0 : retval - 1000;
	else
		return (retval == -ENOENT || retval == -EPERM) ?
				0 : retval - 2000;
}


static inline void endpoint_update(int edi,
				   struct usb_host_endpoint **in,
				   struct usb_host_endpoint **out,
				   struct usb_host_endpoint *e)
{
	if (edi) {
		if (!*in)
			*in = e;
	} else {
		if (!*out)
			*out = e;
	}
}


static int check_guard_bytes(struct usbtest_dev *tdev, struct urb *urb)
{
	u8 *buf = urb->transfer_buffer;
	u8 *guard = buf - buffer_offset(buf);
	unsigned i;

	for (i = 0; guard < buf; i++, guard++) {
		if (*guard != GUARD_BYTE) {
			ERROR(tdev, "guard byte[%d] %d (not %d)\n",
				i, *guard, GUARD_BYTE);
			return -EINVAL;
		}
	}
	return 0;
}


static int ch9_postconfig(struct usbtest_dev *dev)
{
	struct usb_interface	*iface = dev->intf;
	struct usb_device	*udev = interface_to_usbdev(iface);
	int			i, alt, retval;

	/* [9.2.3] if there's more than one altsetting, we need to be able to
	 * set and get each one.  mostly trusts the descriptors from usbcore.
	 */
	for (i = 0; i < iface->num_altsetting; i++) {

		/* 9.2.3 constrains the range here */
		alt = iface->altsetting[i].desc.bAlternateSetting;
		if (alt < 0 || alt >= iface->num_altsetting) {
			dev_err(&iface->dev,
					"invalid alt [%d].bAltSetting = %d\n",
					i, alt);
		}

		/* [real world] get/set unimplemented if there's only one */
		if (realworld && iface->num_altsetting == 1)
			continue;

		/* [9.4.10] set_interface */
		retval = set_altsetting(dev, alt);
		if (retval) {
			dev_err(&iface->dev, "can't set_interface = %d, %d\n",
					alt, retval);
			return retval;
		}

		/* [9.4.4] get_interface always works */
		retval = get_altsetting(dev);
		if (retval != alt) {
			dev_err(&iface->dev, "get alt should be %d, was %d\n",
					alt, retval);
			return (retval < 0) ? retval : -EDOM;
		}

	}

	/* [real world] get_config unimplemented if there's only one */
	if (!realworld || udev->descriptor.bNumConfigurations != 1) {
		int	expected = udev->actconfig->desc.bConfigurationValue;

		/* [9.4.2] get_configuration always works
		 * ... although some cheap devices (like one TI Hub I've got)
		 * won't return config descriptors except before set_config.
		 */
		retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
				USB_REQ_GET_CONFIGURATION,
				USB_DIR_IN | USB_RECIP_DEVICE,
				0, 0, dev->buf, 1, USB_CTRL_GET_TIMEOUT);
		if (retval != 1 || dev->buf[0] != expected) {
			dev_err(&iface->dev, "get config --> %d %d (1 %d)\n",
				retval, dev->buf[0], expected);
			return (retval < 0) ? retval : -EDOM;
		}
	}

	/* there's always [9.4.3] a device descriptor [9.6.1] */
	retval = usb_get_descriptor(udev, USB_DT_DEVICE, 0,
			dev->buf, sizeof(udev->descriptor));
	if (retval != sizeof(udev->descriptor)) {
		dev_err(&iface->dev, "dev descriptor --> %d\n", retval);
		return (retval < 0) ? retval : -EDOM;
	}

	/*
	 * there's always [9.4.3] a bos device descriptor [9.6.2] in USB
	 * 3.0 spec
	 */
	if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0210) {
		struct usb_bos_descriptor *bos = NULL;
		struct usb_dev_cap_header *header = NULL;
		unsigned total, num, length;
		u8 *buf;

		retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf,
				sizeof(*udev->bos->desc));
		if (retval != sizeof(*udev->bos->desc)) {
			dev_err(&iface->dev, "bos descriptor --> %d\n", retval);
			return (retval < 0) ? retval : -EDOM;
		}

		bos = (struct usb_bos_descriptor *)dev->buf;
		total = le16_to_cpu(bos->wTotalLength);
		num = bos->bNumDeviceCaps;

		if (total > TBUF_SIZE)
			total = TBUF_SIZE;

		/*
		 * get generic device-level capability descriptors [9.6.2]
		 * in USB 3.0 spec
		 */
		retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf,
				total);
		if (retval != total) {
			dev_err(&iface->dev, "bos descriptor set --> %d\n",
					retval);
			return (retval < 0) ? retval : -EDOM;
		}

		length = sizeof(*udev->bos->desc);
		buf = dev->buf;
		for (i = 0; i < num; i++) {
			buf += length;
			if (buf + sizeof(struct usb_dev_cap_header) >
					dev->buf + total)
				break;

			header = (struct usb_dev_cap_header *)buf;
			length = header->bLength;

			if (header->bDescriptorType !=
					USB_DT_DEVICE_CAPABILITY) {
				dev_warn(&udev->dev, "not device capability descriptor, skip\n");
				continue;
			}

			switch (header->bDevCapabilityType) {
			case USB_CAP_TYPE_EXT:
				if (buf + USB_DT_USB_EXT_CAP_SIZE >
						dev->buf + total ||
						!is_good_ext(dev, buf)) {
					dev_err(&iface->dev, "bogus usb 2.0 extension descriptor\n");
					return -EDOM;
				}
				break;
			case USB_SS_CAP_TYPE:
				if (buf + USB_DT_USB_SS_CAP_SIZE >
						dev->buf + total ||
						!is_good_ss_cap(dev, buf)) {
					dev_err(&iface->dev, "bogus superspeed device capability descriptor\n");
					return -EDOM;
				}
				break;
			case CONTAINER_ID_TYPE:
				if (buf + USB_DT_USB_SS_CONTN_ID_SIZE >
						dev->buf + total ||
						!is_good_con_id(dev, buf)) {
					dev_err(&iface->dev, "bogus container id descriptor\n");
					return -EDOM;
				}
				break;
			default:
				break;
			}
		}
	}

	/* there's always [9.4.3] at least one config descriptor [9.6.3] */
	for (i = 0; i < udev->descriptor.bNumConfigurations; i++) {
		retval = usb_get_descriptor(udev, USB_DT_CONFIG, i,
				dev->buf, TBUF_SIZE);
		if (!is_good_config(dev, retval)) {
			dev_err(&iface->dev,
					"config [%d] descriptor --> %d\n",
					i, retval);
			return (retval < 0) ? retval : -EDOM;
		}

		/* FIXME cross-checking udev->config[i] to make sure usbcore
		 * parsed it right (etc) would be good testing paranoia
		 */
	}

	/* and sometimes [9.2.6.6] speed dependent descriptors */
	if (le16_to_cpu(udev->descriptor.bcdUSB) == 0x0200) {
		struct usb_qualifier_descriptor *d = NULL;

		/* device qualifier [9.6.2] */
		retval = usb_get_descriptor(udev,
				USB_DT_DEVICE_QUALIFIER, 0, dev->buf,
				sizeof(struct usb_qualifier_descriptor));
		if (retval == -EPIPE) {
			if (udev->speed == USB_SPEED_HIGH) {
				dev_err(&iface->dev,
						"hs dev qualifier --> %d\n",
						retval);
				return retval;
			}
			/* usb2.0 but not high-speed capable; fine */
		} else if (retval != sizeof(struct usb_qualifier_descriptor)) {
			dev_err(&iface->dev, "dev qualifier --> %d\n", retval);
			return (retval < 0) ? retval : -EDOM;
		} else
			d = (struct usb_qualifier_descriptor *) dev->buf;

		/* might not have [9.6.2] any other-speed configs [9.6.4] */
		if (d) {
			unsigned max = d->bNumConfigurations;
			for (i = 0; i < max; i++) {
				retval = usb_get_descriptor(udev,
					USB_DT_OTHER_SPEED_CONFIG, i,
					dev->buf, TBUF_SIZE);
				if (!is_good_config(dev, retval)) {
					dev_err(&iface->dev,
						"other speed config --> %d\n",
						retval);
					return (retval < 0) ? retval : -EDOM;
				}
			}
		}
	}
	/* FIXME fetch strings from at least the device descriptor */

	/* [9.4.5] get_status always works */
	retval = usb_get_std_status(udev, USB_RECIP_DEVICE, 0, dev->buf);
	if (retval) {
		dev_err(&iface->dev, "get dev status --> %d\n", retval);
		return retval;
	}

	/* FIXME configuration.bmAttributes says if we could try to set/clear
	 * the device's remote wakeup feature ... if we can, test that here
	 */

	retval = usb_get_std_status(udev, USB_RECIP_INTERFACE,
			iface->altsetting[0].desc.bInterfaceNumber, dev->buf);
	if (retval) {
		dev_err(&iface->dev, "get interface status --> %d\n", retval);
		return retval;
	}
	/* FIXME get status for each endpoint in the interface */

	return 0;
}

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

#define ERROR(tdev, fmt, args...) \
	dev_err(&(tdev)->intf->dev , fmt , ## args)

#define WARNING(tdev, fmt, args...) \
	dev_warn(&(tdev)->intf->dev , fmt , ## args)

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

#define NUM_SUBCASES	16		/* how many test subcases here? */

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};


static inline unsigned long buffer_offset(void *buf)
{
	return (unsigned long)buf & (ARCH_KMALLOC_MINALIGN - 1);
}


static int ss_isoc_get_packet_num(struct usb_device *udev, int pipe)
{
	struct usb_host_endpoint *ep = usb_pipe_endpoint(udev, pipe);

	return USB_SS_MULT(ep->ss_ep_comp.bmAttributes)
		* (1 + ep->ss_ep_comp.bMaxBurst);
}


static int set_altsetting(struct usbtest_dev *dev, int alternate)
{
	struct usb_interface		*iface = dev->intf;
	struct usb_device		*udev;

	if (alternate < 0 || alternate >= 256)
		return -EINVAL;

	udev = interface_to_usbdev(iface);
	return usb_set_interface(udev,
			iface->altsetting[0].desc.bInterfaceNumber,
			alternate);
}


static void ctrl_complete(struct urb *urb)
{
	struct ctrl_ctx		*ctx = urb->context;
	struct usb_ctrlrequest	*reqp;
	struct subcase		*subcase;
	int			status = urb->status;
	unsigned long		flags;

	reqp = (struct usb_ctrlrequest *)urb->setup_packet;
	subcase = container_of(reqp, struct subcase, setup);

	spin_lock_irqsave(&ctx->lock, flags);
	ctx->count--;
	ctx->pending--;

	/* queue must transfer and complete in fifo order, unless
	 * usb_unlink_urb() is used to unlink something not at the
	 * physical queue head (not tested).
	 */
	if (subcase->number > 0) {
		if ((subcase->number - ctx->last) != 1) {
			ERROR(ctx->dev,
				"subcase %d completed out of order, last %d\n",
				subcase->number, ctx->last);
			status = -EDOM;
			ctx->last = subcase->number;
			goto error;
		}
	}
	ctx->last = subcase->number;

	/* succeed or fault in only one way? */
	if (status == subcase->expected)
		status = 0;

	/* async unlink for cleanup? */
	else if (status != -ECONNRESET) {

		/* some faults are allowed, not required */
		if (subcase->expected > 0 && (
			  ((status == -subcase->expected	/* happened */
			   || status == 0))))			/* didn't */
			status = 0;
		/* sometimes more than one fault is allowed */
		else if (subcase->number == 12 && status == -EPIPE)
			status = 0;
		else
			ERROR(ctx->dev, "subtest %d error, status %d\n",
					subcase->number, status);
	}

	/* unexpected status codes mean errors; ideally, in hardware */
	if (status) {
error:
		if (ctx->status == 0) {
			int		i;

			ctx->status = status;
			ERROR(ctx->dev, "control queue %02x.%02x, err %d, "
					"%d left, subcase %d, len %d/%d\n",
					reqp->bRequestType, reqp->bRequest,
					status, ctx->count, subcase->number,
					urb->actual_length,
					urb->transfer_buffer_length);

			/* FIXME this "unlink everything" exit route should
			 * be a separate test case.
			 */

			/* unlink whatever's still pending */
			for (i = 1; i < ctx->param->sglen; i++) {
				struct urb *u = ctx->urb[
							(i + subcase->number)
							% ctx->param->sglen];

				if (u == urb || !u->dev)
					continue;
				spin_unlock(&ctx->lock);
				status = usb_unlink_urb(u);
				spin_lock(&ctx->lock);
				switch (status) {
				case -EINPROGRESS:
				case -EBUSY:
				case -EIDRM:
					continue;
				default:
					ERROR(ctx->dev, "urb unlink --> %d\n",
							status);
				}
			}
			status = ctx->status;
		}
	}

	/* resubmit if we need to, else mark this as done */
	if ((status == 0) && (ctx->pending < ctx->count)) {
		status = usb_submit_urb(urb, GFP_ATOMIC);
		if (status != 0) {
			ERROR(ctx->dev,
				"can't resubmit ctrl %02x.%02x, err %d\n",
				reqp->bRequestType, reqp->bRequest, status);
			urb->dev = NULL;
		} else
			ctx->pending++;
	} else
		urb->dev = NULL;

	/* signal completion when nothing's queued */
	if (ctx->pending == 0)
		complete(&ctx->complete);
	spin_unlock_irqrestore(&ctx->lock, flags);
}

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

#define	INTERRUPT_RATE		1	/* msec/transfer */

#define ERROR(tdev, fmt, args...) \
	dev_err(&(tdev)->intf->dev , fmt , ## args)

#define WARNING(tdev, fmt, args...) \
	dev_warn(&(tdev)->intf->dev , fmt , ## args)

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

#define NUM_SUBCASES	16		/* how many test subcases here? */

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};


static int test_unaligned_bulk(
	struct usbtest_dev *tdev,
	int pipe,
	unsigned length,
	int iterations,
	unsigned transfer_flags,
	const char *label)
{
	int retval;
	struct urb *urb = usbtest_alloc_urb(testdev_to_usbdev(tdev),
			pipe, length, transfer_flags, 1, 0, simple_callback);

	if (!urb)
		return -ENOMEM;

	retval = simple_io(tdev, urb, iterations, 0, 0, label);
	simple_free_urb(urb);
	return retval;
}


static void complicated_callback(struct urb *urb)
{
	struct transfer_context	*ctx = urb->context;
	unsigned long flags;

	spin_lock_irqsave(&ctx->lock, flags);
	ctx->count--;

	ctx->packet_count += urb->number_of_packets;
	if (urb->error_count > 0)
		ctx->errors += urb->error_count;
	else if (urb->status != 0)
		ctx->errors += (ctx->is_iso ? urb->number_of_packets : 1);
	else if (urb->actual_length != urb->transfer_buffer_length)
		ctx->errors++;
	else if (check_guard_bytes(ctx->dev, urb) != 0)
		ctx->errors++;

	if (urb->status == 0 && ctx->count > (ctx->pending - 1)
			&& !ctx->submit_error) {
		int status = usb_submit_urb(urb, GFP_ATOMIC);
		switch (status) {
		case 0:
			goto done;
		default:
			dev_err(&ctx->dev->intf->dev,
					"resubmit err %d\n",
					status);
			/* FALLTHROUGH */
		case -ENODEV:			/* disconnected */
		case -ESHUTDOWN:		/* endpoint disabled */
			ctx->submit_error = 1;
			break;
		}
	}

	ctx->pending--;
	if (ctx->pending == 0) {
		if (ctx->errors)
			dev_err(&ctx->dev->intf->dev,
				"during the test, %lu errors out of %lu\n",
				ctx->errors, ctx->packet_count);
		complete(&ctx->done);
	}
done:
	spin_unlock_irqrestore(&ctx->lock, flags);
}


static void simple_callback(struct urb *urb)
{
	complete(urb->context);
}


static int perform_sglist(
	struct usbtest_dev	*tdev,
	unsigned		iterations,
	int			pipe,
	struct usb_sg_request	*req,
	struct scatterlist	*sg,
	int			nents
)
{
	struct usb_device	*udev = testdev_to_usbdev(tdev);
	int			retval = 0;
	struct sg_timeout	timeout = {
		.req = req,
	};

	timer_setup_on_stack(&timeout.timer, sg_timeout, 0);

	while (retval == 0 && iterations-- > 0) {
		retval = usb_sg_init(req, udev, pipe,
				(udev->speed == USB_SPEED_HIGH)
					? (INTERRUPT_RATE << 3)
					: INTERRUPT_RATE,
				sg, nents, 0, GFP_KERNEL);

		if (retval)
			break;
		mod_timer(&timeout.timer, jiffies +
				msecs_to_jiffies(SIMPLE_IO_TIMEOUT));
		usb_sg_wait(req);
		if (!del_timer_sync(&timeout.timer))
			retval = -ETIMEDOUT;
		else
			retval = req->status;
		destroy_timer_on_stack(&timeout.timer);

		/* FIXME check resulting data pattern */

		/* FIXME if endpoint halted, clear halt (and log) */
	}

	/* FIXME for unlink or fault handling tests, don't report
	 * failure if retval is as we expected ...
	 */
	if (retval)
		ERROR(tdev, "perform_sglist failed, "
				"iterations left %d, status %d\n",
				iterations, retval);
	return retval;
}

#define SIMPLE_IO_TIMEOUT	10000	/* in milliseconds */

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

#define	INTERRUPT_RATE		1	/* msec/transfer */

#define ERROR(tdev, fmt, args...) \
	dev_err(&(tdev)->intf->dev , fmt , ## args)

#define WARNING(tdev, fmt, args...) \
	dev_warn(&(tdev)->intf->dev , fmt , ## args)

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};


static void __exit usbtest_exit(void)
{
	usb_deregister(&usbtest_driver);
}


usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device	*udev;
	struct usbtest_dev	*dev;
	struct usbtest_info	*info;
	char			*rtest, *wtest;
	char			*irtest, *iwtest;
	char			*intrtest, *intwtest;

	udev = interface_to_usbdev(intf);

#ifdef	GENERIC
	/* specify devices by module parameters? */
	if (id->match_flags == 0) {
		/* vendor match required, product match optional */
		if (!vendor || le16_to_cpu(udev->descriptor.idVendor) != (u16)vendor)
			return -ENODEV;
		if (product && le16_to_cpu(udev->descriptor.idProduct) != (u16)product)
			return -ENODEV;
		dev_info(&intf->dev, "matched module params, "
					"vend=0x%04x prod=0x%04x\n",
				le16_to_cpu(udev->descriptor.idVendor),
				le16_to_cpu(udev->descriptor.idProduct));
	}
#endif

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;
	info = (struct usbtest_info *) id->driver_info;
	dev->info = info;
	mutex_init(&dev->lock);

	dev->intf = intf;

	/* cacheline-aligned scratch for i/o */
	dev->buf = kmalloc(TBUF_SIZE, GFP_KERNEL);
	if (dev->buf == NULL) {
		kfree(dev);
		return -ENOMEM;
	}

	/* NOTE this doesn't yet test the handful of difference that are
	 * visible with high speed interrupts:  bigger maxpacket (1K) and
	 * "high bandwidth" modes (up to 3 packets/uframe).
	 */
	rtest = wtest = "";
	irtest = iwtest = "";
	intrtest = intwtest = "";
	if (force_interrupt || udev->speed == USB_SPEED_LOW) {
		if (info->ep_in) {
			dev->in_pipe = usb_rcvintpipe(udev, info->ep_in);
			rtest = " intr-in";
		}
		if (info->ep_out) {
			dev->out_pipe = usb_sndintpipe(udev, info->ep_out);
			wtest = " intr-out";
		}
	} else {
		if (override_alt >= 0 || info->autoconf) {
			int status;

			status = get_endpoints(dev, intf);
			if (status < 0) {
				WARNING(dev, "couldn't get endpoints, %d\n",
						status);
				kfree(dev->buf);
				kfree(dev);
				return status;
			}
			/* may find bulk or ISO pipes */
		} else {
			if (info->ep_in)
				dev->in_pipe = usb_rcvbulkpipe(udev,
							info->ep_in);
			if (info->ep_out)
				dev->out_pipe = usb_sndbulkpipe(udev,
							info->ep_out);
		}
		if (dev->in_pipe)
			rtest = " bulk-in";
		if (dev->out_pipe)
			wtest = " bulk-out";
		if (dev->in_iso_pipe)
			irtest = " iso-in";
		if (dev->out_iso_pipe)
			iwtest = " iso-out";
		if (dev->in_int_pipe)
			intrtest = " int-in";
		if (dev->out_int_pipe)
			intwtest = " int-out";
	}

	usb_set_intfdata(intf, dev);
	dev_info(&intf->dev, "%s\n", info->name);
	dev_info(&intf->dev, "%s {control%s%s%s%s%s%s%s} tests%s\n",
			usb_speed_string(udev->speed),
			info->ctrl_out ? " in/out" : "",
			rtest, wtest,
			irtest, iwtest,
			intrtest, intwtest,
			info->alt >= 0 ? " (+alt)" : "");
	return 0;
}


static unsigned get_maxpacket(struct usb_device *udev, int pipe)
{
	struct usb_host_endpoint	*ep;

	ep = usb_pipe_endpoint(udev, pipe);
	return le16_to_cpup(&ep->desc.wMaxPacketSize);
}


static int is_good_ss_cap(struct usbtest_dev *tdev, u8 *buf)
{
	struct usb_ss_cap_descriptor *ss;

	ss = (struct usb_ss_cap_descriptor *) buf;

	if (ss->bLength != USB_DT_USB_SS_CAP_SIZE) {
		ERROR(tdev, "bogus superspeed device capability descriptor length\n");
		return 0;
	}

	/*
	 * only bit[1] of bmAttributes is used for LTM and others are
	 * reserved
	 */
	if (ss->bmAttributes & ~0x02) {	/* reserved == 0 */
		ERROR(tdev, "reserved bits set in bmAttributes\n");
		return 0;
	}

	/* bits[0:3] of wSpeedSupported is used and others are reserved */
	if (le16_to_cpu(ss->wSpeedSupported) & ~0x0f) {	/* reserved == 0 */
		ERROR(tdev, "reserved bits set in wSpeedSupported\n");
		return 0;
	}

	return 1;
}


static struct urb *usbtest_alloc_urb(
	struct usb_device	*udev,
	int			pipe,
	unsigned long		bytes,
	unsigned		transfer_flags,
	unsigned		offset,
	u8			bInterval,
	usb_complete_t		complete_fn)
{
	struct urb		*urb;

	urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!urb)
		return urb;

	if (bInterval)
		usb_fill_int_urb(urb, udev, pipe, NULL, bytes, complete_fn,
				NULL, bInterval);
	else
		usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, complete_fn,
				NULL);

	urb->interval = (udev->speed == USB_SPEED_HIGH)
			? (INTERRUPT_RATE << 3)
			: INTERRUPT_RATE;
	urb->transfer_flags = transfer_flags;
	if (usb_pipein(pipe))
		urb->transfer_flags |= URB_SHORT_NOT_OK;

	if ((bytes + offset) == 0)
		return urb;

	if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
		urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset,
			GFP_KERNEL, &urb->transfer_dma);
	else
		urb->transfer_buffer = kmalloc(bytes + offset, GFP_KERNEL);

	if (!urb->transfer_buffer) {
		usb_free_urb(urb);
		return NULL;
	}

	/* To test unaligned transfers add an offset and fill the
		unused memory with a guard value */
	if (offset) {
		memset(urb->transfer_buffer, GUARD_BYTE, offset);
		urb->transfer_buffer += offset;
		if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
			urb->transfer_dma += offset;
	}

	/* For inbound transfers use guard byte so that test fails if
		data not correctly copied */
	memset(urb->transfer_buffer,
			usb_pipein(urb->pipe) ? GUARD_BYTE : 0,
			bytes);
	return urb;
}


static int get_altsetting(struct usbtest_dev *dev)
{
	struct usb_interface	*iface = dev->intf;
	struct usb_device	*udev = interface_to_usbdev(iface);
	int			retval;

	retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
			USB_REQ_GET_INTERFACE, USB_DIR_IN|USB_RECIP_INTERFACE,
			0, iface->altsetting[0].desc.bInterfaceNumber,
			dev->buf, 1, USB_CTRL_GET_TIMEOUT);
	switch (retval) {
	case 1:
		return dev->buf[0];
	case 0:
		retval = -ERANGE;
		/* FALLTHROUGH */
	default:
		return retval;
	}
}


static int test_halt(struct usbtest_dev *tdev, int ep, struct urb *urb)
{
	int	retval;

	/* shouldn't look or act halted now */
	retval = verify_not_halted(tdev, ep, urb);
	if (retval < 0)
		return retval;

	/* set halt (protocol test only), verify it worked */
	retval = usb_control_msg(urb->dev, usb_sndctrlpipe(urb->dev, 0),
			USB_REQ_SET_FEATURE, USB_RECIP_ENDPOINT,
			USB_ENDPOINT_HALT, ep,
			NULL, 0, USB_CTRL_SET_TIMEOUT);
	if (retval < 0) {
		ERROR(tdev, "ep %02x couldn't set halt, %d\n", ep, retval);
		return retval;
	}
	retval = verify_halted(tdev, ep, urb);
	if (retval < 0) {
		int ret;

		/* clear halt anyways, else further tests will fail */
		ret = usb_clear_halt(urb->dev, urb->pipe);
		if (ret)
			ERROR(tdev, "ep %02x couldn't clear halt, %d\n",
			      ep, ret);

		return retval;
	}

	/* clear halt (tests API + protocol), verify it worked */
	retval = usb_clear_halt(urb->dev, urb->pipe);
	if (retval < 0) {
		ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval);
		return retval;
	}
	retval = verify_not_halted(tdev, ep, urb);
	if (retval < 0)
		return retval;

	/* NOTE:  could also verify SET_INTERFACE clear halts ... */

	return 0;
}


static struct urb *iso_alloc_urb(
	struct usb_device	*udev,
	int			pipe,
	struct usb_endpoint_descriptor	*desc,
	long			bytes,
	unsigned offset
)
{
	struct urb		*urb;
	unsigned		i, maxp, packets;

	if (bytes < 0 || !desc)
		return NULL;

	maxp = usb_endpoint_maxp(desc);
	if (udev->speed >= USB_SPEED_SUPER)
		maxp *= ss_isoc_get_packet_num(udev, pipe);
	else
		maxp *= usb_endpoint_maxp_mult(desc);

	packets = DIV_ROUND_UP(bytes, maxp);

	urb = usb_alloc_urb(packets, GFP_KERNEL);
	if (!urb)
		return urb;
	urb->dev = udev;
	urb->pipe = pipe;

	urb->number_of_packets = packets;
	urb->transfer_buffer_length = bytes;
	urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset,
							GFP_KERNEL,
							&urb->transfer_dma);
	if (!urb->transfer_buffer) {
		usb_free_urb(urb);
		return NULL;
	}
	if (offset) {
		memset(urb->transfer_buffer, GUARD_BYTE, offset);
		urb->transfer_buffer += offset;
		urb->transfer_dma += offset;
	}
	/* For inbound transfers use guard byte so that test fails if
		data not correctly copied */
	memset(urb->transfer_buffer,
			usb_pipein(urb->pipe) ? GUARD_BYTE : 0,
			bytes);

	for (i = 0; i < packets; i++) {
		/* here, only the last packet will be short */
		urb->iso_frame_desc[i].length = min((unsigned) bytes, maxp);
		bytes -= urb->iso_frame_desc[i].length;

		urb->iso_frame_desc[i].offset = maxp * i;
	}

	urb->complete = complicated_callback;
	/* urb->context = SET BY CALLER */
	urb->interval = 1 << (desc->bInterval - 1);
	urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
	return urb;
}

static int override_alt = -1;

static void complicated_callback(struct urb *urb);

struct usbtest_param_32 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s32		duration_sec;
	__s32		duration_usec;
}

struct usbtest_param_64 {
	/* inputs */
	__u32		test_num;	/* 0..(TEST_CASES-1) */
	__u32		iterations;
	__u32		length;
	__u32		vary;
	__u32		sglen;

	/* outputs */
	__s64		duration_sec;
	__s64		duration_usec;
}

#define USBTEST_REQUEST_32    _IOWR('U', 100, struct usbtest_param_32)

#define USBTEST_REQUEST_64    _IOWR('U', 100, struct usbtest_param_64)

struct usbtest_info {
	const char		*name;
	u8			ep_in;		/* bulk/intr source */
	u8			ep_out;		/* bulk/intr sink */
	unsigned		autoconf:1;
	unsigned		ctrl_out:1;
	unsigned		iso:1;		/* try iso in/out */
	unsigned		intr:1;		/* try interrupt in/out */
	int			alt;
}

struct usbtest_dev {
	struct usb_interface	*intf;
	struct usbtest_info	*info;
	int			in_pipe;
	int			out_pipe;
	int			in_iso_pipe;
	int			out_iso_pipe;
	int			in_int_pipe;
	int			out_int_pipe;
	struct usb_endpoint_descriptor	*iso_in, *iso_out;
	struct usb_endpoint_descriptor	*int_in, *int_out;
	struct mutex		lock;

#define TBUF_SIZE	256
	u8			*buf;
}

#define ERROR(tdev, fmt, args...) \
	dev_err(&(tdev)->intf->dev , fmt , ## args)

#define WARNING(tdev, fmt, args...) \
	dev_warn(&(tdev)->intf->dev , fmt , ## args)

#define GUARD_BYTE	0xA5

static unsigned pattern;

static unsigned mod_pattern;

struct sg_timeout {
	struct timer_list timer;
	struct usb_sg_request *req;
}

static unsigned realworld = 1;

struct ctrl_ctx {
	spinlock_t		lock;
	struct usbtest_dev	*dev;
	struct completion	complete;
	unsigned		count;
	unsigned		pending;
	int			status;
	struct urb		**urb;
	struct usbtest_param_32	*param;
	int			last;
}

#define NUM_SUBCASES	16		/* how many test subcases here? */

struct subcase {
	struct usb_ctrlrequest	setup;
	int			number;
	int			expected;
}

struct queued_ctx {
	struct completion	complete;
	atomic_t		pending;
	unsigned		num;
	int			status;
	struct urb		**urbs;
}

struct transfer_context {
	unsigned		count;
	unsigned		pending;
	spinlock_t		lock;
	struct completion	done;
	int			submit_error;
	unsigned long		errors;
	unsigned long		packet_count;
	struct usbtest_dev	*dev;
	bool			is_iso;
}

static unsigned force_interrupt;

static unsigned short vendor;

static unsigned short product;

static struct usbtest_info ez1_info = {
	.name		= "EZ-USB device",
	.ep_in		= 2,
	.ep_out		= 2,
	.alt		= 1,
};