DST: Distributed storage.
Subsystems (6)
A message that is not in the archive (sent off-list or not yet received): [email protected]
cover DST: Distributed storage.
Distributed storage.
I'm pleased to announce the 11'th release of the distributed storage subsystem (DST). This is a maintenance release and includes bug fixes and simple feature extensions only.
DST allows to form a storage on top of local and remote nodes and combine them into linear or mirroring setup, which in turn can be exported to remote nodes.
Short changelog:
* wakeup state when mirror detected error to seedup reconnect
* if connecting in csum mode to no-csum server, do not enable csums
* do not clean queue until all users are removed
* allow to increase size of the storage in linear add callback (with this change it is possible to add nodes into linear array in real time without stopping storage. Filesystem has to be prepared for the case when underlying device has changed its size. Real-time addon of mirror nodes is also supported)
* allow to delete gendisk only after device was started
* dst debug config option
* Name: Gamardjoba, genacvale! ('Hi friend' in georgian)
Great thanks to Matthew Hodgson <[email protected]> for debugging!
Overall list of features of the DST can be found on project's homepage:
http://tservice.net.ru/~s0mbre/old/?section=projects&item=dst
Thank you.
PATCH 1/4 DST: Distributed storage documentation.
Distributed storage documentation.
Algorithms used in the system, userspace interfaces (sysfs dirs and files), design and implementation details are described here.
Documentation/dst/algorithms.txt new file +115 -0
@@ -0,0 +1,115 @@ +Each storage by itself is just a set of contiguous logical blocks, with +allowed number of operations. Nodes, each of which has own start and size, +are placed into storage by appropriate algorithm, which remaps +logical sector number into real node's sector. One can create +own algorithms, since DST has pluggable interface for that. +Currently mirrored and linear algorithms are supported. + +Let's briefly describe how they work. + +Linear algorithm. +Simple approach of concatenating storages into single device with +increased size is used in this algorithm. Essentially new device +has size equal to sum of sizes of underlying nodes and nodes are +placed one after another. + + /----- Node 1 ---\ /------ Node 3 ----\ +start end start end + |==================|========================|==================| + | start end | + | \------- Node 2 ---------/ | + | | +start end + \-------------------------- DST storage ----------------------/ + + /\ + || + || + + IO operations + + Figure 1. + 3 nodes combined into single storage using linear algorithm. + +Mirror algorithm. +In this algorithms nodes are placed under each other, so when +operation comes to the first one, it can be mirrored to all +underlying nodes. In case of reading, actual data is obtained from +the nearest node - algoritm keeps track of previous operation +and knows where it was stopped, so that subsequent seek to the +start of the new request will take the shortest time. +Writing is always mirrored to all underlying nodes. + + IO operations + || + || + \/ + +|---------------- DST storage -------------------| +| prev position | +|-------|------------ Node 1 --------------------| +| prev pos | +|-------------------- Node 2 -----|--------------| +|prev pos | +|---|---------------- Node 3 --------------------| + + Figure 2. + 3 nodes combined into single storage using mirror algorithm. + +Each algorithm must implement number of callbacks, +which must be registered during initialization time. + +struct dst_alg_ops +{ + int (*add_node)(struct dst_node *n); + void (*del_node)(struct dst_node *n); + int (*remap)(struct dst_request *req); + int (*error)(struct kst_state *state, int err); + struct module *owner; +}; + +@add_node. +This callback is invoked when new node is being added into the storage, +but before node is actually added into the storage, so that it could +be accessed from it. When it is called, all appropriate initialization +of the underlying device is already completed (system has been connected +to remote node or got a reference to the local block device). At this +stage algorithm can add node into private map. +It must return zero on success or negative value otherwise. + +@del_node. +This callback is invoked when node is being deleted from the storage, +i.e. when its reference counter hits zero. It is called before +any cleaning is performed. +It must return zero on success or negative value otherwise. + +@remap. +This callback is invoked each time new bio hits the storage. +Request structure contains BIO itself, pointer to the node, which originally +stores the whole region under given IO request, and various parameters +used by storage core to process this block request. +It must return zero on success or negative value otherwise. It is upto +this method to call all cleaning if remapping failed, for example it must +call kst_bio_endio() for given callback in case of error, which in turn +will call bio_endio(). Note, that dst_request structure provided in this +callback is allocated on stack, so if there is a need to use it outside +of the given function, it must be cloned (it will happen automatically +in state's push callback, but that copy will not be shared by any other +user). + +@error. +This callback is invoked for each error, which happend when processed +requests for remote nodes or when talking to remote size +of the local export node (state contains data related to data +transfers over the network). +If this function has fixed given error, it must return 0 or negative +error value otherwise. + +@owner. +This is module reference counter updated automatically by DST core. + +Algorithm must provide its name and above structure to the +dst_alloc_alg() function, which will return a reference to the newly +created algorithm. +To remove it, one needs to call dst_remove_alg() with given algorithm +pointer.
Documentation/dst/dst.txt new file +69 -0
69 lines not on this page · raw message
Documentation/dst/sysfs.txt new file +30 -0
@@ -0,0 +1,30 @@ +This file describes sysfs files created for each storage. + +1. Per-storage files. +Each storage has its own dir /sysfs/devices/$storage_name, +which contains following files: + +alg - contains name of the algorithm used to created given storage +name - name of the storage +nodes - map of the storage (list of nodes and their sizes and starts) +remove_all_nodes - writable file which allows to remove all nodes from given + storage +n-$start-$cookie - per node directory, where + $start - start of the given node in sectors, + $cookie - unique node's id used by DST + +2. Per-node files. +Node's files are located in /sysfs/devices/$storage_name/n-$start-$cookie +directory, described above. + +chunks - private file for mirroring algorithm, contains map of update/dirty + sectors of the node, '-' means update, '+' is dirty and has to be + resynced sector +clean - writable file, writing leads to marking node as clean (in sync) +dirty - writable file, writing leads to marking node as dirty (not in sync) +size - size of the given node in sectors +start - start of the given node in the storage in sectors +type - contains type of the node in the following format: $type: $dev + where $type is either 'L' or 'R' - local or remote acordingly, + and $dev is device name for local node (/dev/sda1 for example) + or address of the remote node (192.168.4.81:1025 for example)
PATCH 2/4 DST: Core distributed storage files.
Core distributed storage files.
Include userspace interfaces, initialization, block layer bindings and other core functionality.
drivers/block/Kconfig +2 -0
@@ -451,6 +451,8 @@ config ATA_OVER_ETH This driver provides Support for ATA over Ethernet block devices like the Coraid EtherDrive (R) Storage Blade. +source "drivers/block/dst/Kconfig" + source "drivers/s390/block/Kconfig" endmenu
drivers/block/Makefile +1 -0
@@ -29,3 +29,4 @@ obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_SX8) += sx8.o obj-$(CONFIG_BLK_DEV_UB) += ub.o +obj-$(CONFIG_DST) += dst/
drivers/block/dst/Kconfig new file +28 -0
@@ -0,0 +1,28 @@ +config DST + tristate "Distributed storage" + depends on NET + select CONNECTOR + select LIBCRC32C + ---help--- + This driver allows to create a distributed storage. + +config DST_DEBUG + bool "DST debug" + depends on DST + ---help--- + This option will turn HEAVY debugging of the DST. + Turn it on ONLY if you have to debug some really obscure problem. + +config DST_ALG_LINEAR + tristate "Linear distribution algorithm" + depends on DST + ---help--- + This module allows to create linear mapping of the nodes + in the distributed storage. + +config DST_ALG_MIRROR + tristate "Mirror distribution algorithm" + depends on DST + ---help--- + This module allows to create a mirror of the noes in the + distributed storage.
drivers/block/dst/Makefile new file +6 -0
@@ -0,0 +1,6 @@ +obj-$(CONFIG_DST) += dst.o + +dst-y := dcore.o kst.o + +obj-$(CONFIG_DST_ALG_LINEAR) += alg_linear.o +obj-$(CONFIG_DST_ALG_MIRROR) += alg_mirror.o
drivers/block/dst/dcore.c new file +1631 -0
1,631 lines not on this page · raw message
include/linux/connector.h +3 -1
@@ -36,9 +36,11 @@#define CN_VAL_CIFS 0x1#define CN_W1_IDX 0x3/* w1 communication */ #define CN_W1_VAL 0x1 +#define CN_DST_IDX 0x4/* Distributed storage */ +#define CN_DST_VAL 0x1 -#define CN_NETLINK_USERS 4 +#define CN_NETLINK_USERS 5/* * Maximum connector's message size.
include/linux/dst.h new file +385 -0
385 lines not on this page · raw message
PATCH 3/4 DST: Network state machine.
Network state machine.
Includes network async processing state machine and related tasks.
drivers/block/dst/kst.c new file +1513 -0
@@ -0,0 +1,1513 @@ +/* +* 2007+ Copyright (c) Evgeniy Polyakov <[email protected]> +* All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/socket.h> +#include <linux/kthread.h> +#include <linux/net.h> +#include <linux/in.h> +#include <linux/poll.h> +#include <linux/bio.h> +#include <linux/dst.h> + +#include <net/sock.h> + +struct kst_poll_helper +{ +poll_table pt; +struct kst_state *st; +}; + +static LIST_HEAD (kst_worker_list); +static DEFINE_MUTEX (kst_worker_mutex); + +/* +* This function creates bound socket for local export node. +*/ +static int kst_sock_create (struct kst_state *st,struct saddr *addr, +int type,int proto,int backlog) +{ +int err; + + err =sock_create (addr->sa_family, type, proto, &st->socket); +if (err) + goto err_out_exit; + + err = st->socket->ops->bind (st->socket, (struct sockaddr *)addr, + addr->sa_data_len); + + err = st->socket->ops->listen (st->socket, backlog); +if (err) + goto err_out_release; + + st->socket->sk->sk_allocation =GFP_NOIO ; + +return 0 ; + +err_out_release: +sock_release (st->socket); +err_out_exit: +return err; +} + +static void kst_sock_release (struct kst_state *st) +{ +if (st->socket) { +sock_release (st->socket); + st->socket =NULL ; + } +} + +void kst_wake (struct kst_state *st) +{ +if (st) { +struct kst_worker *w = st->node->w; +unsigned long flags; + +spin_lock_irqsave (&w->ready_lock, flags); +if (list_empty (&st->ready_entry)) +list_add_tail (&st->ready_entry, &w->ready_list); +spin_unlock_irqrestore (&w->ready_lock, flags); + +wake_up (&w->wait); + } +} +EXPORT_SYMBOL_GPL (kst_wake); + +/* +* Polling machinery. +*/ +static int kst_state_wake_callback (wait_queue_t *wait,unsigned mode, +int sync,void *key) +{ + struct kst_state *st = container_of(wait, struct kst_state, wait); +kst_wake (st); +return 1 ; +} + +static void kst_queue_func (struct file *file,wait_queue_head_t *whead, +poll_table *pt) +{ + struct kst_state *st = container_of(pt, struct kst_poll_helper, pt)->st; + + st->whead = whead; +init_waitqueue_func_entry (&st->wait, kst_state_wake_callback); +add_wait_queue (whead, &st->wait); +} + +static void kst_poll_exit (struct kst_state *st) +{ +if (st->whead) { +remove_wait_queue (st->whead, &st->wait); + st->whead =NULL ; + } +} + +/* +* This function removes request from state tree and ordering list. +*/ +void kst_del_req (struct dst_request *req) +{ +list_del_init (&req->request_list_entry); +} +EXPORT_SYMBOL_GPL (kst_del_req); + +static struct dst_request *kst_req_first (struct kst_state *st) +{ +struct dst_request *req =NULL ; + +if (!list_empty (&st->request_list)) + req = list_entry(st->request_list.next, struct dst_request, + request_list_entry); +return req; +} + +/* +* This function dequeues first request from the queue and tree. +*/ +static struct dst_request *kst_dequeue_req (struct kst_state *st) +{ +struct dst_request *req; + +mutex_lock (&st->request_lock); + req =kst_req_first (st); +if (req) +kst_del_req (req); +mutex_unlock (&st->request_lock); +return req; +} + +/* +* This function enqueues request into tree, indexed by start of the request, +* and also puts request into ordered queue. +*/ +int kst_enqueue_req (struct kst_state *st,struct dst_request *req) +{ +if (unlikely (req->flags &DST_REQ_CHECK_QUEUE )) { +struct dst_request *r; + +list_for_each_entry (r, &st->request_list, request_list_entry) { +if (bio_rw (r->bio) !=bio_rw (req->bio)) +continue ; + +if (r->start >= req->start + req->size) +continue ; + +if (r->start + r->size <= req->start) +continue ; + +return -EEXIST ; + } + } + +list_add_tail (&req->request_list_entry, &st->request_list); +return 0 ; +} +EXPORT_SYMBOL_GPL (kst_enqueue_req); + +/* +* BIOs for local exporting node are freed via this function. +*/ +static void kst_export_put_bio (struct bio *bio) +{ +int i; +struct bio_vec *bv; + +dprintk ("%s: bio: %p, size: %u, idx: %d, num: %d, req: %p.\n" , + __func__, bio, bio->bi_size, bio->bi_idx, + bio->bi_vcnt, bio->bi_private); + +bio_for_each_segment (bv, bio, i) +__free_page (bv->bv_page); +bio_put (bio); +} + +/* +* This is a generic request completion function for requests, +* queued for async processing. +* If it is local export node, state machine is different, +* see details below. +*/ +void kst_complete_req (struct dst_request *req,int err) +{ +dprintk ("%s: bio: %p, req: %p, size: %llu, orig_size: %llu, " +"bi_size: %u, err: %d, flags: %u.\n" , + __func__, req->bio, req, req->size, req->orig_size, + req->bio->bi_size, err, req->flags); + +if (req->flags &DST_REQ_EXPORT ) { +if (err || !(req->flags &DST_REQ_EXPORT_WRITE )) { + req->bio_endio (req, err); + goto out; + } + + req->bio->bi_rw =WRITE ; +generic_make_request (req->bio); + }else { + req->bio_endio (req, err); + } +out: +dst_free_request (req); +} +EXPORT_SYMBOL_GPL (kst_complete_req); + +static void kst_flush_requests (struct kst_state *st) +{ +struct dst_request *req; + +while ((req =kst_dequeue_req (st)) !=NULL ) +kst_complete_req (req, -EIO ); +} + +static int kst_poll_init (struct kst_state *st) +{ +struct kst_poll_helper ph; + + ph.st = st; +init_poll_funcptr (&ph.pt, &kst_queue_func); + + st->socket->ops->poll (NULL , st->socket, &ph.pt); +return 0 ; +} + +/* +* Main state creation function. +* It creates new state according to given operations +* and links it into worker structure and node. +*/ +static struct kst_state *kst_state_init (struct dst_node *node, +unsigned int permissions, +struct kst_state_ops *ops,void *data) +{ +struct kst_state *st; +int err; + + st =kzalloc (sizeof (struct kst_state ),GFP_KERNEL ); +if (!st) +return ERR_PTR (-ENOMEM ); + + st->permissions = permissions; + st->node = node; + st->ops = ops; +INIT_LIST_HEAD (&st->ready_entry); +INIT_LIST_HEAD (&st->entry); +INIT_LIST_HEAD (&st->request_list); +mutex_init (&st->request_lock); + + err = st->ops->init (st, data); +if (err) + goto err_out_free; +mutex_lock (&node->w->state_mutex); +list_add_tail (&st->entry, &node->w->state_list); +mutex_unlock (&node->w->state_mutex); + +kst_wake (st); + +return st; + +err_out_free: +kfree (st); +return ERR_PTR (err); +} + +/* +* This function is called when node is removed, +* or when state is destroyed for connected to local exporting +* node client. +*/ +void kst_state_exit (struct kst_state *st) +{ +struct kst_worker *w = st->node->w; + +mutex_lock (&w->state_mutex); +list_del_init (&st->entry); +mutex_unlock (&w->state_mutex); + + st->ops->exit (st); + +if (st == st->node->state) + st->node->state =NULL ; + +kfree (st); +} + +static int kst_error (struct kst_state *st,int err) +{ +if ((err == -ECONNRESET || err == -EPIPE ) && st->ops->recovery) + err = st->ops->recovery (st, err); + +return st->node->st->alg->ops->error (st, err); +} + +/* +* This is main state processing function. +* It tries to complete request and invoke appropriate +* callbacks in case of errors or successfull operation finish. +*/ +static int kst_thread_process_state (struct kst_state *st) +{ +int err, empty; +unsigned int revents; +struct dst_request *req, *tmp; + +mutex_lock (&st->request_lock); +if (st->ops->ready) { + err = st->ops->ready (st); +if (err) { +mutex_unlock (&st->request_lock); +if (err <0 ) +kst_state_exit (st); +return err; + } + } + + err =0 ; + empty =1 ; + req =NULL ; +list_for_each_entry_safe (req, tmp, &st->request_list, request_list_entry) { + empty =0 ; + revents = st->socket->ops->poll (st->socket->file, + st->socket,NULL ); +if (!revents) +break ; + err = req->callback (req, revents); +if (req->size && !err) + err =1 ; + +if (err <0 || !req->size) { +if (!req->size) + err =0 ; +kst_del_req (req); +kst_complete_req (req, err); + } + +if (err) +break ; + } + +dprintk ("%s: broke the loop: err: %d, list_empty: %d.\n" , + __func__, err,list_empty (&st->request_list)); +mutex_unlock (&st->request_lock); + +if (err <0 ) { +dprintk ("%s: req: %p, err: %d, st: %p, node->state: %p.\n" , + __func__, req, err, st, st->node->state); + +if (st != st->node->state) { +/* +* Accepted client has state not related to storage +* node, so it must be freed explicitely. +* We do not try to fix clients connections to local +* export nodes, just drop the client. +*/ + +kst_state_exit (st); +return err; + } + + err =kst_error (st, err); +if (err) +return err; + +kst_wake (st); + } + +if (list_empty (&st->request_list) && !empty) +kst_wake (st); + +return err; +} + +/* +* Main worker thread - one per storage. +*/ +static int kst_thread_func (void *data) +{ +struct kst_worker *w = data; +struct kst_state *st; +unsigned long flags; +int err =0 ; + +while (!kthread_should_stop ()) { +wait_event_interruptible_timeout (w->wait, + !list_empty (&w->ready_list) || +kthread_should_stop (), +HZ ); + + st =NULL ; +spin_lock_irqsave (&w->ready_lock, flags); +if (!list_empty (&w->ready_list)) { + st = list_entry(w->ready_list.next, struct kst_state, + ready_entry); +list_del_init (&st->ready_entry); + } +spin_unlock_irqrestore (&w->ready_lock, flags); + +if (!st) +continue ; + + err =kst_thread_process_state (st); + } + +return err; +} + +/* +* Worker initialization - this object will host andprocess all states, +* which in turn host requests for remote targets. +*/ +struct kst_worker *kst_worker_init (int id) +{ +struct kst_worker *w; +int err; + + w =kzalloc (sizeof (struct kst_worker ),GFP_KERNEL ); +if (!w) +return ERR_PTR (-ENOMEM ); + + w->id = id; +init_waitqueue_head (&w->wait); +spin_lock_init (&w->ready_lock); +mutex_init (&w->state_mutex); + +INIT_LIST_HEAD (&w->ready_list); +INIT_LIST_HEAD (&w->state_list); + + w->req_pool =mempool_create_slab_pool (256 , dst_request_cache); +if (!w->req_pool) { + err = -ENOMEM ; + goto err_out_free; + } + + w->thread =kthread_run (&kst_thread_func, w,"kst%d" , w->id); +if (IS_ERR (w->thread)) { + err =PTR_ERR (w->thread); + goto err_out_destroy; + } + +mutex_lock (&kst_worker_mutex); +list_add_tail (&w->entry, &kst_worker_list); +mutex_unlock (&kst_worker_mutex); + +return w; + +err_out_destroy: +mempool_destroy (w->req_pool); +err_out_free: +kfree (w); +return ERR_PTR (err); +} + +void kst_worker_exit (struct kst_worker *w) +{ +struct kst_state *st, *n; + +mutex_lock (&kst_worker_mutex); +list_del (&w->entry); +mutex_unlock (&kst_worker_mutex); + +kthread_stop (w->thread); + +list_for_each_entry_safe (st, n, &w->state_list, entry) { +kst_state_exit (st); + } + +mempool_destroy (w->req_pool); +kfree (w); +} + +/* +* Common state exit callback. +* Removes itself from worker's list of states, +* releases socket and flushes all requests. +*/ +static void kst_common_exit (struct kst_state *st) +{ +unsigned long flags; + +kst_poll_exit (st); + +spin_lock_irqsave (&st->node->w->ready_lock, flags); +list_del_init (&st->ready_entry); +spin_unlock_irqrestore (&st->node->w->ready_lock, flags); + +kst_flush_requests (st); +kst_sock_release (st); +} + +/* +* Listen socket contains security attributes in request_list, +* so it can not be flushed via usual way. +*/ +static void kst_listen_flush (struct kst_state *st) +{ +struct dst_secure *s, *tmp; + +list_for_each_entry_safe (s, tmp, &st->request_list, sec_entry) { +list_del (&s->sec_entry); +kfree (s); + } +} + +static void kst_listen_exit (struct kst_state *st) +{ +kst_listen_flush (st); +kst_common_exit (st); +} + +/* +* BIO vector receiving function - does not block, but may sleep because +* of scheduling policy. +*/ +static int kst_data_recv_bio_vec (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +struct msghdr msg; +struct kvec iov; +void *kaddr; +int err; + + kaddr =kmap (bv->bv_page); + + iov.iov_base = kaddr + bv->bv_offset + offset; + iov.iov_len = size; + + msg.msg_iov = (struct iovec *)&iov; + msg.msg_iovlen =1 ; + msg.msg_name =NULL ; + msg.msg_namelen =0 ; + msg.msg_control =NULL ; + msg.msg_controllen =0 ; + msg.msg_flags =MSG_DONTWAIT |MSG_NOSIGNAL ; + + err =kernel_recvmsg (st->socket, &msg, &iov,1 , iov.iov_len, + msg.msg_flags); +kunmap (bv->bv_page); + +return err; +} + +/* +* BIO vector sending function - does not block, but may sleep because +* of scheduling policy. +*/ +static int kst_data_send_bio_vec (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +return kernel_sendpage (st->socket, bv->bv_page, + bv->bv_offset + offset, size, +MSG_DONTWAIT |MSG_NOSIGNAL ); +} + +static int kst_data_send_bio_vec_slow (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +struct msghdr msg; +struct kvec iov; +void *addr; +int err; + + addr =kmap (bv->bv_page); + iov.iov_base = addr + bv->bv_offset + offset; + iov.iov_len = size; + + msg.msg_iov = (struct iovec *)&iov; + msg.msg_iovlen =1 ; + msg.msg_name =NULL ; + msg.msg_namelen =0 ; + msg.msg_control =NULL ; + msg.msg_controllen =0 ; + msg.msg_flags =MSG_DONTWAIT |MSG_NOSIGNAL ; + + err =kernel_sendmsg (st->socket, &msg, &iov,1 , iov.iov_len); +kunmap (bv->bv_page); + +return err; +} + +static u32 dst_csum_bvec (struct bio_vec *bv,unsigned int offset,unsigned int size) +{ +void *addr; +u32 csum; + + addr =kmap_atomic (bv->bv_page,KM_USER0 ); + csum =dst_csum_data (addr + bv->bv_offset + offset, size); +kunmap_atomic (addr,KM_USER0 ); + +return csum; +} + +typedef int (*kst_data_process_bio_vec_t )(struct kst_state *st, +struct bio_vec *bv,unsigned int offset,unsigned int size); + +/* +* @req: processing request. +* Contains BIO and all related to its processing info. +* +* This function sends or receives requested number of pages from given BIO. +* +* In case of errors negative value is returned and @size, +* @index and @off are set to the: +* - number of bytes not yet processed (i.e. the rest of the bytes to be +* processed). +* - index of the last bio_vec started to be processed (header sent). +* - offset of the first byte to be processed in the bio_vec. +* +* If there are no errors, zero is returned. +* -EAGAIN is not an error and is transformed into zero return value, +* called must check if @size is zero, in that case whole BIO is processed +* and thus req->bio_endio() can be called, othervise new request must be allocated +* to be processed later. +*/ +static int kst_data_process_bio (struct dst_request *req) +{ +int err = -ENOSPC ; +struct dst_remote_request r; +kst_data_process_bio_vec_t func; +unsigned int cur_size; +int use_csum =test_bit (DST_NODE_USE_CSUM , &req->node->flags); + +if (bio_rw (req->bio) ==WRITE ) { +int i; + + func = kst_data_send_bio_vec; +for (i=req->idx; i<req->num; ++i) { +struct bio_vec *bv =bio_iovec_idx (req->bio, i); + +if (PageSlab (bv->bv_page)) { + func = kst_data_send_bio_vec_slow; +break ; + } + } + r.cmd =cpu_to_be32 (DST_WRITE ); + }else { + r.cmd =cpu_to_be32 (DST_READ ); + func = kst_data_recv_bio_vec; + } + +dprintk ("%s: start: [%c], start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u, flags: %x, use_csum: %d.\n" , + __func__, (bio_rw (req->bio) ==WRITE )?'W' :'R' , + req->start, req->idx, req->num, req->size, req->offset, + req->flags, use_csum); + +while (req->idx < req->num) { +struct bio_vec *bv =bio_iovec_idx (req->bio, req->idx); + + cur_size =min_t (u64, bv->bv_len - req->offset, req->size); + +dprintk ("%s: page: %p, slab: %d, count: %d, max: %d, off: %u, len: %u, req->offset: %u, " +"req->size: %llu, cur_size: %u, flags: %x, " +"use_csum: %d, req->csum: %x.\n" , + __func__, bv->bv_page,PageSlab (bv->bv_page), +atomic_read (&bv->bv_page->_count), req->bio->bi_vcnt, + bv->bv_offset, bv->bv_len, + req->offset, req->size, cur_size, + req->flags, use_csum, req->tmp_csum); + +if (cur_size ==0 ) { +printk (KERN_ERR "%s: %d/%d: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"req_offset: %u, req_size: %llu, " +"req: %p, bio: %p, err: %d.\n" , + __func__, req->idx, req->num, req->start, + bv->bv_offset, bv->bv_len, + req->offset, req->size, + req, req->bio, err); +BUG (); + } + +if (!(req->flags &DST_REQ_HEADER_SENT )) { + r.sector =cpu_to_be64 (req->start); + r.offset =cpu_to_be32 (bv->bv_offset + req->offset); + r.size =cpu_to_be32 (cur_size); + r.csum =0 ; + +if (use_csum &&bio_rw (req->bio) ==WRITE && + !req->tmp_offset) { + req->tmp_offset = req->offset; + r.csum =cpu_to_be32 (dst_csum_bvec (bv, + req->offset, cur_size)); + } + + err =dst_data_send_header (req->state->socket, &r); +dprintk ("%s: %d/%d: sending header: cmd: %u, start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num,be32_to_cpu (r.cmd), + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, err); + +if (err !=sizeof (struct dst_remote_request )) { +if (err >=0 ) + err = -EINVAL ; +break ; + } + + req->flags |=DST_REQ_HEADER_SENT ; + } + +if (use_csum && (bio_rw (req->bio) !=WRITE ) && + !(req->flags &DST_REQ_CHEKSUM_RECV )) { +struct dst_remote_request tmp_req; + + err =dst_data_recv_header (req->state->socket, &tmp_req,0 ); +dprintk ("%s: %d/%d: receiving header: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num, + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, err); + +if (err !=sizeof (struct dst_remote_request )) { +if (err >=0 ) + err = -EINVAL ; +break ; + } + +if (req->tmp_csum) { +printk ("%s: req: %p, old csum: %x, new: %x.\n" , + __func__, req, req->tmp_csum, +be32_to_cpu (tmp_req.csum)); +BUG_ON (1 ); + } + +dprintk ("%s: req: %p, old csum: %x, new: %x.\n" , + __func__, req, req->tmp_csum, +be32_to_cpu (tmp_req.csum)); + req->tmp_csum =be32_to_cpu (tmp_req.csum); + + req->flags |=DST_REQ_CHEKSUM_RECV ; + } + + err =func (req->state, bv, req->offset, cur_size); +if (err <=0 ) +break ; + + req->offset += err; + req->size -= err; + +if (req->offset != bv->bv_len) { +dprintk ("%s: %d/%d: this: start: %llu, bv_offset: %u, " +"bv_len: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num, req->start, + bv->bv_offset, bv->bv_len, + req->offset, cur_size, err); + err = -EAGAIN ; +break ; + } + +if (use_csum &&bio_rw (req->bio) !=WRITE ) { +u32 csum =dst_csum_bvec (bv, req->tmp_offset, + bv->bv_len - req->tmp_offset); + +dprintk ("%s: req: %p, csum: %x, received csum: %x.\n" , + __func__, req, csum, req->tmp_csum); + +if (csum != req->tmp_csum) { +printk ("%s: %d/%d: broken checksum: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, orig_size: %llu.\n" , + __func__, req->idx, req->num, + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, req->orig_size); +printk ("%s: broken checksum: req: %p, csum: %x, " +"should be: %x, flags: %x, " +"req->tmp_offset: %u, rw: %lu.\n" , + __func__, req, csum, req->tmp_csum, + req->flags, req->tmp_offset,bio_rw (req->bio)); + + req->offset -= err; + req->size += err; + + err = -EREMOTEIO ; +break ; + } + } + + req->offset =0 ; + req->idx++; + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + req->tmp_csum =0 ; + req->start +=to_sector (bv->bv_len); + } + +if (err <=0 && err != -EAGAIN ) { +if (err ==0 ) + err = -ECONNRESET ; + }else + err =0 ; + +if (err <0 || (req->idx == req->num && req->size)) { +dprintk ("%s: return: idx: %d, num: %d, offset: %u, " +"size: %llu, err: %d.\n" , + __func__, req->idx, req->num, req->offset, + req->size, err); + } +dprintk ("%s: end: start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u.\n" , + __func__, req->start, req->idx, req->num, + req->size, req->offset); + +return err; +} + +void kst_bio_endio (struct dst_request *req,int err) +{ +if (err &&printk_ratelimit ()) +printk ("%s: freeing bio: %p, bi_size: %u, " +"orig_size: %llu, req: %p, err: %d.\n" , + __func__, req->bio, req->bio->bi_size, req->orig_size, + req, err); +bio_endio (req->bio, req->orig_size, err); +} +EXPORT_SYMBOL_GPL (kst_bio_endio); + +/* +* This callback is invoked by worker thread to process given request. +*/ +int kst_data_callback (struct dst_request *req,unsigned int revents) +{ +int err; + +dprintk ("%s: req: %p, num: %d, idx: %d, bio: %p, " +"revents: %x, flags: %x.\n" , + __func__, req, req->num, req->idx, req->bio, + revents, req->flags); + +if (req->flags &DST_REQ_EXPORT_READ ) +return 1 ; + + err =kst_data_process_bio (req); + +if (revents & (POLLERR |POLLHUP |POLLRDHUP )) + err = -EPIPE ; + +return err; +} +EXPORT_SYMBOL_GPL (kst_data_callback); + +struct dst_request *dst_clone_request (struct dst_request *req,mempool_t *pool) +{ +struct dst_request *new_req; + + new_req =mempool_alloc (pool,GFP_NOIO ); +if (!new_req) +return NULL ; + +memset (new_req,0 ,sizeof (struct dst_request )); + +dprintk ("%s: req: %p, new_req: %p.\n" , __func__, req, new_req); + +if (req) { + new_req->bio = req->bio; + new_req->state = req->state; + new_req->node = req->node; + new_req->idx = req->idx; + new_req->num = req->num; + new_req->size = req->size; + new_req->orig_size = req->orig_size; + new_req->offset = req->offset; + new_req->tmp_offset = req->tmp_offset; + new_req->tmp_csum = req->tmp_csum; + new_req->start = req->start; + new_req->flags = req->flags; + new_req->bio_endio = req->bio_endio; + new_req->priv = req->priv; + } + +return new_req; +} +EXPORT_SYMBOL_GPL (dst_clone_request); + +void dst_free_request (struct dst_request *req) +{ +dprintk ("%s: free req: %p, pool: %p, bio: %p, state: %p, node: %p.\n" , + __func__, req, req->node->w->req_pool, + req->bio, req->state, req->node); +mempool_free (req, req->node->w->req_pool); +} +EXPORT_SYMBOL_GPL (dst_free_request); + +/* +* This is main data processing function, eventually invoked from block layer. +* It tries to complte request, but if it is about to block, it allocates +* new request and queues it to main worker to be processed when events allow. +*/ +static int kst_data_push (struct dst_request *req) +{ +struct kst_state *st = req->state; +struct dst_request *new_req; +unsigned int revents; +int err, locked =0 ; + +dprintk ("%s: start: %llu, size: %llu, bio: %p.\n" , + __func__, req->start, req->size, req->bio); + +if (!list_empty (&st->request_list) || (req->flags &DST_REQ_ALWAYS_QUEUE )) + goto alloc_new_req; + +if (mutex_trylock (&st->request_lock)) { + locked =1 ; + +if (!list_empty (&st->request_list)) + goto alloc_new_req; + + revents = st->socket->ops->poll (NULL , st->socket,NULL ); +if (revents &POLLOUT ) { + err =kst_data_process_bio (req); +if (err <0 ) + goto out_unlock; + +if (!req->size) + goto out_bio_endio; + } + } + +alloc_new_req: + err = -ENOMEM ; + new_req =dst_clone_request (req, req->node->w->req_pool); +if (!new_req) + goto out_unlock; + + new_req->callback = &kst_data_callback; + +if (!locked) +mutex_lock (&st->request_lock); + + locked =1 ; + + err =kst_enqueue_req (st, new_req); +if (err) + goto out_unlock; +mutex_unlock (&st->request_lock); + + err =0 ; + goto out; + +out_bio_endio: + req->bio_endio (req, err); +out_unlock: +if (locked) +mutex_unlock (&st->request_lock); + locked =0 ; + +if (err) { + err =kst_error (st, err); +if (!err) + goto alloc_new_req; + } + +if (err &&printk_ratelimit ()) { +printk ("%s: error [%c], start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u, err: %d.\n" , + __func__, (bio_rw (req->bio) ==WRITE )?'W' :'R' , + req->start, req->idx, req->num, req->size, + req->offset, err); + } + +out: + +kst_wake (st); +return err; +} + +/* +* Remote node initialization callback. +*/ +static int kst_data_init (struct kst_state *st,void *data) +{ +int err; + + st->socket = data; + st->socket->sk->sk_allocation =GFP_NOIO ; +/* +* Why not? +*/ + st->socket->sk->sk_sndbuf = st->socket->sk->sk_sndbuf =1024 *1024 *10 ; + + err =kst_poll_init (st); +if (err) +return err; + +return 0 ; +} + +/* +* Remote node recovery function - tries to reconnect to given target. +*/ +static int kst_data_recovery (struct kst_state *st,int err) +{ +struct socket *sock; +struct sockaddr addr; +int addrlen; +struct dst_request *req; + +if (err != -ECONNRESET && err != -EPIPE ) { +dprintk ("%s: state %p does not know how " +"to recover from error %d.\n" , + __func__, st, err); +return err; + } + + err =sock_create (st->socket->ops->family, st->socket->type, + st->socket->sk->sk_protocol, &sock); +if (err <0 ) + goto err_out_exit; + + sock->sk->sk_sndtimeo = sock->sk->sk_rcvtimeo = +msecs_to_jiffies (DST_DEFAULT_TIMEO ); + + err = sock->ops->getname (st->socket, &addr, &addrlen,2 ); +if (err) + goto err_out_destroy; + + err = sock->ops->connect (sock, &addr, addrlen,0 ); +if (err) + goto err_out_destroy; + +kst_poll_exit (st); +kst_sock_release (st); + +mutex_lock (&st->request_lock); + err = st->ops->init (st, sock); +if (!err) { +/* +* After reconnection is completed all requests +* must be resent from the state they were finished previously, +* but with new headers. +*/ +list_for_each_entry (req, &st->request_list, request_list_entry) + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + } +mutex_unlock (&st->request_lock); +if (err <0 ) + goto err_out_destroy; + +kst_wake (st); +dprintk ("%s: reconnected.\n" , __func__); + +return 0 ; + +err_out_destroy: +sock_release (sock); +err_out_exit: +dprintk ("%s: revovery failed: st: %p, err: %d.\n" , __func__, st, err); +return err; +} + +/* +* Local exporting node end IO callbacks. +*/ +static int kst_export_write_end_io (struct bio *bio,unsigned int size,int err) +{ +dprintk ("%s: bio: %p, size: %u, idx: %d, num: %d, err: %d.\n" , + __func__, bio, bio->bi_size, bio->bi_idx, bio->bi_vcnt, err); + +if (bio->bi_size) +return 1 ; + +kst_export_put_bio (bio); +return 0 ; +} + +static int kst_export_read_end_io (struct bio *bio,unsigned int size,int err) +{ +struct dst_request *req = bio->bi_private; +struct kst_state *st = req->state; +int use_csum =test_bit (DST_NODE_USE_CSUM , &req->node->flags); + +dprintk ("%s: bio: %p, req: %p, size: %u, idx: %d, num: %d, err: %d.\n" , + __func__, bio, req, bio->bi_size, bio->bi_idx, + bio->bi_vcnt, err); + +if (bio->bi_size) +return 1 ; + +if (err) { +kst_export_put_bio (bio); +return 0 ; + } + + bio->bi_size = req->size = req->orig_size; + bio->bi_rw =WRITE ; +if (use_csum) + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + +/* +* This is a race with kst_data_callback(), which checks +* this bit to determine if it can or can not process given +* request. This does not harm actually, since subsequent +* state wakeup will call it again and thus will pick +* given request in time. +*/ + req->flags &= ~DST_REQ_EXPORT_READ ; +kst_wake (st); +return 0 ; +} + +/* +* This callback is invoked each time new request from remote +* node to given local export node is received. +* It allocates new block IO request and queues it for processing. +*/ +static int kst_export_ready (struct kst_state *st) +{ +struct dst_remote_request r; +struct bio *bio; +int err, nr, i; +struct dst_request *req; +unsigned int revents = st->socket->ops->poll (NULL , st->socket,NULL ); + +if (revents & (POLLERR |POLLHUP )) { + err = -EPIPE ; + goto err_out_exit; + } + +if (!(revents &POLLIN ) || !list_empty (&st->request_list)) +return 0 ; + + err =dst_data_recv_header (st->socket, &r,1 ); +if (err !=sizeof (struct dst_remote_request )) { + err = -ECONNRESET ; + goto err_out_exit; + } + +kst_convert_header (&r); + +dprintk ("\n%s: st: %p, cmd: %u, sector: %llu, size: %u, " +"csum: %x, offset: %u.\n" , + __func__, st, r.cmd, r.sector, + r.size, r.csum, r.offset); + + err = -EINVAL ; +if (r.cmd !=DST_READ && r.cmd !=DST_WRITE && r.cmd !=DST_REMOTE_CFG ) + goto err_out_exit; + +if ((s64)(r.sector +to_sector (r.size)) <0 || + (r.sector +to_sector (r.size)) > st->node->size || + r.offset >=PAGE_SIZE ) + goto err_out_exit; + +if (r.cmd ==DST_REMOTE_CFG ) { + r.sector = st->node->size; + +if (test_bit (DST_NODE_USE_CSUM , &st->node->flags)) + r.csum =1 ; + +kst_convert_header (&r); + + err =dst_data_send_header (st->socket, &r); +if (err !=sizeof (struct dst_remote_request )) { + err = -EINVAL ; + goto err_out_exit; + } +kst_wake (st); +return 0 ; + } + + nr =DIV_ROUND_UP (r.size,PAGE_SIZE ); + +while (r.size) { +int nr_pages =min (BIO_MAX_PAGES , nr); +unsigned int size; +struct page *page; + + err = -ENOMEM ; + req =dst_clone_request (NULL , st->node->w->req_pool); +if (!req) + goto err_out_exit; + + bio =bio_alloc (GFP_NOIO , nr_pages); +if (!bio) + goto err_out_free_req; + + req->flags =DST_REQ_EXPORT |DST_REQ_HEADER_SENT | +DST_REQ_CHEKSUM_RECV ; + req->bio = bio; + req->state = st; + req->node = st->node; + req->callback = &kst_data_callback; + req->bio_endio = &kst_bio_endio; + + req->tmp_offset =0 ; + req->tmp_csum = r.csum; + +/* +* Yes, looks a bit weird. +* Logic is simple - for local exporting node all operations +* are reversed compared to usual nodes, since usual nodes +* process remote data and local export node process remote +* requests, so that writing data means sending data to +* remote node and receiving on the local export one. +* +* So, to process writing to the exported node we need first +* to receive data from the net (i.e. to perform READ +* operationin terms of usual node), and then put it to the +* storage (WRITE command, so it will be changed before +* calling generic_make_request()). +* +* To process read request from the exported node we need +* first to read it from storage (READ command for BIO) +* and then send it over the net (perform WRITE operation +* in terms of network). +*/ +if (r.cmd ==DST_WRITE ) { + req->flags |=DST_REQ_EXPORT_WRITE ; + bio->bi_end_io = kst_export_write_end_io; + }else { + req->flags |=DST_REQ_EXPORT_READ ; + bio->bi_end_io = kst_export_read_end_io; + } + bio->bi_rw =READ ; + bio->bi_private = req; + bio->bi_sector = r.sector; + bio->bi_bdev = st->node->bdev; + +for (i =0 ; i < nr_pages; ++i) { + page =alloc_page (GFP_NOIO ); +if (!page) +break ; + + size =min_t (u32,PAGE_SIZE - r.offset, r.size); + + err =bio_add_page (bio, page, size,0 ); +dprintk ("%s: %d/%d: page: %p, size: %u, " +"offset: %u (used zero), err: %d.\n" , + __func__, i, nr_pages, page, size, + r.offset, err); +if (err <=0 ) +break ; + +if (err == size) + nr--; + + r.size -= err; + r.sector +=to_sector (err); + +if (!r.size) +break ; + } + +if (!bio->bi_vcnt) { + err = -ENOMEM ; + goto err_out_put; + } + + req->size = req->orig_size = bio->bi_size; + req->start = bio->bi_sector; + req->idx =0 ; + req->num = bio->bi_vcnt; + +dprintk ("%s: submitting: bio: %p, req: %p, start: %llu, " +"size: %llu, idx: %d, num: %d, offset: %u, csum: %x.\n" , + __func__, bio, req, req->start, req->size, + req->idx, req->num, req->offset, req->tmp_csum); + + err =kst_enqueue_req (st, req); +if (err) + goto err_out_put; + +if (r.cmd ==DST_READ ) { +generic_make_request (bio); + } + } + +kst_wake (st); +return 0 ; + +err_out_put: +bio_put (bio); +err_out_free_req: +dst_free_request (req); +err_out_exit: +return err; +} + +static void kst_export_exit (struct kst_state *st) +{ +struct dst_node *n = st->node; + +kst_common_exit (st); +dst_node_put (n); +} + +static struct kst_state_ops kst_data_export_ops = { + .init = &kst_data_init, + .push = &kst_data_push, + .exit = &kst_export_exit, + .ready = &kst_export_ready, +}; + +/* +* This callback is invoked each time listening socket for +* given local export node becomes ready. +* It creates new state for connected client and queues for processing. +*/ +static int kst_listen_ready (struct kst_state *st) +{ +struct socket *newsock; +struct saddr addr; +struct kst_state *newst; +int err; +unsigned int revents, permissions =0 ; +struct dst_secure *s; + + revents = st->socket->ops->poll (NULL , st->socket,NULL ); +if (!(revents &POLLIN )) +return 1 ; + + err =sock_create (st->socket->ops->family, st->socket->type, + st->socket->sk->sk_protocol, &newsock); +if (err) + goto err_out_exit; + + err = st->socket->ops->accept (st->socket, newsock,0 ); +if (err) + goto err_out_put; + +if (newsock->ops->getname (newsock, (struct sockaddr *)&addr, + (int *)&addr.sa_data_len,2 ) <0 ) { + err = -ECONNABORTED ; + goto err_out_put; + } + +list_for_each_entry (s, &st->request_list, sec_entry) { +void *sec_addr, *new_addr; + + sec_addr = ((void *)&s->sec.addr) + s->sec.check_offset; + new_addr = ((void *)&addr) + s->sec.check_offset; + +if (!memcmp (sec_addr, new_addr, + addr.sa_data_len - s->sec.check_offset)) { + permissions = s->sec.permissions; +break ; + } + } + +/* +* So far only reading and writing are supported. +* Block device does not know about anything else, +* but as far as I recall, there was a prognosis, +* that computer will never require more than 640kb of RAM. +*/ +if (permissions ==0 ) { + err = -EPERM ; + goto err_out_put; + } + +if (st->socket->ops->family ==AF_INET ) { +struct sockaddr_in *sin = (struct sockaddr_in *)&addr; +printk (KERN_INFO "%s: Client: %u.%u.%u.%u:%d.\n" , __func__, +NIPQUAD (sin->sin_addr.s_addr),ntohs (sin->sin_port)); + }else if (st->socket->ops->family ==AF_INET6 ) { +struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&addr; +printk (KERN_INFO "%s: Client: " +"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%d" , + __func__, +NIP6 (sin->sin6_addr),ntohs (sin->sin6_port)); + } + +dst_node_get (st->node); + newst =kst_state_init (st->node, permissions, + &kst_data_export_ops, newsock); +if (IS_ERR (newst)) { + err =PTR_ERR (newst); + goto err_out_put; + } + +/* +* Negative return value means error, positive - stop this state +* processing. Zero allows to check state for pending requests. +* Listening socket contains security objects in request list, +* since it does not have any requests. +*/ +return 1 ; + +err_out_put: +sock_release (newsock); +err_out_exit: +return 1 ; +} + +static int kst_listen_init (struct kst_state *st,void *data) +{ +int err = -ENOMEM , i; +struct dst_le_template *tmp = data; +struct dst_secure *s; + +for (i=0 ; i<tmp->le->secure_attr_num; ++i) { + s =kmalloc (sizeof (struct dst_secure ),GFP_KERNEL ); +if (!s) + goto err_out_exit; + +memcpy (&s->sec, tmp->data,sizeof (struct dst_secure_user )); + +list_add_tail (&s->sec_entry, &st->request_list); + tmp->data +=sizeof (struct dst_secure_user ); + +if (s->sec.addr.sa_family ==AF_INET ) { +struct sockaddr_in *sin = + (struct sockaddr_in *)&s->sec.addr; +printk (KERN_INFO "%s: Client: %u.%u.%u.%u:%d, " +"permissions: %x.\n" , + __func__,NIPQUAD (sin->sin_addr.s_addr), +ntohs (sin->sin_port), s->sec.permissions); + }else if (s->sec.addr.sa_family ==AF_INET6 ) { +struct sockaddr_in6 *sin = + (struct sockaddr_in6 *)&s->sec.addr; +printk (KERN_INFO "%s: Client: " +"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%d, " +"permissions: %x.\n" , + __func__,NIP6 (sin->sin6_addr), +ntohs (sin->sin6_port), s->sec.permissions); + } + } + + err =kst_sock_create (st, &tmp->le->rctl.addr, tmp->le->rctl.type, + tmp->le->rctl.proto, tmp->le->backlog); +if (err) + goto err_out_exit; + + err =kst_poll_init (st); +if (err) + goto err_out_release; + +return 0 ; + +err_out_release: +kst_sock_release (st); +err_out_exit: +kst_listen_flush (st); +return err; +} + +/* +* Operations for different types of states. +* There are three: +* data state - created for remote node, when distributed storage connects +* to remote node, which contain data. +* listen state - created for local export node, when remote distributed +* storage's node connects to given node to get/put data. +* data export state - created for each client connected to above listen +* state. +*/ +static struct kst_state_ops kst_listen_ops = { + .init = &kst_listen_init, + .exit = &kst_listen_exit, + .ready = &kst_listen_ready, +}; +static struct kst_state_ops kst_data_ops = { + .init = &kst_data_init, + .push = &kst_data_push, + .exit = &kst_common_exit, + .recovery = &kst_data_recovery, +}; + +struct kst_state *kst_listener_state_init (struct dst_node *node, +struct dst_le_template *tmp) +{ +return kst_state_init (node,DST_PERM_READ |DST_PERM_WRITE , + &kst_listen_ops, tmp); +} + +struct kst_state *kst_data_state_init (struct dst_node *node, +struct socket *newsock) +{ +return kst_state_init (node,DST_PERM_READ |DST_PERM_WRITE , + &kst_data_ops, newsock); +} + +/* +* Remove all workers and associated states. +*/ +void kst_exit_all (void ) +{ +struct kst_worker *w, *n; + +list_for_each_entry_safe (w, n, &kst_worker_list, entry) { +kst_worker_exit (w); + } +}
PATCH 4/4 DST: Algorithms used in distributed storage.
Algorithms used in distributed storage.
Mirror and linear mapping code.
drivers/block/dst/alg_linear.c new file +105 -0
@@ -0,0 +1,105 @@ +/* +* 2007+ Copyright (c) Evgeniy Polyakov <[email protected]> +* All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dst.h> + +static struct dst_alg *alg_linear; + +/* +* This callback is invoked when node is removed from storage. +*/ +static void dst_linear_del_node (struct dst_node *n) +{ +} + +/* +* This callback is invoked when node is added to storage. +*/ +static int dst_linear_add_node (struct dst_node *n) +{ +struct dst_storage *st = n->st; + +dprintk ("%s: disk_size: %llu, node_size: %llu.\n" , + __func__, st->disk_size, n->size); + +mutex_lock (&st->tree_lock); + n->start = st->disk_size; + st->disk_size += n->size; +set_capacity (st->disk, st->disk_size); +mutex_unlock (&st->tree_lock); + +return 0 ; +} + +static int dst_linear_remap (struct dst_request *req) +{ +int err; + +if (req->node->bdev) { +generic_make_request (req->bio); +return 0 ; + } + + err =kst_check_permissions (req->state, req->bio); +if (err) +return err; + +return req->state->ops->push (req); +} + +/* +* Failover callback - it is invoked each time error happens during +* request processing. +*/ +static int dst_linear_error (struct kst_state *st,int err) +{ +if (err) +set_bit (DST_NODE_FROZEN , &st->node->flags); +else +clear_bit (DST_NODE_FROZEN , &st->node->flags); +return 0 ; +} + +static struct dst_alg_ops alg_linear_ops = { + .remap = dst_linear_remap, + .add_node = dst_linear_add_node, + .del_node = dst_linear_del_node, + .error = dst_linear_error, + .owner =THIS_MODULE , +}; + +static int __devinit alg_linear_init (void ) +{ + alg_linear =dst_alloc_alg ("alg_linear" , &alg_linear_ops); +if (!alg_linear) +return -ENOMEM ; + +return 0 ; +} + +static void __devexit alg_linear_exit(void) +{ +dst_remove_alg (alg_linear); +} + +module_init (alg_linear_init); +module_exit (alg_linear_exit); + +MODULE_LICENSE ("GPL" ); +MODULE_AUTHOR ("Evgeniy Polyakov <[email protected]>" ); +MODULE_DESCRIPTION ("Linear distributed algorithm." );
drivers/block/dst/alg_mirror.c new file +1128 -0
1,128 lines not on this page · raw message
Re: [4/4] DST: Algorithms used in distributed storage.
Quoted 3 lines · Evgeniy Polyakov
Algorithms used in distributed storage.
Mirror and linear mapping code.
Hi, i've finally take a look on your DST solution.
It seems what your current implementation will not work on nonstandard devices for example software raid0.
other comments are follows:
Quoted 423 lines
Signed-off-by Evgeniy Polyakov <[email protected]>drivers/block/dst/alg_linear.c new file +105
-0@@ -0,0 +1,105 @@ +/* +* 2007+ Copyright (c) Evgeniy Polyakov <[email protected]> +* All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dst.h> + +static struct dst_alg *alg_linear; + +/* +* This callback is invoked when node is removed from storage. +*/ +static void dst_linear_del_node (struct dst_node *n) +{ +} + +/* +* This callback is invoked when node is added to storage. +*/ +static int dst_linear_add_node (struct dst_node *n) +{ +struct dst_storage *st = n->st; + +dprintk ("%s: disk_size: %llu, node_size: %llu.\n" , + __func__, st->disk_size, n->size); + +mutex_lock (&st->tree_lock); + n->start = st->disk_size; + st->disk_size += n->size; +set_capacity (st->disk, st->disk_size); +mutex_unlock (&st->tree_lock); + +return 0 ; +} + +static int dst_linear_remap (struct dst_request *req) +{ +int err; + +if (req->node->bdev) { +generic_make_request (req->bio); +return 0 ; + } + + err =kst_check_permissions (req->state, req->bio); +if (err) +return err; + +return req->state->ops->push (req); +} + +/* +* Failover callback - it is invoked each time error happens during +* request processing. +*/ +static int dst_linear_error (struct kst_state *st,int err) +{ +if (err) +set_bit (DST_NODE_FROZEN , &st->node->flags); +else +clear_bit (DST_NODE_FROZEN , &st->node->flags); +return 0 ; +} + +static struct dst_alg_ops alg_linear_ops = { + .remap = dst_linear_remap, + .add_node = dst_linear_add_node, + .del_node = dst_linear_del_node, + .error = dst_linear_error, + .owner =THIS_MODULE , +}; + +static int __devinit alg_linear_init (void ) +{ + alg_linear =dst_alloc_alg ("alg_linear" , &alg_linear_ops); +if (!alg_linear) +return -ENOMEM ; + +return 0 ; +} + +static void __devexit alg_linear_exit(void) +{ +dst_remove_alg (alg_linear); +} + +module_init (alg_linear_init); +module_exit (alg_linear_exit); + +MODULE_LICENSE ("GPL" ); +MODULE_AUTHOR ("Evgeniy Polyakov <[email protected]>" ); +MODULE_DESCRIPTION ("Linear distributed algorithm." );drivers/block/dst/alg_mirror.c new file +302
-0@@ -0,0 +1,1128 @@ +/* +* 2007+ Copyright (c) Evgeniy Polyakov <[email protected]> +* All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/dst.h> + +struct dst_mirror_node_data +{ +u64 age; +}; + +struct dst_mirror_priv +{ +unsigned int chunk_num; + +u64 last_start; + +spinlock_t backlog_lock; +struct list_head backlog_list; + +struct dst_mirror_node_data old_data, new_data; + +unsigned long *chunk; +}; + +static struct dst_alg *alg_mirror; +static struct bio_set *dst_mirror_bio_set; + +static int dst_mirror_resync (struct dst_node *n,int ndp); + +static void dst_mirror_mark_sync (struct dst_node *n) +{ +if (test_bit (DST_NODE_NOTSYNC , &n->flags)) { +struct dst_mirror_priv *priv = n->priv; + +clear_bit (DST_NODE_NOTSYNC , &n->flags); +dprintk ("%s: node: %p, %llu:%llu synchronization " +"has been completed.\n" , + __func__, n, n->start, n->size); + priv->old_data.age =0 ; + } +} + +static void dst_mirror_mark_notsync (struct dst_node *n) +{ +if (!test_bit (DST_NODE_NOTSYNC , &n->flags)) { +set_bit (DST_NODE_NOTSYNC , &n->flags); +dprintk ("%s: not synced node n: %p.\n" , __func__, n); + } +} + +static void dst_mirror_mark_node_notsync (struct dst_node *n) +{ +struct dst_mirror_priv *p = n->priv; + +memset (p->chunk,0xff ,DIV_ROUND_UP (p->chunk_num,BITS_PER_LONG )*sizeof (long )); +dst_mirror_mark_notsync (n); +dst_mirror_resync (n,0 ); +} + +static void dst_mirror_mark_node_sync (struct dst_node *n) +{ +struct dst_mirror_priv *p = n->priv; + +memset (p->chunk,0x0 ,DIV_ROUND_UP (p->chunk_num,BITS_PER_LONG )*sizeof (long )); +dst_mirror_mark_sync (n); +} + +static ssize_t dst_mirror_mark_dirty (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ + struct dst_node *n = container_of(dev, struct dst_node, device); + +dst_mirror_mark_node_notsync (n); +return count; +} + +static ssize_t dst_mirror_mark_clean (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ + struct dst_node *n = container_of(dev, struct dst_node, device); + +dst_mirror_mark_node_sync (n); +return count; +} + +static ssize_t dst_mirror_chunk_mask_show (struct device *dev, +struct device_attribute *attr,char *buf) +{ + struct dst_node *n = container_of(dev, struct dst_node, device); +struct dst_mirror_priv *priv = n->priv; +unsigned int i; +int rest =PAGE_SIZE , rest_bits = priv->chunk_num; + +for (i =0 ; i <DIV_ROUND_UP (priv->chunk_num,BITS_PER_LONG ); ++i) { +int bit, j; + +for (j =0 ; j <min (BITS_PER_LONG , rest_bits); ++j) { + bit = (priv->chunk[i] >> j) &1 ; +sprintf (buf,"%c" , (bit)?'+' :'-' ); + buf++; + } + + rest_bits -= j; + rest -= j; + +if (rest <BITS_PER_LONG || rest_bits <=0 ) +break ; + } + +return PAGE_SIZE - rest; +} + +static struct device_attribute dst_mirror_attrs[] = { +__ATTR (chunks,S_IRUGO , dst_mirror_chunk_mask_show,NULL ), +__ATTR (dirty,S_IWUSR ,NULL , dst_mirror_mark_dirty), +__ATTR (clean,S_IWUSR ,NULL , dst_mirror_mark_clean), +}; + +/* +* This callback is invoked when node is removed from storage. +*/ +static void dst_mirror_del_node (struct dst_node *n) +{ +struct dst_mirror_priv *priv = n->priv; +struct dst_request *req, *tmp; + +list_for_each_entry_safe (req, tmp, &priv->backlog_list, request_list_entry) { +kst_del_req (req); +kst_complete_req (req, -ENODEV ); + } + +if (priv) { +vfree (priv->chunk); +kfree (priv); + n->priv =NULL ; + } + +if (n->device.parent == &n->st->device) { +int i; + +for (i=0 ; i<ARRAY_SIZE (dst_mirror_attrs); ++i) +device_remove_file (&n->device, &dst_mirror_attrs[i]); + } +} + +static void dst_mirror_handle_priv (struct dst_node *n) +{ +if (n->priv) { +int err, i; + +for (i=0 ; i<ARRAY_SIZE (dst_mirror_attrs); ++i) + err =device_create_file (&n->device, + &dst_mirror_attrs[i]); + } +} + +static void dst_mirror_destructor (struct bio *bio) +{ +dprintk ("%s: bio: %p.\n" , __func__, bio); +bio_free (bio, dst_mirror_bio_set); +} + +/* +* This function copies node's private on-disk data from first node +* to the new one. +*/ +static int dst_mirror_get_node_data (struct dst_node *n, +struct dst_mirror_node_data *ndata,int old) +{ +struct dst_node *first; +struct dst_mirror_priv *p; + +mutex_lock (&n->st->tree_lock); + first =dst_storage_tree_search (n->st, n->start); +mutex_unlock (&n->st->tree_lock); +if (!first) { +dprintk ("%s: there are no nodes in the storage.\n" , __func__); +return -ENODEV ; + } + + p = first->priv; +memcpy (ndata, (old)?&p->old_data:&p->new_data,sizeof (struct dst_mirror_node_data )); + +dst_node_put (first); +return 0 ; +} + +struct dst_mirror_ndp +{ +int err; +struct page *page; +struct completion complete; +}; + +static void dst_mirror_ndb_complete (struct dst_mirror_ndp *cmp,int err) +{ + cmp->err = err; +dprintk ("%s: completing request: cmp: %p, err: %d.\n" , + __func__, cmp, err); +complete (&cmp->complete); +} + +static void dst_mirror_ndp_bio_endio (struct dst_request *req,int err) +{ +struct dst_mirror_ndp *cmp = req->bio->bi_private; + +dst_mirror_ndb_complete (cmp, err); +} + +static int dst_mirror_ndp_end_io (struct bio *bio,unsigned int size,int err) +{ +struct dst_mirror_ndp *cmp = bio->bi_private; + +if (bio->bi_size) +return 0 ; + +dst_mirror_ndb_complete (cmp, err); +return 0 ; +} + +/* +* This function reads or writes node's private data from underlying media. +*/ +static int dst_mirror_process_node_data (struct dst_node *n, +struct dst_mirror_node_data *ndata,int op) +{ +struct bio *bio; +int err = -ENOMEM ; +struct dst_mirror_ndp *cmp; +void *addr; + + cmp =kzalloc (sizeof (struct dst_mirror_ndp ),GFP_KERNEL ); +if (!cmp) + goto err_out_exit; + + cmp->page =alloc_page (GFP_NOIO ); +if (!cmp->page) + goto err_out_free_cmp; + + addr =kmap (cmp->page); + +init_completion (&cmp->complete); + +if (op ==WRITE ) +memcpy (addr, ndata,sizeof (struct dst_mirror_node_data )); + + bio =bio_alloc_bioset (GFP_NOIO ,1 , dst_mirror_bio_set); +if (!bio) + goto err_out_free_page; + + bio->bi_rw = op; + bio->bi_private = cmp; + bio->bi_sector = n->size; + bio->bi_bdev = n->bdev; + bio->bi_destructor = dst_mirror_destructor; + bio->bi_end_io = dst_mirror_ndp_end_io; + + err =bio_add_pc_page (n->st->queue, bio, cmp->page,512 ,0 ); +if (err <=0 ) + goto err_out_free_bio; + +if (n->bdev) { +generic_make_request (bio); + }else { +struct dst_request req; + +memset (&req,0 ,sizeof (struct dst_request )); + + req.node = n; + req.state = n->state; + req.start = bio->bi_sector; + req.size = req.orig_size = bio->bi_size; + req.bio = bio; + req.idx = bio->bi_idx; + req.num = bio->bi_vcnt; + req.flags =0 ; + req.offset =0 ; + req.bio_endio = &dst_mirror_ndp_bio_endio; + req.callback = &kst_data_callback; + + err = req.state->ops->push (&req); +if (err) + req.bio_endio (&req, err); + } + + dprintk("%s: waiting for completion: bio: %p, cmp: %p.\n",
drivers/block/dst/alg_mirror.c line 312
Quoted 10 lines
drivers/block/dst/alg_mirror.c new file +10
-0+ __func__, bio, cmp); + +wait_for_completion (&cmp->complete); + + err = cmp->err; + +if (!err && (op !=WRITE )) +memcpy (ndata, addr,sizeof (struct dst_mirror_node_data )); + +kunmap (cmp->page);
<< MINOR_BUG:
You has forgot to unmap page on error path, so IMHO it is better to move kunmap to "err_out_free_cmp" label.
Quoted 110 lines
drivers/block/dst/alg_mirror.c new file +110
-0+ +dprintk ("%s: freeing bio: %p, err: %d.\n" , __func__, bio, err); + +err_out_free_bio: +bio_put (bio); +err_out_free_page: +__free_page (cmp->page); +err_out_free_cmp: +kfree (cmp); +err_out_exit: +return err; +} + +/* +* This function reads node's private data from underlying media. +*/ +static int dst_mirror_read_node_data (struct dst_node *n, +struct dst_mirror_node_data *ndata) +{ +return dst_mirror_process_node_data (n, ndata,READ ); +} + +/* +* This function writes node's private data from underlying media. +*/ +static int dst_mirror_write_node_data (struct dst_node *n, +struct dst_mirror_node_data *ndata) +{ +dprintk ("%s: writing new age: %llx, node: %p %llu-%llu.\n" , + __func__, ndata->age, n, n->start, n->size); +return dst_mirror_process_node_data (n, ndata,WRITE ); +} + +static int dst_mirror_ndp_setup (struct dst_node *n,int first_node,int clean_on_sync) +{ +struct dst_mirror_priv *p = n->priv; +int sync =1 , err; + + err =dst_mirror_read_node_data (n, &p->old_data); +if (err) +return err; + +if (first_node) { + p->new_data.age = *(u64 *)&n->st; + +dprintk ("%s: first age: %llx -> %llx. " +"Old will be set to new for the first node.\n" , + __func__, p->old_data.age, p->new_data.age); + + err =dst_mirror_write_node_data (n, &p->new_data); +if (err) +return err; + p->old_data.age = p->new_data.age; + }else { + err =dst_mirror_get_node_data (n, &p->new_data,1 ); +if (err) +return err; + +if (p->new_data.age != p->old_data.age) { + sync =0 ; +dprintk ("%s: node %llu:%llu is not synced with the first " +"node (old != new): %llx != %llx.\n" , + __func__, n->start, n->start+n->size, + p->old_data.age, p->new_data.age); + err =dst_mirror_get_node_data (n, &p->new_data,0 ); +if (err) +return err; + }else { + err =dst_mirror_get_node_data (n, &p->new_data,0 ); +if (err) +return err; + + err =dst_mirror_write_node_data (n, &p->new_data); +if (err) +return err; + +dprintk ("%s: node %llu:%llu is in sync with the first node.\n" , + __func__, n->start, n->start+n->size); + } + } + +if (!sync) +dst_mirror_mark_node_notsync (n); +else if (clean_on_sync) +dst_mirror_mark_node_sync (n); + +dprintk ("%s: age: old: %llx, new: %llx.\n" , __func__, p->old_data.age, p->new_data.age); + +return 0 ; +} + +/* +* This callback is invoked when node is added to storage. +*/ +static int dst_mirror_add_node (struct dst_node *n) +{ +struct dst_storage *st = n->st; +struct dst_mirror_priv *priv; +int err = -ENOMEM , first_node =0 ; +u64 disk_size; + + n->size--;/* A sector size actually. */ + +mutex_lock (&st->tree_lock); + disk_size = st->disk_size; +if (st->disk_size) { + st->disk_size =min (n->size, st->disk_size); + }else { + st->disk_size = n->size; + first_node =1 ;
drivers/block/dst/alg_mirror.c line 432
Quoted 10 lines
drivers/block/dst/alg_mirror.c new file +10
-0+ } +mutex_unlock (&st->tree_lock); + + priv =kzalloc (sizeof (struct dst_mirror_priv ),GFP_KERNEL ); +if (!priv) +return -ENOMEM ; + + priv->chunk_num = st->disk_size; + + priv->chunk =vmalloc (DIV_ROUND_UP (priv->chunk_num,BITS_PER_LONG ) *sizeof (long ));
<< Ohhh. My. I want to add my 500G hdd. Do you really wanna
say what i have to store 128Mb in memory object for this.
Quoted 213 lines
drivers/block/dst/alg_mirror.c new file +213
-0+if (!priv->chunk) + goto err_out_free; + +spin_lock_init (&priv->backlog_lock); +INIT_LIST_HEAD (&priv->backlog_list); + +dprintk ("%s: %llu:%llu, chunk_num: %u, disk_size: %llu.\n\n" , + __func__, n->start, n->size, + priv->chunk_num, st->disk_size); + + n->priv_callback = &dst_mirror_handle_priv; + n->priv = priv; + + err =dst_mirror_ndp_setup (n, first_node,1 ); +if (err) + goto err_out_free_chunk; + +return 0 ; + +err_out_free_chunk: +vfree (priv->chunk); +err_out_free: +kfree (priv); + n->priv =NULL ; + +mutex_lock (&st->tree_lock); + st->disk_size = disk_size; +mutex_unlock (&st->tree_lock); +return err; +} + +static void dst_mirror_sync_destructor (struct bio *bio) +{ +struct bio_vec *bv; +int i; + +bio_for_each_segment (bv, bio, i) +__free_page (bv->bv_page); +bio_free (bio, dst_mirror_bio_set); +} + +/* +* Without errors it is always called under node's request lock, +* so it is safe to requeue them. +*/ +static void dst_mirror_bio_error (struct dst_request *req,int err) +{ +int i; +struct dst_mirror_priv *priv = req->node->priv; +unsigned int num, idx; +u64 start = req->start -to_sector (req->orig_size - req->size); + +if (err) +dst_mirror_mark_notsync (req->node); + + priv->last_start = req->start; + + idx = start; + num =to_sector (req->orig_size); + +dprintk ("%s: %llu:%llu start: %llu, size: %llu, " +"chunk_num: %u, idx: %d, num: %d, err: %d, node: %p.\n" , + __func__, req->node->start, req->node->size, + start, req->orig_size, priv->chunk_num, + idx, num, err, req->node); + +if (unlikely (idx >= priv->chunk_num || idx + num > priv->chunk_num)) { +dprintk ("%s: %llu:%llu req: %p, start: %llu, orig_size: %llu, " +"req_start: %llu, req_size: %llu, " +"chunk_num: %u, idx: %d, num: %d, err: %d.\n" , + __func__, req->node->start, req->node->size, req, + start, req->orig_size, + req->start, req->size, + priv->chunk_num, idx, num, err); +return ; + } + +if (err) { +for (i=0 ; i<num; ++i) +__set_bit (idx+i, priv->chunk); + }else { +for (i=0 ; i<num; ++i) +__clear_bit (idx+i, priv->chunk); + } +} + +static void dst_mirror_sync_req_endio (struct dst_request *req,int err) +{ +struct dst_node *n = req->node; +struct dst_mirror_priv *p = req->node->priv; +int i; +unsigned long notsync =0 ; + +dst_mirror_bio_error (req, err); + +dprintk ("%s: freeing bio: %p, bi_size: %u, " +"orig_size: %llu, req: %p, node: %p.\n" , + __func__, req->bio, req->bio->bi_size, req->orig_size, req, + req->node); + +bio_put (req->bio); + +if (!test_bit (DST_NODE_NOTSYNC , &n->flags)) +return ; + +for (i =0 ; i <DIV_ROUND_UP (p->chunk_num,BITS_PER_LONG ); ++i) { + notsync = p->chunk[i]; +if (notsync) +break ; + } + +if (notsync) { +dprintk ("%s: %d/%d sync: %lx, ffs: %lu, chunk_num (modulo %u): %d.\n" , + __func__, i,DIV_ROUND_UP (p->chunk_num,BITS_PER_LONG ), + notsync,__ffs (notsync),BITS_PER_LONG , + p->chunk_num%BITS_PER_LONG ); +if (i !=DIV_ROUND_UP (p->chunk_num,BITS_PER_LONG ) -1 ) +return ; + +if (__ffs (notsync) != (p->chunk_num%BITS_PER_LONG )) +return ; + } + +dst_mirror_mark_sync (n); +} + +static int dst_mirror_sync_endio (struct bio *bio,unsigned int size,int err) +{ +struct dst_request *req = bio->bi_private; +struct dst_node *n = req->node; +struct dst_mirror_priv *priv = n->priv; +unsigned long flags; + +dprintk ("%s: bio: %p, err: %d, size: %u, req: %p.\n" , + __func__, bio, err, bio->bi_size, req); + +if (bio->bi_size) +return 1 ; + + bio->bi_rw =WRITE ; + bio->bi_size = req->orig_size; + bio->bi_sector = req->start; + +if (!err) { +spin_lock_irqsave (&priv->backlog_lock, flags); +list_add_tail (&req->request_list_entry, &priv->backlog_list); +spin_unlock_irqrestore (&priv->backlog_lock, flags); +kst_wake (req->state); + }else { + req->bio_endio (req, err); +dst_free_request (req); + } +return 0 ; +} + +static int dst_mirror_sync_block (struct dst_node *n, +int bit_start,int bit_num) +{ +u64 start =to_bytes (bit_start); +u32 size =to_bytes (bit_num); +struct bio *bio; +unsigned int nr_pages =DIV_ROUND_UP (size,PAGE_SIZE ), i; +struct page *page; +int err = -ENOMEM ; +struct dst_request *req; + +dprintk ("%s: bit_start: %d, bit_num: %d, start: %llu, nr_pages: %u, " +"disk_size: %llu.\n" , + __func__, bit_start, bit_num, start, nr_pages, + n->st->disk_size); + +while (nr_pages) { + req =dst_clone_request (NULL , n->w->req_pool); +if (!req) +return -ENOMEM ; + + bio =bio_alloc_bioset (GFP_NOIO , nr_pages, dst_mirror_bio_set); +if (!bio) + goto err_out_free_req; + + bio->bi_rw =READ ; + bio->bi_private = req; + bio->bi_sector =to_sector (start); + bio->bi_bdev =NULL ; + bio->bi_destructor = dst_mirror_sync_destructor; + bio->bi_end_io = dst_mirror_sync_endio; + +for (i =0 ; i < nr_pages; ++i) { + err = -ENOMEM ; + + page =alloc_page (GFP_NOIO ); +if (!page) +break ; + + err =bio_add_pc_page (n->st->queue, bio, page, +min_t (u32,PAGE_SIZE , size),0 ); +if (err <=0 ) +break ; + size -= err; + err =0 ; + } + +if (err && !bio->bi_vcnt) + goto err_out_put_bio; + + req->node = n; + req->state = n->state; + req->start = bio->bi_sector; + req->size = req->orig_size = bio->bi_size; + req->bio = bio; + req->idx = bio->bi_idx; + req->num = bio->bi_vcnt; + req->flags =DST_REQ_CHECK_QUEUE ;
drivers/block/dst/alg_mirror.c line 655
Quoted 10 lines
drivers/block/dst/alg_mirror.c new file +10
-0+ req->offset =0 ; + req->bio_endio = &dst_mirror_sync_req_endio; + req->callback = &kst_data_callback; + +dprintk ("%s: start: %llu, size: %llu/%u, bio: %p, req: %p, " +"node: %p.\n" , + __func__, req->start, req->size, nr_pages, bio, + req, req->node); + + err = n->st->queue->make_request_fn (n->st->queue, bio);
<< Why direct make_request_fn instead of generic_make_request?
Quoted 46 lines
drivers/block/dst/alg_mirror.c new file +46
-0+if (err) + goto err_out_put_bio; + + nr_pages -= bio->bi_vcnt; + start += bio->bi_size; + } + +return 0 ; + +err_out_put_bio: +bio_put (bio); +err_out_free_req: +dst_free_request (req); +return err; +} + +/* +* Resync logic. +* +* System allocates and queues requests for number of regions. +* Each request initially is reading from the one of the nodes. +* When it is completed, system checks if given region was already +* written to, and in such case just drops read request, otherwise +* it writes it to the node being updated. Any write clears not-uptodate +* bit, which is used as a flag that region must be synchronized or not. +* Reading is never performed from the node under resync. +*/ +static int dst_mirror_resync (struct dst_node *n,int ndp) +{ +struct dst_mirror_priv *priv = n->priv; +int err =0 , sync =1 , total = priv->chunk_num; +unsigned int i; + +dprintk ("%s: node: %p, %llu:%llu synchronization has been started.\n" , + __func__, n, n->start, n->size); + +if (ndp) { + err =dst_mirror_ndp_setup (n,0 ,0 ); +if (err) +return err; + } + +for (i =0 ; i <DIV_ROUND_UP (priv->chunk_num,BITS_PER_LONG ); ++i) { +int bit, num, start; +unsigned long word = priv->chunk[i]; +
drivers/block/dst/alg_mirror.c line 711
Quoted 10 lines
drivers/block/dst/alg_mirror.c new file +10
-0+if (!word) +continue ; + + num =0 ; + start =-1 ; +while (word && num <BITS_PER_LONG ) { + bit =__ffs (word); +if (start ==-1 ) + start = bit; + num++;
<< MINOR_BUG: Seems you have misstyped here. AFAIU @num represent position
of last non zero bit (start + num == last_non_zero_bit_pos)
if (start == -1) {
start = bit;
num = 1;
} else
num += bit;Quoted 423 lines
drivers/block/dst/alg_mirror.c new file +417
-1+ word >>= (bit+1 ); + +if (--total ==0 ) +break ; + } + +if (start !=-1 ) { + err =dst_mirror_sync_block (n, start + i*BITS_PER_LONG , + num); +if (err) +break ; + sync =0 ; + } + +if (total ==0 ) +break ; + } + +return err; +} + +static int dst_mirror_end_io (struct bio *bio,unsigned int size,int err) +{ +struct dst_request *req = bio->bi_private; + +if (bio->bi_size) +return 0 ; + +dprintk ("%s: req: %p, bio: %p, req->bio: %p, err: %d.\n" , + __func__, req, bio, req->bio, err); + req->bio_endio (req, err); +bio_put (bio); +return 0 ; +} + +static void dst_mirror_read_endio (struct dst_request *req,int err) +{ +dst_mirror_bio_error (req, err); + +if (err && req->state) +kst_wake (req->state); + +if (!err || req->callback) +kst_bio_endio (req, err); +} + +static void dst_mirror_write_endio (struct dst_request *req,int err) +{ +dst_mirror_bio_error (req, err); + +if (err && req->state) +kst_wake (req->state); + + req = req->priv; + +dprintk ("%s: req: %p, priv: %p err: %d, bio: %p, " +"cnt: %d, orig_size: %llu.\n" , + __func__, req, req->priv, err, req->bio, +atomic_read (&req->refcnt), req->orig_size); + +if (atomic_dec_and_test (&req->refcnt)) { +bio_endio (req->bio, req->orig_size,0 ); +dst_free_request (req); + } +} + +static int dst_mirror_process_request_nosync (struct dst_request *req, +struct dst_node *n) +{ +int err =0 ; + +/* +* Block layer requires to clone a bio. +*/ +if (n->bdev) { +struct bio *clone =bio_alloc_bioset (GFP_NOIO , + req->bio->bi_max_vecs, dst_mirror_bio_set); + +__bio_clone (clone, req->bio); + + clone->bi_bdev = n->bdev; + clone->bi_destructor = dst_mirror_destructor; + clone->bi_private = req; + clone->bi_end_io = &dst_mirror_end_io; + +dprintk ("%s: clone: %p, bio: %p, req: %p.\n" , + __func__, clone, req->bio, req); + +generic_make_request (clone); + }else { +struct dst_request nr; +/* +* Network state processing engine will clone request +* by itself if needed. We can not use the same structure +* here, since number of its fields will be modified. +*/ +memcpy (&nr, req,sizeof (struct dst_request )); + + nr.node = n; + nr.state = n->state; + nr.priv = req; + + err =kst_check_permissions (n->state, req->bio); +if (!err) + err = n->state->ops->push (&nr); + } + +dprintk ("%s: req: %p, n: %p, bdev: %p, err: %d.\n" , + __func__, req, n, n->bdev, err); + +return err; +} + +static void dst_mirror_sync_requeue (struct dst_node *n) +{ +struct dst_mirror_priv *p = n->priv; +struct dst_request *req; +unsigned int num, idx, i; +u64 start; +unsigned long flags; +int err; + +while (!list_empty (&p->backlog_list)) { + req =NULL ; +spin_lock_irqsave (&p->backlog_lock, flags); +if (!list_empty (&p->backlog_list)) { + req =list_entry (p->backlog_list.next, + struct dst_request, + request_list_entry); +list_del_init (&req->request_list_entry); + } +spin_unlock_irqrestore (&p->backlog_lock, flags); + +if (!req) +break ; + + start = req->start -to_sector (req->orig_size - req->size); + + idx = start; + num =to_sector (req->orig_size); + +for (i=0 ; i<num; ++i) +if (test_bit (idx+i, p->chunk)) +break ; + +dprintk ("%s: idx: %u, num: %u, i: %u, req: %p, " +"start: %llu, size: %llu.\n" , + __func__, idx, num, i, req, + req->start, req->orig_size); + + err =-1 ; +if (i != num) { + err =dst_mirror_process_request_nosync (req, n); +if (!err) +dst_free_request (req); + } + +if (err) +kst_complete_req (req, err); + } + +if (!test_bit (DST_NODE_NOTSYNC , &n->flags) && + p->old_data.age != p->new_data.age) { +dst_mirror_write_node_data (n, &p->new_data); + p->old_data.age = p->new_data.age; + } +} + +static int dst_mirror_process_request (struct dst_request *req, +struct dst_node *n) +{ +dst_mirror_sync_requeue (n); +return dst_mirror_process_request_nosync (req, n); +} + +static int dst_mirror_write (struct dst_request *oreq) +{ +struct dst_node *n, *node = oreq->node; +struct dst_request *req; +int num, err =0 , err_num =0 , orig_num; + + req =dst_clone_request (oreq, oreq->node->w->req_pool); +if (!req) { + err = -ENOMEM ; + goto err_out_exit; + } + + req->priv = req; + +/* +* This logic is pretty simple - req->bio_endio will not +* call bio_endio() until all mirror devices completed +* processing of the request (no matter with or without error). +* Mirror's req->bio_endio callback will take care of that. +*/ + orig_num = num =atomic_read (&req->node->shared_num) +1 ; +atomic_set (&req->refcnt, num); + + req->bio_endio = &dst_mirror_write_endio; + +dprintk ("\n%s: req: %p, mirror to %d nodes.\n" , + __func__, req, num); + + err =dst_mirror_process_request (req, node); +if (err) + err_num++; + +if (--num) { +list_for_each_entry (n, &node->shared, shared) { +dprintk ("\n%s: req: %p, start: %llu, size: %llu, " +"num: %d, n: %p, state: %p.\n" , + __func__, req, req->start, + req->size, num, n, n->state); + + err =dst_mirror_process_request (req, n); +if (err) + err_num++; + +if (--num <=0 ) +break ; + } + } + +if (err_num == orig_num) { +dprintk ("%s: req: %p, num: %d, err: %d.\n" , + __func__, req, num, err); + err = -ENODEV ; + goto err_out_exit; + } + +return 0 ; + +err_out_exit: +return err; +} + +static int dst_mirror_read (struct dst_request *req) +{ +struct dst_node *node = req->node, *n, *min_dist_node; +struct dst_mirror_priv *priv = node->priv; +u64 dist, d; +int err; + + req->bio_endio = &dst_mirror_read_endio; + +do { + err = -ENODEV ; + min_dist_node =NULL ; + dist =-1ULL ; + +/* +* Reading is never performed from the node under resync. +* If this will cause any troubles (like all nodes must be +* resynced between each other), this check can be removed +* and per-chunk dirty bit can be tested instead. +*/ + +if (!test_bit (DST_NODE_NOTSYNC , &node->flags)) { + priv = node->priv; +if (req->start > priv->last_start) + dist = req->start - priv->last_start; +else + dist = priv->last_start - req->start; + min_dist_node = req->node; + } + +list_for_each_entry (n, &node->shared, shared) { +if (test_bit (DST_NODE_NOTSYNC , &n->flags)) +continue ; + + priv = n->priv; + +if (req->start > priv->last_start) + d = req->start - priv->last_start; +else + d = priv->last_start - req->start; + +if (d < dist) + min_dist_node = n; + } + +if (!min_dist_node) +break ; + + req->node = min_dist_node; + req->state = req->node->state; + +if (req->node->bdev) { + req->bio->bi_bdev = req->node->bdev; +generic_make_request (req->bio); + err =0 ; +break ; + } + + err = req->state->ops->push (req); +if (err) { +dprintk ("%s: req: %p, bio: %p, node: %p, err: %d.\n" , + __func__, req, req->bio, min_dist_node, err); +dst_mirror_mark_notsync (req->node); + } + }while (err && min_dist_node); + +if (err || !min_dist_node) { +dprintk ("%s: req: %p, bio: %p, node: %p, err: %d.\n" , + __func__, req, req->bio, min_dist_node, err); +if (!err) + err = -ENODEV ; + } +dprintk ("%s: req: %p, err: %d.\n" , __func__, req, err); +return err; +} + +/* +* This callback is invoked from block layer request processing function, +* its task is to remap block request to different nodes. +*/ +static int dst_mirror_remap (struct dst_request *req) +{ +int (*remap[])(struct dst_request *) = + {&dst_mirror_read, &dst_mirror_write}; + +return remap[bio_rw (req->bio) ==WRITE ](req); +} + +static int dst_mirror_error (struct kst_state *st,int err) +{ +struct dst_request *req, *tmp; +unsigned int revents = st->socket->ops->poll (NULL , st->socket,NULL ); + +dprintk ("%s: err: %d, revents: %x, notsync: %d.\n" , + __func__, err, revents, +test_bit (DST_NODE_NOTSYNC , &st->node->flags)); + +if (err == -EEXIST ) +return err; + +if (!(revents & (POLLERR |POLLHUP )) && + (err == -EPIPE || err == -ECONNRESET )) { +if (test_bit (DST_NODE_NOTSYNC , &st->node->flags)) { +return dst_mirror_resync (st->node,1 ); + } +return 0 ; + } + +if (atomic_read (&st->node->shared_num) ==0 && + !st->node->shared_head) { +dprintk ("%s: this node is the only one in the mirror, " +"can not mark it notsync.\n" , __func__); +return err; + } + +dst_mirror_mark_notsync (st->node); + +mutex_lock (&st->request_lock); +list_for_each_entry_safe (req, tmp, &st->request_list, + request_list_entry) { +kst_del_req (req); +dprintk ("%s: requeue [%c], start: %llu, idx: %d," +" num: %d, size: %llu, offset: %u, err: %d.\n" , + __func__, (bio_rw (req->bio) ==WRITE )?'W' :'R' , + req->start, req->idx, req->num, req->size, + req->offset, err); + +if (bio_rw (req->bio) !=WRITE ) { + req->start -=to_sector (req->orig_size - req->size); + req->size = req->orig_size; + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + req->idx =0 ; +if (dst_mirror_read (req)) +dst_free_request (req); + }else { +kst_complete_req (req, err); + } + } +mutex_unlock (&st->request_lock); +return err; +} + +static struct dst_alg_ops alg_mirror_ops = { + .remap = dst_mirror_remap, + .add_node = dst_mirror_add_node, + .del_node = dst_mirror_del_node, + .error = dst_mirror_error, + .owner =THIS_MODULE , +}; + +static int __devinit alg_mirror_init (void ) +{ +int err = -ENOMEM ; + + dst_mirror_bio_set =bioset_create (256 ,256 ); +if (!dst_mirror_bio_set) +return -ENOMEM ; + + alg_mirror =dst_alloc_alg ("alg_mirror" , &alg_mirror_ops); +if (!alg_mirror) + goto err_out; + +return 0 ; + +err_out: +bioset_free (dst_mirror_bio_set); +return err; +} + +static void __devexit alg_mirror_exit(void) +{ +dst_remove_alg (alg_mirror); +bioset_free (dst_mirror_bio_set); +} + +module_init (alg_mirror_init); +module_exit (alg_mirror_exit); + +MODULE_LICENSE ("GPL" ); +MODULE_AUTHOR ("Evgeniy Polyakov <[email protected]>" ); +MODULE_DESCRIPTION ("Mirror distributed algorithm." ); --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Re: [4/4] DST: Algorithms used in distributed storage.
Quoted 9 lines · Dmitry Monakhov
Quoted 3 lines · Evgeniy Polyakov
Algorithms used in distributed storage.
Mirror and linear mapping code.Hi, i've finally take a look on your DST solution.
It seems what your current implementation will not work on nonstandard devices for example software raid0.
other comments are follows:
Quoted 8 lines
drivers/block/dst/alg_mirror.c
Quoted 5 lines
drivers/block/dst/alg_mirror.c new file +4
-0+static int dst_mirror_process_node_data(struct dst_node *n, + struct dst_mirror_node_data *ndata, int op) + + kunmap(cmp->page);<<
MINOR_BUG:
You has forgot to unmap page on error path, so IMHO it is better to move kunmap to "err_out_free_cmp" label.
Yep, I will fix this.
Quoted 9 lines
drivers/block/dst/alg_mirror.c line 432
Quoted 7 lines
drivers/block/dst/alg_mirror.c new file +7
-0+ priv =kzalloc (sizeof (struct dst_mirror_priv ),GFP_KERNEL ); +if (!priv) +return -ENOMEM ; + + priv->chunk_num = st->disk_size; + + priv->chunk =vmalloc (DIV_ROUND_UP (priv->chunk_num,BITS_PER_LONG ) *sizeof (long ));<< Ohhh. My. I want to add my 500G hdd. Do you really wanna
say what i have to store 128Mb in memory object for this.
Right now yes. There was a code which used single bit for bigger data units, but I dropped it because of resync troubles (i.e. when one single sector has been updated, it requires to resync the whole block). I can not say which case is better though.
Quoted 7 lines
drivers/block/dst/alg_mirror.c line 655
Quoted 6 lines
drivers/block/dst/alg_mirror.c new file +6
-0+dprintk ("%s: start: %llu, size: %llu/%u, bio: %p, req: %p, " +"node: %p.\n" , + __func__, req->start, req->size, nr_pages, bio, + req, req->node); + + err = n->st->queue->make_request_fn (n->st->queue, bio);<< Why direct
make_request_fninstead ofgeneric_make_request?
generic_make_request() will queue the bio in this case, so I call request_fn directly.
Quoted 16 lines
Quoted 4 lines
drivers/block/dst/alg_mirror.c new file +4
-0+for (i =0 ; i <DIV_ROUND_UP (priv->chunk_num,BITS_PER_LONG ); ++i) { +int bit, num, start; +unsigned long word = priv->chunk[i]; +drivers/block/dst/alg_mirror.c line 711
Quoted 10 lines
drivers/block/dst/alg_mirror.c new file +10
-0+if (!word) +continue ; + + num =0 ; + start =-1 ; +while (word && num <BITS_PER_LONG ) { + bit =__ffs (word); +if (start ==-1 ) + start = bit; + num++;<<
MINOR_BUG: Seems you have misstyped here. AFAIU @num represent position
of last non zero bit (start + num ==last_non_zero_bit_pos)
Quoted 5 lines
if (start == -1) {
start = bit;
num = 1;
} else
num += bit;Yes, you are right of course.
Since I shift word to more than a single bit, @num has to be update accordingly.
Quoted 1 line
drivers/block/dst/alg_mirror.c line 712
Quoted 1 line
drivers/block/dst/alg_mirror.c new file +1
-0+ word >>= (bit+1 );
Dmitry, thanks a lot for comments, I will fix issues you pointed in the next release, although will stay bitmap case opened for a while.
Signature · 1 line
Evgeniy Polyakov
Re: [3/4] DST: Network state machine.
In reply to Evgeniy PolyakovQuoted 4 lines · Evgeniy Polyakov
Network state machine.
Includes network async processing state machine and related tasks.
Hi, I've tried to play a little bit with DST and discover huge memory leak. Every read request from remote node result in bio + bio's pages leak.
Data flow:
->kst_export_ready ## prepare and submit bio
->generic_make_request(bio) ## submit it
->kst_export_read_end_io ## block layer call bio_end_io callback
->kst_thread_process_state ## process ready requests
->kst_data_callback
->kst_data_process_bio ## submit pages to network layer
->kst_complete_req
->kst_bio_endio
->kst_export_read_end_io ## WoW we calling the same bio_end_io
## callback twice
->dst_free_request(req); ## request will be destroyed but it's bio
## and all bio's pages wasn't released.We may release bio's pages after it was sent to network, it is safe because sendpage() already called get_page(). I've attached simple patch which this this.
Quoted 1,524 lines
Signed-off-by Evgeniy Polyakov <[email protected]>drivers/block/dst/kst.c new file +1512
-1@@ -0,0 +1,1513 @@ +/* +* 2007+ Copyright (c) Evgeniy Polyakov <[email protected]>> + * All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/socket.h> +#include <linux/kthread.h> +#include <linux/net.h> +#include <linux/in.h> +#include <linux/poll.h> +#include <linux/bio.h> +#include <linux/dst.h> + +#include <net/sock.h> + +struct kst_poll_helper +{ +poll_table pt; +struct kst_state *st; +}; + +static LIST_HEAD (kst_worker_list); +static DEFINE_MUTEX (kst_worker_mutex); + +/* +* This function creates bound socket for local export node. +*/ +static int kst_sock_create (struct kst_state *st,struct saddr *addr, +int type,int proto,int backlog) +{ +int err; + + err =sock_create (addr->sa_family, type, proto, &st->socket); +if (err) + goto err_out_exit; + + err = st->socket->ops->bind (st->socket, (struct sockaddr *)addr, + addr->sa_data_len); + + err = st->socket->ops->listen (st->socket, backlog); +if (err) + goto err_out_release; + + st->socket->sk->sk_allocation =GFP_NOIO ; + +return 0 ; + +err_out_release: +sock_release (st->socket); +err_out_exit: +return err; +} + +static void kst_sock_release (struct kst_state *st) +{ +if (st->socket) { +sock_release (st->socket); + st->socket =NULL ; + } +} + +void kst_wake (struct kst_state *st) +{ +if (st) { +struct kst_worker *w = st->node->w; +unsigned long flags; + +spin_lock_irqsave (&w->ready_lock, flags); +if (list_empty (&st->ready_entry)) +list_add_tail (&st->ready_entry, &w->ready_list); +spin_unlock_irqrestore (&w->ready_lock, flags); + +wake_up (&w->wait); + } +} +EXPORT_SYMBOL_GPL (kst_wake); + +/* +* Polling machinery. +*/ +static int kst_state_wake_callback (wait_queue_t *wait,unsigned mode, +int sync,void *key) +{ + struct kst_state *st = container_of(wait, struct kst_state, wait); +kst_wake (st); +return 1 ; +} + +static void kst_queue_func (struct file *file,wait_queue_head_t *whead, +poll_table *pt) +{ + struct kst_state *st = container_of(pt, struct kst_poll_helper, pt)->st; + + st->whead = whead; +init_waitqueue_func_entry (&st->wait, kst_state_wake_callback); +add_wait_queue (whead, &st->wait); +} + +static void kst_poll_exit (struct kst_state *st) +{ +if (st->whead) { +remove_wait_queue (st->whead, &st->wait); + st->whead =NULL ; + } +} + +/* +* This function removes request from state tree and ordering list. +*/ +void kst_del_req (struct dst_request *req) +{ +list_del_init (&req->request_list_entry); +} +EXPORT_SYMBOL_GPL (kst_del_req); + +static struct dst_request *kst_req_first (struct kst_state *st) +{ +struct dst_request *req =NULL ; + +if (!list_empty (&st->request_list)) + req = list_entry(st->request_list.next, struct dst_request, + request_list_entry); +return req; +} + +/* +* This function dequeues first request from the queue and tree. +*/ +static struct dst_request *kst_dequeue_req (struct kst_state *st) +{ +struct dst_request *req; + +mutex_lock (&st->request_lock); + req =kst_req_first (st); +if (req) +kst_del_req (req); +mutex_unlock (&st->request_lock); +return req; +} + +/* +* This function enqueues request into tree, indexed by start of the request, +* and also puts request into ordered queue. +*/ +int kst_enqueue_req (struct kst_state *st,struct dst_request *req) +{ +if (unlikely (req->flags &DST_REQ_CHECK_QUEUE )) { +struct dst_request *r; + +list_for_each_entry (r, &st->request_list, request_list_entry) { +if (bio_rw (r->bio) !=bio_rw (req->bio)) +continue ; + +if (r->start >= req->start + req->size) +continue ; + +if (r->start + r->size <= req->start) +continue ; + +return -EEXIST ; + } + } + +list_add_tail (&req->request_list_entry, &st->request_list); +return 0 ; +} +EXPORT_SYMBOL_GPL (kst_enqueue_req); + +/* +* BIOs for local exporting node are freed via this function. +*/ +static void kst_export_put_bio (struct bio *bio) +{ +int i; +struct bio_vec *bv; + +dprintk ("%s: bio: %p, size: %u, idx: %d, num: %d, req: %p.\n" , + __func__, bio, bio->bi_size, bio->bi_idx, + bio->bi_vcnt, bio->bi_private); + +bio_for_each_segment (bv, bio, i) +__free_page (bv->bv_page); +bio_put (bio); +} + +/* +* This is a generic request completion function for requests, +* queued for async processing. +* If it is local export node, state machine is different, +* see details below. +*/ +void kst_complete_req (struct dst_request *req,int err) +{ +dprintk ("%s: bio: %p, req: %p, size: %llu, orig_size: %llu, " +"bi_size: %u, err: %d, flags: %u.\n" , + __func__, req->bio, req, req->size, req->orig_size, + req->bio->bi_size, err, req->flags); + +if (req->flags &DST_REQ_EXPORT ) { +if (err || !(req->flags &DST_REQ_EXPORT_WRITE )) { + req->bio_endio (req, err); + goto out; + } + + req->bio->bi_rw =WRITE ; +generic_make_request (req->bio); + }else { + req->bio_endio (req, err); + } +out: +dst_free_request (req); +} +EXPORT_SYMBOL_GPL (kst_complete_req); + +static void kst_flush_requests (struct kst_state *st) +{ +struct dst_request *req; + +while ((req =kst_dequeue_req (st)) !=NULL ) +kst_complete_req (req, -EIO ); +} + +static int kst_poll_init (struct kst_state *st) +{ +struct kst_poll_helper ph; + + ph.st = st; +init_poll_funcptr (&ph.pt, &kst_queue_func); + + st->socket->ops->poll (NULL , st->socket, &ph.pt); +return 0 ; +} + +/* +* Main state creation function. +* It creates new state according to given operations +* and links it into worker structure and node. +*/ +static struct kst_state *kst_state_init (struct dst_node *node, +unsigned int permissions, +struct kst_state_ops *ops,void *data) +{ +struct kst_state *st; +int err; + + st =kzalloc (sizeof (struct kst_state ),GFP_KERNEL ); +if (!st) +return ERR_PTR (-ENOMEM ); + + st->permissions = permissions; + st->node = node; + st->ops = ops; +INIT_LIST_HEAD (&st->ready_entry); +INIT_LIST_HEAD (&st->entry); +INIT_LIST_HEAD (&st->request_list); +mutex_init (&st->request_lock); + + err = st->ops->init (st, data); +if (err) + goto err_out_free; +mutex_lock (&node->w->state_mutex); +list_add_tail (&st->entry, &node->w->state_list); +mutex_unlock (&node->w->state_mutex); + +kst_wake (st); + +return st; + +err_out_free: +kfree (st); +return ERR_PTR (err); +} + +/* +* This function is called when node is removed, +* or when state is destroyed for connected to local exporting +* node client. +*/ +void kst_state_exit (struct kst_state *st) +{ +struct kst_worker *w = st->node->w; + +mutex_lock (&w->state_mutex); +list_del_init (&st->entry); +mutex_unlock (&w->state_mutex); + + st->ops->exit (st); + +if (st == st->node->state) + st->node->state =NULL ; + +kfree (st); +} + +static int kst_error (struct kst_state *st,int err) +{ +if ((err == -ECONNRESET || err == -EPIPE ) && st->ops->recovery) + err = st->ops->recovery (st, err); + +return st->node->st->alg->ops->error (st, err); +} + +/* +* This is main state processing function. +* It tries to complete request and invoke appropriate +* callbacks in case of errors or successfull operation finish. +*/ +static int kst_thread_process_state (struct kst_state *st) +{ +int err, empty; +unsigned int revents; +struct dst_request *req, *tmp; + +mutex_lock (&st->request_lock); +if (st->ops->ready) { + err = st->ops->ready (st); +if (err) { +mutex_unlock (&st->request_lock); +if (err <0 ) +kst_state_exit (st); +return err; + } + } + + err =0 ; + empty =1 ; + req =NULL ; +list_for_each_entry_safe (req, tmp, &st->request_list, request_list_entry) { + empty =0 ; + revents = st->socket->ops->poll (st->socket->file, + st->socket,NULL ); +if (!revents) +break ; + err = req->callback (req, revents); +if (req->size && !err) + err =1 ; + +if (err <0 || !req->size) { +if (!req->size) + err =0 ; +kst_del_req (req); +kst_complete_req (req, err); + } + +if (err) +break ; + } + +dprintk ("%s: broke the loop: err: %d, list_empty: %d.\n" , + __func__, err,list_empty (&st->request_list)); +mutex_unlock (&st->request_lock); + +if (err <0 ) { +dprintk ("%s: req: %p, err: %d, st: %p, node->state: %p.\n" , + __func__, req, err, st, st->node->state); + +if (st != st->node->state) { +/* +* Accepted client has state not related to storage +* node, so it must be freed explicitely. +* We do not try to fix clients connections to local +* export nodes, just drop the client. +*/ + +kst_state_exit (st); +return err; + } + + err =kst_error (st, err); +if (err) +return err; + +kst_wake (st); + } + +if (list_empty (&st->request_list) && !empty) +kst_wake (st); + +return err; +} + +/* +* Main worker thread - one per storage. +*/ +static int kst_thread_func (void *data) +{ +struct kst_worker *w = data; +struct kst_state *st; +unsigned long flags; +int err =0 ; + +while (!kthread_should_stop ()) { +wait_event_interruptible_timeout (w->wait, + !list_empty (&w->ready_list) || +kthread_should_stop (), +HZ ); + + st =NULL ; +spin_lock_irqsave (&w->ready_lock, flags); +if (!list_empty (&w->ready_list)) { + st = list_entry(w->ready_list.next, struct kst_state, + ready_entry); +list_del_init (&st->ready_entry); + } +spin_unlock_irqrestore (&w->ready_lock, flags); + +if (!st) +continue ; + + err =kst_thread_process_state (st); + } + +return err; +} + +/* +* Worker initialization - this object will host andprocess all states, +* which in turn host requests for remote targets. +*/ +struct kst_worker *kst_worker_init (int id) +{ +struct kst_worker *w; +int err; + + w =kzalloc (sizeof (struct kst_worker ),GFP_KERNEL ); +if (!w) +return ERR_PTR (-ENOMEM ); + + w->id = id; +init_waitqueue_head (&w->wait); +spin_lock_init (&w->ready_lock); +mutex_init (&w->state_mutex); + +INIT_LIST_HEAD (&w->ready_list); +INIT_LIST_HEAD (&w->state_list); + + w->req_pool =mempool_create_slab_pool (256 , dst_request_cache); +if (!w->req_pool) { + err = -ENOMEM ; + goto err_out_free; + } + + w->thread =kthread_run (&kst_thread_func, w,"kst%d" , w->id); +if (IS_ERR (w->thread)) { + err =PTR_ERR (w->thread); + goto err_out_destroy; + } + +mutex_lock (&kst_worker_mutex); +list_add_tail (&w->entry, &kst_worker_list); +mutex_unlock (&kst_worker_mutex); + +return w; + +err_out_destroy: +mempool_destroy (w->req_pool); +err_out_free: +kfree (w); +return ERR_PTR (err); +} + +void kst_worker_exit (struct kst_worker *w) +{ +struct kst_state *st, *n; + +mutex_lock (&kst_worker_mutex); +list_del (&w->entry); +mutex_unlock (&kst_worker_mutex); + +kthread_stop (w->thread); + +list_for_each_entry_safe (st, n, &w->state_list, entry) { +kst_state_exit (st); + } + +mempool_destroy (w->req_pool); +kfree (w); +} + +/* +* Common state exit callback. +* Removes itself from worker's list of states, +* releases socket and flushes all requests. +*/ +static void kst_common_exit (struct kst_state *st) +{ +unsigned long flags; + +kst_poll_exit (st); + +spin_lock_irqsave (&st->node->w->ready_lock, flags); +list_del_init (&st->ready_entry); +spin_unlock_irqrestore (&st->node->w->ready_lock, flags); + +kst_flush_requests (st); +kst_sock_release (st); +} + +/* +* Listen socket contains security attributes in request_list, +* so it can not be flushed via usual way. +*/ +static void kst_listen_flush (struct kst_state *st) +{ +struct dst_secure *s, *tmp; + +list_for_each_entry_safe (s, tmp, &st->request_list, sec_entry) { +list_del (&s->sec_entry); +kfree (s); + } +} + +static void kst_listen_exit (struct kst_state *st) +{ +kst_listen_flush (st); +kst_common_exit (st); +} + +/* +* BIO vector receiving function - does not block, but may sleep because +* of scheduling policy. +*/ +static int kst_data_recv_bio_vec (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +struct msghdr msg; +struct kvec iov; +void *kaddr; +int err; + + kaddr =kmap (bv->bv_page); + + iov.iov_base = kaddr + bv->bv_offset + offset; + iov.iov_len = size; + + msg.msg_iov = (struct iovec *)&iov; + msg.msg_iovlen =1 ; + msg.msg_name =NULL ; + msg.msg_namelen =0 ; + msg.msg_control =NULL ; + msg.msg_controllen =0 ; + msg.msg_flags =MSG_DONTWAIT |MSG_NOSIGNAL ; + + err =kernel_recvmsg (st->socket, &msg, &iov,1 , iov.iov_len, + msg.msg_flags); +kunmap (bv->bv_page); + +return err; +} + +/* +* BIO vector sending function - does not block, but may sleep because +* of scheduling policy. +*/ +static int kst_data_send_bio_vec (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +return kernel_sendpage (st->socket, bv->bv_page, + bv->bv_offset + offset, size, +MSG_DONTWAIT |MSG_NOSIGNAL ); +} + +static int kst_data_send_bio_vec_slow (struct kst_state *st,struct bio_vec *bv, +unsigned int offset,unsigned int size) +{ +struct msghdr msg; +struct kvec iov; +void *addr; +int err; + + addr =kmap (bv->bv_page); + iov.iov_base = addr + bv->bv_offset + offset; + iov.iov_len = size; + + msg.msg_iov = (struct iovec *)&iov; + msg.msg_iovlen =1 ; + msg.msg_name =NULL ; + msg.msg_namelen =0 ; + msg.msg_control =NULL ; + msg.msg_controllen =0 ; + msg.msg_flags =MSG_DONTWAIT |MSG_NOSIGNAL ; + + err =kernel_sendmsg (st->socket, &msg, &iov,1 , iov.iov_len); +kunmap (bv->bv_page); + +return err; +} + +static u32 dst_csum_bvec (struct bio_vec *bv,unsigned int offset,unsigned int size) +{ +void *addr; +u32 csum; + + addr =kmap_atomic (bv->bv_page,KM_USER0 ); + csum =dst_csum_data (addr + bv->bv_offset + offset, size); +kunmap_atomic (addr,KM_USER0 ); + +return csum; +} + +typedef int (*kst_data_process_bio_vec_t )(struct kst_state *st, +struct bio_vec *bv,unsigned int offset,unsigned int size); + +/* +* @req: processing request. +* Contains BIO and all related to its processing info. +* +* This function sends or receives requested number of pages from given BIO. +* +* In case of errors negative value is returned and @size, +* @index and @off are set to the: +* - number of bytes not yet processed (i.e. the rest of the bytes to be +* processed). +* - index of the last bio_vec started to be processed (header sent). +* - offset of the first byte to be processed in the bio_vec. +* +* If there are no errors, zero is returned. +* -EAGAIN is not an error and is transformed into zero return value, +* called must check if @size is zero, in that case whole BIO is processed +* and thus req->bio_endio() can be called, othervise new request must be allocated +* to be processed later. +*/ +static int kst_data_process_bio (struct dst_request *req) +{ +int err = -ENOSPC ; +struct dst_remote_request r; +kst_data_process_bio_vec_t func; +unsigned int cur_size; +int use_csum =test_bit (DST_NODE_USE_CSUM , &req->node->flags); + +if (bio_rw (req->bio) ==WRITE ) { +int i; + + func = kst_data_send_bio_vec; +for (i=req->idx; i<req->num; ++i) { +struct bio_vec *bv =bio_iovec_idx (req->bio, i); + +if (PageSlab (bv->bv_page)) { + func = kst_data_send_bio_vec_slow; +break ; + } + } + r.cmd =cpu_to_be32 (DST_WRITE ); + }else { + r.cmd =cpu_to_be32 (DST_READ ); + func = kst_data_recv_bio_vec; + } + +dprintk ("%s: start: [%c], start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u, flags: %x, use_csum: %d.\n" , + __func__, (bio_rw (req->bio) ==WRITE )?'W' :'R' , + req->start, req->idx, req->num, req->size, req->offset, + req->flags, use_csum); + +while (req->idx < req->num) { +struct bio_vec *bv =bio_iovec_idx (req->bio, req->idx); + + cur_size =min_t (u64, bv->bv_len - req->offset, req->size); + +dprintk ("%s: page: %p, slab: %d, count: %d, max: %d, off: %u, len: %u, req->offset: %u, " +"req->size: %llu, cur_size: %u, flags: %x, " +"use_csum: %d, req->csum: %x.\n" , + __func__, bv->bv_page,PageSlab (bv->bv_page), +atomic_read (&bv->bv_page->_count), req->bio->bi_vcnt, + bv->bv_offset, bv->bv_len, + req->offset, req->size, cur_size, + req->flags, use_csum, req->tmp_csum); + +if (cur_size ==0 ) { +printk (KERN_ERR "%s: %d/%d: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"req_offset: %u, req_size: %llu, " +"req: %p, bio: %p, err: %d.\n" , + __func__, req->idx, req->num, req->start, + bv->bv_offset, bv->bv_len, + req->offset, req->size, + req, req->bio, err); +BUG (); + } + +if (!(req->flags &DST_REQ_HEADER_SENT )) { + r.sector =cpu_to_be64 (req->start); + r.offset =cpu_to_be32 (bv->bv_offset + req->offset); + r.size =cpu_to_be32 (cur_size); + r.csum =0 ; + +if (use_csum &&bio_rw (req->bio) ==WRITE && + !req->tmp_offset) { + req->tmp_offset = req->offset; + r.csum =cpu_to_be32 (dst_csum_bvec (bv, + req->offset, cur_size)); + } + + err =dst_data_send_header (req->state->socket, &r); +dprintk ("%s: %d/%d: sending header: cmd: %u, start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num,be32_to_cpu (r.cmd), + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, err); + +if (err !=sizeof (struct dst_remote_request )) { +if (err >=0 ) + err = -EINVAL ; +break ; + } + + req->flags |=DST_REQ_HEADER_SENT ; + } + +if (use_csum && (bio_rw (req->bio) !=WRITE ) && + !(req->flags &DST_REQ_CHEKSUM_RECV )) { +struct dst_remote_request tmp_req; + + err =dst_data_recv_header (req->state->socket, &tmp_req,0 ); +dprintk ("%s: %d/%d: receiving header: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num, + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, err); + +if (err !=sizeof (struct dst_remote_request )) { +if (err >=0 ) + err = -EINVAL ; +break ; + } + +if (req->tmp_csum) { +printk ("%s: req: %p, old csum: %x, new: %x.\n" , + __func__, req, req->tmp_csum, +be32_to_cpu (tmp_req.csum)); +BUG_ON (1 ); + } + +dprintk ("%s: req: %p, old csum: %x, new: %x.\n" , + __func__, req, req->tmp_csum, +be32_to_cpu (tmp_req.csum)); + req->tmp_csum =be32_to_cpu (tmp_req.csum); + + req->flags |=DST_REQ_CHEKSUM_RECV ; + } + + err =func (req->state, bv, req->offset, cur_size); +if (err <=0 ) +break ; + + req->offset += err; + req->size -= err; + +if (req->offset != bv->bv_len) { +dprintk ("%s: %d/%d: this: start: %llu, bv_offset: %u, " +"bv_len: %u, offset: %u, " +"cur_size: %u, err: %d.\n" , + __func__, req->idx, req->num, req->start, + bv->bv_offset, bv->bv_len, + req->offset, cur_size, err); + err = -EAGAIN ; +break ; + } + +if (use_csum &&bio_rw (req->bio) !=WRITE ) { +u32 csum =dst_csum_bvec (bv, req->tmp_offset, + bv->bv_len - req->tmp_offset); + +dprintk ("%s: req: %p, csum: %x, received csum: %x.\n" , + __func__, req, csum, req->tmp_csum); + +if (csum != req->tmp_csum) { +printk ("%s: %d/%d: broken checksum: start: %llu, " +"bv_offset: %u, bv_len: %u, " +"a offset: %u, offset: %u, " +"cur_size: %u, orig_size: %llu.\n" , + __func__, req->idx, req->num, + req->start, bv->bv_offset, bv->bv_len, + bv->bv_offset + req->offset, + req->offset, cur_size, req->orig_size); +printk ("%s: broken checksum: req: %p, csum: %x, " +"should be: %x, flags: %x, " +"req->tmp_offset: %u, rw: %lu.\n" , + __func__, req, csum, req->tmp_csum, + req->flags, req->tmp_offset,bio_rw (req->bio)); + + req->offset -= err; + req->size += err; + + err = -EREMOTEIO ; +break ; + } + } + + req->offset =0 ; + req->idx++; + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + req->tmp_csum =0 ; + req->start +=to_sector (bv->bv_len); + } + +if (err <=0 && err != -EAGAIN ) { +if (err ==0 ) + err = -ECONNRESET ; + }else + err =0 ; + +if (err <0 || (req->idx == req->num && req->size)) { +dprintk ("%s: return: idx: %d, num: %d, offset: %u, " +"size: %llu, err: %d.\n" , + __func__, req->idx, req->num, req->offset, + req->size, err); + } +dprintk ("%s: end: start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u.\n" , + __func__, req->start, req->idx, req->num, + req->size, req->offset); + +return err; +} + +void kst_bio_endio (struct dst_request *req,int err) +{ +if (err &&printk_ratelimit ()) +printk ("%s: freeing bio: %p, bi_size: %u, " +"orig_size: %llu, req: %p, err: %d.\n" , + __func__, req->bio, req->bio->bi_size, req->orig_size, + req, err); +bio_endio (req->bio, req->orig_size, err); +} +EXPORT_SYMBOL_GPL (kst_bio_endio); + +/* +* This callback is invoked by worker thread to process given request. +*/ +int kst_data_callback (struct dst_request *req,unsigned int revents) +{ +int err; + +dprintk ("%s: req: %p, num: %d, idx: %d, bio: %p, " +"revents: %x, flags: %x.\n" , + __func__, req, req->num, req->idx, req->bio, + revents, req->flags); + +if (req->flags &DST_REQ_EXPORT_READ ) +return 1 ; + + err =kst_data_process_bio (req); + +if (revents & (POLLERR |POLLHUP |POLLRDHUP )) + err = -EPIPE ; + +return err; +} +EXPORT_SYMBOL_GPL (kst_data_callback); + +struct dst_request *dst_clone_request (struct dst_request *req,mempool_t *pool) +{ +struct dst_request *new_req; + + new_req =mempool_alloc (pool,GFP_NOIO ); +if (!new_req) +return NULL ; + +memset (new_req,0 ,sizeof (struct dst_request )); + +dprintk ("%s: req: %p, new_req: %p.\n" , __func__, req, new_req); + +if (req) { + new_req->bio = req->bio; + new_req->state = req->state; + new_req->node = req->node; + new_req->idx = req->idx; + new_req->num = req->num; + new_req->size = req->size; + new_req->orig_size = req->orig_size; + new_req->offset = req->offset; + new_req->tmp_offset = req->tmp_offset; + new_req->tmp_csum = req->tmp_csum; + new_req->start = req->start; + new_req->flags = req->flags; + new_req->bio_endio = req->bio_endio; + new_req->priv = req->priv; + } + +return new_req; +} +EXPORT_SYMBOL_GPL (dst_clone_request); + +void dst_free_request (struct dst_request *req) +{ +dprintk ("%s: free req: %p, pool: %p, bio: %p, state: %p, node: %p.\n" , + __func__, req, req->node->w->req_pool, + req->bio, req->state, req->node); +mempool_free (req, req->node->w->req_pool); +} +EXPORT_SYMBOL_GPL (dst_free_request); + +/* +* This is main data processing function, eventually invoked from block layer. +* It tries to complte request, but if it is about to block, it allocates +* new request and queues it to main worker to be processed when events allow. +*/ +static int kst_data_push (struct dst_request *req) +{ +struct kst_state *st = req->state; +struct dst_request *new_req; +unsigned int revents; +int err, locked =0 ; + +dprintk ("%s: start: %llu, size: %llu, bio: %p.\n" , + __func__, req->start, req->size, req->bio); + +if (!list_empty (&st->request_list) || (req->flags &DST_REQ_ALWAYS_QUEUE )) + goto alloc_new_req; + +if (mutex_trylock (&st->request_lock)) { + locked =1 ; + +if (!list_empty (&st->request_list)) + goto alloc_new_req; + + revents = st->socket->ops->poll (NULL , st->socket,NULL ); +if (revents &POLLOUT ) { + err =kst_data_process_bio (req); +if (err <0 ) + goto out_unlock; + +if (!req->size) + goto out_bio_endio; + } + } + +alloc_new_req: + err = -ENOMEM ; + new_req =dst_clone_request (req, req->node->w->req_pool); +if (!new_req) + goto out_unlock; + + new_req->callback = &kst_data_callback; + +if (!locked) +mutex_lock (&st->request_lock); + + locked =1 ; + + err =kst_enqueue_req (st, new_req); +if (err) + goto out_unlock; +mutex_unlock (&st->request_lock); + + err =0 ; + goto out; + +out_bio_endio: + req->bio_endio (req, err); +out_unlock: +if (locked) +mutex_unlock (&st->request_lock); + locked =0 ; + +if (err) { + err =kst_error (st, err); +if (!err) + goto alloc_new_req; + } + +if (err &&printk_ratelimit ()) { +printk ("%s: error [%c], start: %llu, idx: %d, num: %d, " +"size: %llu, offset: %u, err: %d.\n" , + __func__, (bio_rw (req->bio) ==WRITE )?'W' :'R' , + req->start, req->idx, req->num, req->size, + req->offset, err); + } + +out: + +kst_wake (st); +return err; +} + +/* +* Remote node initialization callback. +*/ +static int kst_data_init (struct kst_state *st,void *data) +{ +int err; + + st->socket = data; + st->socket->sk->sk_allocation =GFP_NOIO ; +/* +* Why not? +*/ + st->socket->sk->sk_sndbuf = st->socket->sk->sk_sndbuf =1024 *1024 *10 ; + + err =kst_poll_init (st); +if (err) +return err; + +return 0 ; +} + +/* +* Remote node recovery function - tries to reconnect to given target. +*/ +static int kst_data_recovery (struct kst_state *st,int err) +{ +struct socket *sock; +struct sockaddr addr; +int addrlen; +struct dst_request *req; + +if (err != -ECONNRESET && err != -EPIPE ) { +dprintk ("%s: state %p does not know how " +"to recover from error %d.\n" , + __func__, st, err); +return err; + } + + err =sock_create (st->socket->ops->family, st->socket->type, + st->socket->sk->sk_protocol, &sock); +if (err <0 ) + goto err_out_exit; + + sock->sk->sk_sndtimeo = sock->sk->sk_rcvtimeo = +msecs_to_jiffies (DST_DEFAULT_TIMEO ); + + err = sock->ops->getname (st->socket, &addr, &addrlen,2 ); +if (err) + goto err_out_destroy; + + err = sock->ops->connect (sock, &addr, addrlen,0 ); +if (err) + goto err_out_destroy; + +kst_poll_exit (st); +kst_sock_release (st); + +mutex_lock (&st->request_lock); + err = st->ops->init (st, sock); +if (!err) { +/* +* After reconnection is completed all requests +* must be resent from the state they were finished previously, +* but with new headers. +*/ +list_for_each_entry (req, &st->request_list, request_list_entry) + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + } +mutex_unlock (&st->request_lock); +if (err <0 ) + goto err_out_destroy; + +kst_wake (st); +dprintk ("%s: reconnected.\n" , __func__); + +return 0 ; + +err_out_destroy: +sock_release (sock); +err_out_exit: +dprintk ("%s: revovery failed: st: %p, err: %d.\n" , __func__, st, err); +return err; +} + +/* +* Local exporting node end IO callbacks. +*/ +static int kst_export_write_end_io (struct bio *bio,unsigned int size,int err) +{ +dprintk ("%s: bio: %p, size: %u, idx: %d, num: %d, err: %d.\n" , + __func__, bio, bio->bi_size, bio->bi_idx, bio->bi_vcnt, err); + +if (bio->bi_size) +return 1 ; + +kst_export_put_bio (bio); +return 0 ; +} + +static int kst_export_read_end_io (struct bio *bio,unsigned int size,int err) +{ +struct dst_request *req = bio->bi_private; +struct kst_state *st = req->state; +int use_csum =test_bit (DST_NODE_USE_CSUM , &req->node->flags); + +dprintk ("%s: bio: %p, req: %p, size: %u, idx: %d, num: %d, err: %d.\n" , + __func__, bio, req, bio->bi_size, bio->bi_idx, + bio->bi_vcnt, err); + +if (bio->bi_size) +return 1 ; + +if (err) { +kst_export_put_bio (bio); +return 0 ; + } + + bio->bi_size = req->size = req->orig_size; + bio->bi_rw =WRITE ; +if (use_csum) + req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV ); + +/* +* This is a race with kst_data_callback(), which checks +* this bit to determine if it can or can not process given +* request. This does not harm actually, since subsequent +* state wakeup will call it again and thus will pick +* given request in time. +*/ + req->flags &= ~DST_REQ_EXPORT_READ ; +kst_wake (st); +return 0 ; +} + +/* +* This callback is invoked each time new request from remote +* node to given local export node is received. +* It allocates new block IO request and queues it for processing. +*/ +static int kst_export_ready (struct kst_state *st) +{ +struct dst_remote_request r; +struct bio *bio; +int err, nr, i; +struct dst_request *req; +unsigned int revents = st->socket->ops->poll (NULL , st->socket,NULL ); + +if (revents & (POLLERR |POLLHUP )) { + err = -EPIPE ; + goto err_out_exit; + } + +if (!(revents &POLLIN ) || !list_empty (&st->request_list)) +return 0 ; + + err =dst_data_recv_header (st->socket, &r,1 ); +if (err !=sizeof (struct dst_remote_request )) { + err = -ECONNRESET ; + goto err_out_exit; + } + +kst_convert_header (&r); + +dprintk ("\n%s: st: %p, cmd: %u, sector: %llu, size: %u, " +"csum: %x, offset: %u.\n" , + __func__, st, r.cmd, r.sector, + r.size, r.csum, r.offset); + + err = -EINVAL ; +if (r.cmd !=DST_READ && r.cmd !=DST_WRITE && r.cmd !=DST_REMOTE_CFG ) + goto err_out_exit; + +if ((s64)(r.sector +to_sector (r.size)) <0 || + (r.sector +to_sector (r.size)) > st->node->size || + r.offset >=PAGE_SIZE ) + goto err_out_exit; + +if (r.cmd ==DST_REMOTE_CFG ) { + r.sector = st->node->size; + +if (test_bit (DST_NODE_USE_CSUM , &st->node->flags)) + r.csum =1 ; + +kst_convert_header (&r); + + err =dst_data_send_header (st->socket, &r); +if (err !=sizeof (struct dst_remote_request )) { + err = -EINVAL ; + goto err_out_exit; + } +kst_wake (st); +return 0 ; + } + + nr =DIV_ROUND_UP (r.size,PAGE_SIZE ); + +while (r.size) { +int nr_pages =min (BIO_MAX_PAGES , nr); +unsigned int size; +struct page *page; + + err = -ENOMEM ; + req =dst_clone_request (NULL , st->node->w->req_pool); +if (!req) + goto err_out_exit; + + bio =bio_alloc (GFP_NOIO , nr_pages); +if (!bio) + goto err_out_free_req; + + req->flags =DST_REQ_EXPORT |DST_REQ_HEADER_SENT | +DST_REQ_CHEKSUM_RECV ; + req->bio = bio; + req->state = st; + req->node = st->node; + req->callback = &kst_data_callback; + req->bio_endio = &kst_bio_endio; + + req->tmp_offset =0 ; + req->tmp_csum = r.csum; + +/* +* Yes, looks a bit weird. +* Logic is simple - for local exporting node all operations +* are reversed compared to usual nodes, since usual nodes +* process remote data and local export node process remote +* requests, so that writing data means sending data to +* remote node and receiving on the local export one. +* +* So, to process writing to the exported node we need first +* to receive data from the net (i.e. to perform READ +* operationin terms of usual node), and then put it to the +* storage (WRITE command, so it will be changed before +* calling generic_make_request()). +* +* To process read request from the exported node we need +* first to read it from storage (READ command for BIO) +* and then send it over the net (perform WRITE operation +* in terms of network). +*/ +if (r.cmd ==DST_WRITE ) { + req->flags |=DST_REQ_EXPORT_WRITE ; + bio->bi_end_io = kst_export_write_end_io; + }else { + req->flags |=DST_REQ_EXPORT_READ ; + bio->bi_end_io = kst_export_read_end_io; + } + bio->bi_rw =READ ; + bio->bi_private = req; + bio->bi_sector = r.sector; + bio->bi_bdev = st->node->bdev; + +for (i =0 ; i < nr_pages; ++i) { + page =alloc_page (GFP_NOIO ); +if (!page) +break ; + + size =min_t (u32,PAGE_SIZE - r.offset, r.size); + + err =bio_add_page (bio, page, size,0 ); +dprintk ("%s: %d/%d: page: %p, size: %u, " +"offset: %u (used zero), err: %d.\n" , + __func__, i, nr_pages, page, size, + r.offset, err); +if (err <=0 ) +break ; + +if (err == size) + nr--; + + r.size -= err; + r.sector +=to_sector (err); + +if (!r.size) +break ; + } + +if (!bio->bi_vcnt) { + err = -ENOMEM ; + goto err_out_put; + } + + req->size = req->orig_size = bio->bi_size; + req->start = bio->bi_sector; + req->idx =0 ; + req->num = bio->bi_vcnt; + +dprintk ("%s: submitting: bio: %p, req: %p, start: %llu, " +"size: %llu, idx: %d, num: %d, offset: %u, csum: %x.\n" , + __func__, bio, req, req->start, req->size, + req->idx, req->num, req->offset, req->tmp_csum); + + err =kst_enqueue_req (st, req); +if (err) + goto err_out_put; + +if (r.cmd ==DST_READ ) { +generic_make_request (bio); + } + } + +kst_wake (st); +return 0 ; + +err_out_put: +bio_put (bio); +err_out_free_req: +dst_free_request (req); +err_out_exit: +return err; +} + +static void kst_export_exit (struct kst_state *st) +{ +struct dst_node *n = st->node; + +kst_common_exit (st); +dst_node_put (n); +} + +static struct kst_state_ops kst_data_export_ops = { + .init = &kst_data_init, + .push = &kst_data_push, + .exit = &kst_export_exit, + .ready = &kst_export_ready, +}; + +/* +* This callback is invoked each time listening socket for +* given local export node becomes ready. +* It creates new state for connected client and queues for processing. +*/ +static int kst_listen_ready (struct kst_state *st) +{ +struct socket *newsock; +struct saddr addr; +struct kst_state *newst; +int err; +unsigned int revents, permissions =0 ; +struct dst_secure *s; + + revents = st->socket->ops->poll (NULL , st->socket,NULL ); +if (!(revents &POLLIN )) +return 1 ; + + err =sock_create (st->socket->ops->family, st->socket->type, + st->socket->sk->sk_protocol, &newsock); +if (err) + goto err_out_exit; + + err = st->socket->ops->accept (st->socket, newsock,0 ); +if (err) + goto err_out_put; + +if (newsock->ops->getname (newsock, (struct sockaddr *)&addr, + (int *)&addr.sa_data_len,2 ) <0 ) { + err = -ECONNABORTED ; + goto err_out_put; + } + +list_for_each_entry (s, &st->request_list, sec_entry) { +void *sec_addr, *new_addr; + + sec_addr = ((void *)&s->sec.addr) + s->sec.check_offset; + new_addr = ((void *)&addr) + s->sec.check_offset; + +if (!memcmp (sec_addr, new_addr, + addr.sa_data_len - s->sec.check_offset)) { + permissions = s->sec.permissions; +break ; + } + } + +/* +* So far only reading and writing are supported. +* Block device does not know about anything else, +* but as far as I recall, there was a prognosis, +* that computer will never require more than 640kb of RAM. +*/ +if (permissions ==0 ) { + err = -EPERM ; + goto err_out_put; + } + +if (st->socket->ops->family ==AF_INET ) { +struct sockaddr_in *sin = (struct sockaddr_in *)&addr; +printk (KERN_INFO "%s: Client: %u.%u.%u.%u:%d.\n" , __func__, +NIPQUAD (sin->sin_addr.s_addr),ntohs (sin->sin_port)); + }else if (st->socket->ops->family ==AF_INET6 ) { +struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&addr; +printk (KERN_INFO "%s: Client: " +"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%d" , + __func__, +NIP6 (sin->sin6_addr),ntohs (sin->sin6_port)); + } + +dst_node_get (st->node); + newst =kst_state_init (st->node, permissions, + &kst_data_export_ops, newsock); +if (IS_ERR (newst)) { + err =PTR_ERR (newst); + goto err_out_put; + } + +/* +* Negative return value means error, positive - stop this state +* processing. Zero allows to check state for pending requests. +* Listening socket contains security objects in request list, +* since it does not have any requests. +*/ +return 1 ; + +err_out_put: +sock_release (newsock); +err_out_exit: +return 1 ; +} + +static int kst_listen_init (struct kst_state *st,void *data) +{ +int err = -ENOMEM , i; +struct dst_le_template *tmp = data; +struct dst_secure *s; + +for (i=0 ; i<tmp->le->secure_attr_num; ++i) { + s =kmalloc (sizeof (struct dst_secure ),GFP_KERNEL ); +if (!s) + goto err_out_exit; + +memcpy (&s->sec, tmp->data,sizeof (struct dst_secure_user )); + +list_add_tail (&s->sec_entry, &st->request_list); + tmp->data +=sizeof (struct dst_secure_user ); + +if (s->sec.addr.sa_family ==AF_INET ) { +struct sockaddr_in *sin = + (struct sockaddr_in *)&s->sec.addr; +printk (KERN_INFO "%s: Client: %u.%u.%u.%u:%d, " +"permissions: %x.\n" , + __func__,NIPQUAD (sin->sin_addr.s_addr), +ntohs (sin->sin_port), s->sec.permissions); + }else if (s->sec.addr.sa_family ==AF_INET6 ) { +struct sockaddr_in6 *sin = + (struct sockaddr_in6 *)&s->sec.addr; +printk (KERN_INFO "%s: Client: " +"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%d, " +"permissions: %x.\n" , + __func__,NIP6 (sin->sin6_addr), +ntohs (sin->sin6_port), s->sec.permissions); + } + } + + err =kst_sock_create (st, &tmp->le->rctl.addr, tmp->le->rctl.type, + tmp->le->rctl.proto, tmp->le->backlog); +if (err) + goto err_out_exit; + + err =kst_poll_init (st); +if (err) + goto err_out_release; + +return 0 ; + +err_out_release: +kst_sock_release (st); +err_out_exit: +kst_listen_flush (st); +return err; +} + +/* +* Operations for different types of states. +* There are three: +* data state - created for remote node, when distributed storage connects +* to remote node, which contain data. +* listen state - created for local export node, when remote distributed +* storage's node connects to given node to get/put data. +* data export state - created for each client connected to above listen +* state. +*/ +static struct kst_state_ops kst_listen_ops = { + .init = &kst_listen_init, + .exit = &kst_listen_exit, + .ready = &kst_listen_ready, +}; +static struct kst_state_ops kst_data_ops = { + .init = &kst_data_init, + .push = &kst_data_push, + .exit = &kst_common_exit, + .recovery = &kst_data_recovery, +}; + +struct kst_state *kst_listener_state_init (struct dst_node *node, +struct dst_le_template *tmp) +{ +return kst_state_init (node,DST_PERM_READ |DST_PERM_WRITE , + &kst_listen_ops, tmp); +} + +struct kst_state *kst_data_state_init (struct dst_node *node, +struct socket *newsock) +{ +return kst_state_init (node,DST_PERM_READ |DST_PERM_WRITE , + &kst_data_ops, newsock); +} + +/* +* Remove all workers and associated states. +*/ +void kst_exit_all (void ) +{ +struct kst_worker *w, *n; + +list_for_each_entry_safe (w, n, &kst_worker_list, entry) { +kst_worker_exit (w); + } +} -
Quoted 3 lines
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
drivers/block/dst/kst.c | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-)
drivers/block/dst/kst.c +9 -0
@@ -1111,8 +1111,17 @@ static int kst_export_read_end_io(struct bio *bio, unsigned int size, int err)return 0 ; } +/* FIXME: This is a litle bit strange, but bio_end_io will +be called one more time for this bio later from here: +->kst_complete_req +->kst_bio_endio +At this moment network layer has already pinned bio's pages +and we may sefly release all pages, so let's reuse existing +kst_export_write_end_io method instead of writing new one. +*/ bio->bi_size = req->size = req->orig_size; bio->bi_rw =WRITE ; + bio->bi_end_io = kst_export_write_end_io;if (use_csum) req->flags &= ~(DST_REQ_HEADER_SENT |DST_REQ_CHEKSUM_RECV );
Re: [3/4] DST: Network state machine.
Quoted 23 lines · Dmitry Monakhov
Quoted 4 lines · Evgeniy Polyakov
Network state machine.
Includes network async processing state machine and related tasks.
Hi, I've tried to play a little bit with DST and discover huge memory leak. Every read request from remote node result in bio + bio's pages leak.
Data flow:
->kst_export_ready ## prepare and submit bio
->generic_make_request(bio) ## submit it->kst_export_read_end_io ## block layer call
bio_end_iocallback->kst_thread_process_state ## process ready requests
->kst_data_callback->kst_data_process_bio ## submit pages to network layer ->kst_complete_req ->kst_bio_endio ->kst_export_read_end_io ## WoW we calling the same bio_end_io ## callback twice ->dst_free_request(req); ## request will be destroyed but it's bio ## and all bio's pages wasn't released.
Quoted 3 lines
We may release bio's pages after it was sent to network, it is safe because
sendpage()already calledget_page(). I've attached simple patch which this this.
Yes, your patch looks good.
Thanks a lot, Dmitry.
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
In reply to Evgeniy PolyakovDocumentation/dst/sysfs.txt line 4
Quoted 10 lines · Evgeniy Polyakov
Documentation/dst/sysfs.txt new file +4
-0@@ -0,0 +1,30 @@ +This file describes sysfs files created for each storage. + +1. Per-storage files. +Each storage has its own dir /sysfs/devices/$storage_name,
It's always /sys/devices/.
Quoted 4 lines
Documentation/dst/sysfs.txt new file +4
-0+which contains following files: + +alg - contains name of the algorithm used to created given storage +name - name of the storage
Documentation/dst/sysfs.txt line 18
Quoted 10 lines
Documentation/dst/sysfs.txt new file +10
-0+nodes - map of the storage (list of nodes and their sizes and starts) +remove_all_nodes - writable file which allows to remove all nodes from given + storage +n-$start-$cookie - per node directory, where + $start - start of the given node in sectors, + $cookie - unique node's id used by DST + +2. Per-node files. +Node's files are located in /sysfs/devices/$storage_name/n-$start-$cookie +directory, described above.
To which class or bus do the devices you create belong? Care to show a "tree" or "ls -la" of the device?
Kay
Re: [1/4] DST: Distributed storage documentation.
Quoted 13 lines · Kay Sievers
Documentation/dst/sysfs.txt line 4
Quoted 10 lines · Evgeniy Polyakov
Documentation/dst/sysfs.txt new file +4
-0@@ -0,0 +1,30 @@ +This file describes sysfs files created for each storage. + +1. Per-storage files. +Each storage has its own dir /sysfs/devices/$storage_name,It's always
/sys/devices/.
I meant that for each new device, it will be placed into /sys/devices/its_name, but it can also be accessed via /sys/bus/dst/devices/
Quoted 9 lines
Quoted 9 lines
Documentation/dst/sysfs.txt new file +9
-0+which contains following files: + +alg - contains name of the algorithm used to created given storage +name - name of the storage +nodes - map of the storage (list of nodes and their sizes and starts) +remove_all_nodes - writable file which allows to remove all nodes from given + storage +n-$start-$cookie - per node directory, where + $start - start of the given node in sectors,
Quoted 8 lines
Documentation/dst/sysfs.txt line 18
Quoted 5 lines
Documentation/dst/sysfs.txt new file +5
-0+ $cookie - unique node's id used by DST + +2. Per-node files. +Node's files are located in /sysfs/devices/$storage_name/n-$start-$cookie +directory, described above.To which class or bus do the devices you create belong? Care to show a "tree" or "ls -la" of the device?
It is 'dst' bus.
uganda:~/codes# ls -la /sys/devices/staorge/ total 0 drwxr-xr-x 4 root root 0 2007-12-10 11:46 . drwxr-xr-x 9 root root 0 2007-12-10 11:46 .. -r--r--r-- 1 root root 4096 2007-12-10 11:46 alg lrwxrwxrwx 1 root root 0 2007-12-10 11:46 bus -> ../../bus/dst drwxr-xr-x 3 root root 0 2007-12-10 11:46 n-0-ffff81003e24117 -r--r--r-- 1 root root 4096 2007-12-10 11:46 name -r--r--r-- 1 root root 4096 2007-12-10 11:46 nodes drwxr-xr-x 2 root root 0 2007-12-10 11:46 power -rw-r--r-- 1 root root 4096 2007-12-10 11:46 remove_all_nodes lrwxrwxrwx 1 root root 0 2007-12-10 11:46 subsystem -> ../../bus/dst -rw-r--r-- 1 root root 4096 2007-12-10 11:46 uevent uganda:~/codes# ls -l /sys/bus/dst/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 09:52 devices drwxr-xr-x 2 root root 0 2007-12-10 09:52 drivers -rw-r--r-- 1 root root 4096 2007-12-10 11:46 drivers_autoprobe --w------- 1 root root 4096 2007-12-10 11:46 drivers_probe
Quoted 1 line
Kay
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
Quoted 15 lines · Evgeniy Polyakov
Quoted 13 lines · Kay Sievers
Documentation/dst/sysfs.txt line 4
Quoted 10 lines · Evgeniy Polyakov
Documentation/dst/sysfs.txt new file +4
-0@@ -0,0 +1,30 @@ +This file describes sysfs files created for each storage. + +1. Per-storage files. +Each storage has its own dir /sysfs/devices/$storage_name,It's always
/sys/devices/.
Quoted 3 lines
I meant that for each new device, it will be placed into
/sys/devices/its_name, but it can also be accessed via/sys/bus/dst/devices/
Still, it looks like a path. :)
Please don't reference any device directly with a /sys/devices/ path. You have to use the subsystem links to the devices
in /sys/bus/dst/devices/. Devices are free to move around
in /sys/devices, even during runtime. Yours don't do, but anyway, please remove all mentioning of direct access to /sys/devices/.
Btw, where is the top-level /sys/devices/storage/ coming from? I don't see that in the code. We don't accept any new "virtual parents" here. Your devices will automatically appear in /sys/devices/virtual/dst/, and not below your own parent. But that path does not matter anyway, because you should only access them from the /sys/bus/dst/devices/ directory.
And in general please don't claim generic names like "storage" in any namespace for a very specific subsystem like this.
Quoted 29 lines
Quoted 9 lines
Quoted 9 lines
Documentation/dst/sysfs.txt new file +9
-0+which contains following files: + +alg - contains name of the algorithm used to created given storage +name - name of the storage +nodes - map of the storage (list of nodes and their sizes and starts) +remove_all_nodes - writable file which allows to remove all nodes from given + storage +n-$start-$cookie - per node directory, where + $start - start of the given node in sectors,Quoted 8 lines
Documentation/dst/sysfs.txt line 18
Quoted 5 lines
Documentation/dst/sysfs.txt new file +5
-0+ $cookie - unique node's id used by DST + +2. Per-node files. +Node's files are located in /sysfs/devices/$storage_name/n-$start-$cookie +directory, described above.To which class or bus do the devices you create belong? Care to show a "tree" or "ls -la" of the device?
It is 'dst' bus.
uganda:~/codes# ls -la /sys/devices/staorge/ total 0 drwxr-xr-x 4 root root 0 2007-12-10 11:46 . drwxr-xr-x 9 root root 0 2007-12-10 11:46 .. -r--r--r-- 1 root root 4096 2007-12-10 11:46 alg lrwxrwxrwx 1 root root 0 2007-12-10 11:46 bus -> ../../bus/dst drwxr-xr-x 3 root root 0 2007-12-10 11:46 n-0-ffff81003e24117(quoted diff) +0
-2-r--r--r-- 1 root root 4096 2007-12-10 11:46 name -r--r--r-- 1 root root 4096 2007-12-10 11:46 nodes
Quoted 4 lines
drwxr-xr-x 2 root root 0 2007-12-10 11:46 power
-rw-r--r-- 1 root root 4096 2007-12-10 11:46remove_all_nodes
lrwxrwxrwx 1 root root 0 2007-12-10 11:46 subsystem -> ../../bus/dst
-rw-r--r-- 1 root root 4096 2007-12-10 11:46 uevent
Ok, how does:
ls -l /sys/devices/storage/n-0-ffff81003e24117
look?
Quoted 6 lines
uganda:~/codes# ls -l /sys/bus/dst/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 09:52 devices drwxr-xr-x 2 root root 0 2007-12-10 09:52 drivers -rw-r--r-- 1 root root 4096 2007-12-10 11:46 drivers_autoprobe --w------- 1 root root 4096 2007-12-10 11:46 drivers_probe
How does:
ls -l /sys/bus/dst/devices
look?
Further questions:
Why do you do your own refcounting instead of using kref? Why don't you use groups for the attributes?
Why don't you use default attributes for the device, where you get all error handling done by the core.
Kay
Re: [1/4] DST: Distributed storage documentation.
Quoted 11 lines · Kay Sievers
Quoted 3 lines
I meant that for each new device, it will be placed into
/sys/devices/its_name, but it can also be accessed via/sys/bus/dst/devices/Still, it looks like a path. :)
Please don't reference any device directly with a
/sys/devices/path. You have to use the subsystem links to the devices
in/sys/bus/dst/devices/. Devices are free to move around
in/sys/devices, even during runtime. Yours don't do, but anyway, please remove all mentioning of direct access to/sys/devices/.
Ok, I will update documentation to reference /sys/bus/dst/devices instead of /sys/devices
Quoted 9 lines
Btw, where is the top-level
/sys/devices/storage/coming from? I don't see that in the code. We don't accept any new "virtual parents" here.Your devices will automatically appear in
/sys/devices/virtual/dst/, and not below your own parent. But that path does not matter anyway, because you should only access them from the/sys/bus/dst/devices/directory.And in general please don't claim generic names like "storage" in any namespace for a very specific subsystem like this.
It is not a parent - it is an example for device called 'storage', if it will be called 'testing', then path will be /sys/devices/testing or more correct /sys/bus/dst/devices/testing :)
Quoted 11 lines
Quoted 11 lines
It is 'dst' bus.
uganda:~/codes# ls -la /sys/devices/staorge/ total 0 drwxr-xr-x 4 root root 0 2007-12-10 11:46 . drwxr-xr-x 9 root root 0 2007-12-10 11:46 .. -r--r--r-- 1 root root 4096 2007-12-10 11:46 alg lrwxrwxrwx 1 root root 0 2007-12-10 11:46 bus -> ../../bus/dst drwxr-xr-x 3 root root 0 2007-12-10 11:46 n-0-ffff81003e24117(quoted diff) +0
-2-r--r--r-- 1 root root 4096 2007-12-10 11:46 name -r--r--r-- 1 root root 4096 2007-12-10 11:46 nodes
Quoted 8 lines
Quoted 4 lines
drwxr-xr-x 2 root root 0 2007-12-10 11:46 power
-rw-r--r-- 1 root root 4096 2007-12-10 11:46remove_all_nodes
lrwxrwxrwx 1 root root 0 2007-12-10 11:46 subsystem -> ../../bus/dst
-rw-r--r-- 1 root root 4096 2007-12-10 11:46 ueventOk, how does:
ls -l/sys/devices/storage/n-0-ffff81003e24117
look?
uganda:~/codes# ls -l /sys/devices/storage/n-0-ffff81003ebc220/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:23 power -r--r--r-- 1 root root 4096 2007-12-10 13:30 size -r--r--r-- 1 root root 4096 2007-12-10 13:30 start -r--r--r-- 1 root root 4096 2007-12-10 13:30 type -rw-r--r-- 1 root root 4096 2007-12-10 13:30 uevent
Quoted 10 lines
Quoted 6 lines
uganda:~/codes# ls -l /sys/bus/dst/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 09:52 devices drwxr-xr-x 2 root root 0 2007-12-10 09:52 drivers -rw-r--r-- 1 root root 4096 2007-12-10 11:46 drivers_autoprobe --w------- 1 root root 4096 2007-12-10 11:46 drivers_probeHow does:
ls -l/sys/bus/dst/devices
look?
uganda:~/codes# ls -la /sys/bus/dst/devices/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:30 . drwxr-xr-x 4 root root 0 2007-12-10 13:22 .. lrwxrwxrwx 1 root root 0 2007-12-10 13:30 storage -> ../../../devices/storage
Here 'storage' is just a name for device called 'storage', it can be anything else.
Quoted 2 lines
Further questions:
Why do you do your own refcounting instead of using kref?
That's because I always used atomic operations as a reference counters and did not tried krefs :)
They are the same actually (module tricky arches where smp_mb_* are required), so I can replace them in the next release.
Quoted 1 line
Why don't you use groups for the attributes?
For 3-4 attributes it is faster to register them in a loop than typing another structure :)
Quoted 2 lines
Why don't you use default attributes for the device, where you get all error handling done by the core.
What is 'default attributes' and for what devices?
All my sysfs files are so much trivial, so they do not need anything special and I do not see what is error handling you mentioned.
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
Quoted 7 lines · Evgeniy Polyakov
Quoted 2 lines
Further questions:
Why do you do your own refcounting instead of using kref?That's because I always used atomic operations as a reference counters and did not tried krefs :)
They are the same actually (module tricky arches where smp_mb_* are required), so I can replace them in the next release.
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call kref_get() in a loop, which is a very ugly step. Is there kref_set() or somethinglike that? At least not in 2.6.22 what I'm using for now.
Sigh, I've converted most of the DST already...
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
In reply to Evgeniy PolyakovQuoted 7 lines · Evgeniy Polyakov
Quoted 6 lines · Kay Sievers
Quoted 3 lines
I meant that for each new device, it will be placed into
/sys/devices/its_name, but it can also be accessed via/sys/bus/dst/devices/Still, it looks like a path. :)
Quoted 8 lines
Quoted 5 lines
Please don't reference any device directly with a
/sys/devices/path. You have to use the subsystem links to the devices
in/sys/bus/dst/devices/. Devices are free to move around
in/sys/devices, even during runtime. Yours don't do, but anyway, please remove all mentioning of direct access to/sys/devices/.Ok, I will update documentation to reference
/sys/bus/dst/devicesinstead of/sys/devices
Great, thanks!
Quoted 7 lines
Quoted 7 lines
Btw, where is the top-level
/sys/devices/storage/coming from? I don't see that in the code. We don't accept any new "virtual parents" here.Your devices will automatically appear in
/sys/devices/virtual/dst/, and not below your own parent. But that path does not matter anyway, because you should only access them from the/sys/bus/dst/devices/directory.
Quoted 6 lines
Quoted 2 lines
And in general please don't claim generic names like "storage" in any namespace for a very specific subsystem like this.
It is not a parent - it is an example for device called 'storage', if it will be called 'testing', then path will be
/sys/devices/testingor more correct/sys/bus/dst/devices/testing:)
Ah, I see.
Quoted 23 lines
Quoted 11 lines
Quoted 11 lines
It is 'dst' bus.
uganda:~/codes# ls -la /sys/devices/staorge/ total 0 drwxr-xr-x 4 root root 0 2007-12-10 11:46 . drwxr-xr-x 9 root root 0 2007-12-10 11:46 .. -r--r--r-- 1 root root 4096 2007-12-10 11:46 alg lrwxrwxrwx 1 root root 0 2007-12-10 11:46 bus -> ../../bus/dst drwxr-xr-x 3 root root 0 2007-12-10 11:46 n-0-ffff81003e24117(quoted diff) +0
-2-r--r--r-- 1 root root 4096 2007-12-10 11:46 name -r--r--r-- 1 root root 4096 2007-12-10 11:46 nodesQuoted 8 lines
Quoted 4 lines
drwxr-xr-x 2 root root 0 2007-12-10 11:46 power
-rw-r--r-- 1 root root 4096 2007-12-10 11:46remove_all_nodes
lrwxrwxrwx 1 root root 0 2007-12-10 11:46 subsystem -> ../../bus/dst
-rw-r--r-- 1 root root 4096 2007-12-10 11:46 ueventOk, how does:
ls -l/sys/devices/storage/n-0-ffff81003e24117
look?uganda:~/codes# ls -l /sys/devices/storage/n-0-ffff81003ebc220/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:23 power
Review
Quoted 4 lines
(quoted diff) +0
-4-r--r--r-- 1 root root 4096 2007-12-10 13:30 size -r--r--r-- 1 root root 4096 2007-12-10 13:30 start -r--r--r-- 1 root root 4096 2007-12-10 13:30 type -rw-r--r-- 1 root root 4096 2007-12-10 13:30 uevent
This is a "struct device" instance without a subsystem (bus/class), right? It will not send an uevent to userspace. Is that intended? Why don't you add them all to the dst bus?
Quoted 11 lines
Quoted 10 lines
Quoted 6 lines
uganda:~/codes# ls -l /sys/bus/dst/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 09:52 devices drwxr-xr-x 2 root root 0 2007-12-10 09:52 drivers -rw-r--r-- 1 root root 4096 2007-12-10 11:46 drivers_autoprobe --w------- 1 root root 4096 2007-12-10 11:46 drivers_probeHow does:
ls -l/sys/bus/dst/devices
look?
Quoted 8 lines
uganda:~/codes# ls -la /sys/bus/dst/devices/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:30 . drwxr-xr-x 4 root root 0 2007-12-10 13:22 .. lrwxrwxrwx 1 root root 0 2007-12-10 13:30 storage -> ../../../devices/storageHere 'storage' is just a name for device called 'storage', it can be anything else.
Fine.
Quoted 7 lines
Quoted 2 lines
Further questions:
Why do you do your own refcounting instead of using kref?That's because I always used atomic operations as a reference counters and did not tried krefs :)
They are the same actually (module tricky arches where smp_mb_* are required), so I can replace them in the next release.
Quoted 4 lines · Evgeniy Polyakov
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.
Yeah, a loop would look pretty ugly. How about just adding kref_set(), if you need it.
Quoted 4 lines
Quoted 1 line
Why don't you use groups for the attributes?
For 3-4 attributes it is faster to register them in a loop than typing another structure :)
Yeah, but if you would need to recover from an error when the creation of a file fails, a group would do the proper "rollback".
Quoted 6 lines
Quoted 2 lines
Why don't you use default attributes for the device, where you get all error handling done by the core.
What is 'default attributes' and for what devices?
All my sysfs files are so much trivial, so they do not need anything special and I do not see what is error handling you mentioned.
If all devices of a subsystem (bus/class) are of the same type, you can set a default array of attributes in the "struct bus/class" to be created at every device. If you have multiple types of devices in the same subsytem (bus/class) you can to assign a the "device_type", which has the default attribute group.
That way the core will create the files before the event is sent out to userspace, and the files can be access from the event itself. Not sure if that is needed for dst.
Thanks,
Kay
Re: [1/4] DST: Distributed storage documentation.
Quoted 11 lines · Kay Sievers
Review
Quoted 7 lines
uganda:~/codes# ls -l /sys/devices/storage/n-0-ffff81003ebc220/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:23 power(quoted diff) +0
-4-r--r--r-- 1 root root 4096 2007-12-10 13:30 size -r--r--r-- 1 root root 4096 2007-12-10 13:30 start -r--r--r-- 1 root root 4096 2007-12-10 13:30 type -rw-r--r-- 1 root root 4096 2007-12-10 13:30 ueventThis is a "struct device" instance without a subsystem (bus/class), right? It will not send an uevent to userspace. Is that intended? Why don't you add them all to the dst bus?
I created dst bus for storage devices only, nodes are very different objects, and actually they do not need any events from above, but I need to put some attributes somewhere, so it is 'empty' device.
Quoted 7 lines
Quoted 4 lines
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.Yeah, a loop would look pretty ugly. How about just adding
kref_set(), if you need it.
Well, then it distributed storage will not be able to build as standalone module, and kref_set() itself will not be accepted as a single patch, since there are no in-kernel users :)
It is easily doable though.
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
Why don't you use groups for the attributes?
For 3-4 attributes it is faster to register them in a loop than typing another structure :)
Yeah, but if you would need to recover from an error when the creation of a file fails, a group would do the proper "rollback".
I do not care about such errors - if there is such an error for a file, which exports information about type of the node (i.e. string "L" or "R") or some other very meaningful info, then system has enough to care about instead of this, so dst does not do anything special - it ignores such errors :)
On exit path it will be checked and removed correctly.
If there will be additional sysfs files, I think group is a good way to implement them.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
Why don't you use default attributes for the device, where you get all error handling done by the core.
What is 'default attributes' and for what devices?
All my sysfs files are so much trivial, so they do not need anything special and I do not see what is error handling you mentioned.
Quoted 8 lines
If all devices of a subsystem (bus/class) are of the same type, you can set a default array of attributes in the "struct bus/class" to be created at every device. If you have multiple types of devices in the same subsytem (bus/class) you can to assign a the "
device_type", which has the default attribute group.
That way the core will create the files before the event is sent out to userspace, and the files can be access from the event itself. Not sure if that is needed for dst.
Ok, I see.
DST right now has 3 types of files - storage files, it is common for every storage device; node files, which are the same for every node; and per-algorithm private devices - they can be different (actually only mirroring algorithm exports something to userspace).
I think it is possible to use default attributes for storage devices, but node device does not have a bus/class, so they will be untouched.
Quoted 2 lines
Thanks,
Kay
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
Quoted 13 lines · Evgeniy Polyakov
Quoted 11 lines · Kay Sievers
Review
Quoted 7 lines
uganda:~/codes# ls -l /sys/devices/storage/n-0-ffff81003ebc220/ total 0 drwxr-xr-x 2 root root 0 2007-12-10 13:23 power(quoted diff) +0
-4-r--r--r-- 1 root root 4096 2007-12-10 13:30 size -r--r--r-- 1 root root 4096 2007-12-10 13:30 start -r--r--r-- 1 root root 4096 2007-12-10 13:30 type -rw-r--r-- 1 root root 4096 2007-12-10 13:30 ueventThis is a "struct device" instance without a subsystem (bus/class), right? It will not send an uevent to userspace. Is that intended? Why don't you add them all to the dst bus?
Quoted 3 lines
I created dst bus for storage devices only, nodes are very different objects, and actually they do not need any events from above, but I need to put some attributes somewhere, so it is 'empty' device.
Ok.
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.
Quoted 7 lines
Quoted 2 lines
Yeah, a loop would look pretty ugly. How about just adding
kref_set(), if you need it.Well, then it distributed storage will not be able to build as standalone module, and
kref_set()itself will not be accepted as a single patch, since there are no in-kernel users :)
It is easily doable though.
Most rules have exceptions. :) Send a patch, so we can see how it looks like.
Quoted 36 lines
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
Why don't you use groups for the attributes?
For 3-4 attributes it is faster to register them in a loop than typing another structure :)
Yeah, but if you would need to recover from an error when the creation of a file fails, a group would do the proper "rollback".
I do not care about such errors - if there is such an error for a file, which exports information about type of the node (i.e. string "L" or "R") or some other very meaningful info, then system has enough to care about instead of this, so dst does not do anything special - it ignores such errors :)
On exit path it will be checked and removed correctly.
If there will be additional sysfs files, I think group is a good way to implement them.Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
Why don't you use default attributes for the device, where you get all error handling done by the core.
What is 'default attributes' and for what devices?
All my sysfs files are so much trivial, so they do not need anything special and I do not see what is error handling you mentioned.Quoted 8 lines
If all devices of a subsystem (bus/class) are of the same type, you can set a default array of attributes in the "struct bus/class" to be created at every device. If you have multiple types of devices in the same subsytem (bus/class) you can to assign a the "
device_type", which has the default attribute group.
That way the core will create the files before the event is sent out to userspace, and the files can be access from the event itself. Not sure if that is needed for dst.Ok, I see.
Quoted 7 lines
DST right now has 3 types of files - storage files, it is common for every storage device; node files, which are the same for every node; and per-algorithm private devices - they can be different (actually only mirroring algorithm exports something to userspace).
I think it is possible to use default attributes for storage devices, but node device does not have a bus/class, so they will be untouched.
Sounds fine.
Thanks,
Kay
Re: [1/4] DST: Distributed storage documentation.
Quoted 8 lines · Kay Sievers
Quoted 8 lines
Quoted 7 lines
Quoted 4 lines
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.Yeah, a loop would look pretty ugly. How about just adding
kref_set(), if you need it.
Quoted 7 lines
Quoted 4 lines
Well, then it distributed storage will not be able to build as standalone module, and
kref_set()itself will not be accepted as a single patch, since there are no in-kernel users :)
It is easily doable though.Most rules have exceptions. :) Send a patch, so we can see how it looks like.
It looks really non-trivial :)
include/linux/kref.h +1 -0
@@ -24,6 +24,7 @@ struct kref {atomic_t refcount; }; +void kref_set (struct kref *kref,int num);void kref_init (struct kref *kref);void kref_get (struct kref *kref);int kref_put (struct kref *kref,void (*release) (struct kref *kref));
lib/kref.c +12 -2
@@ -15,13 +15,23 @@#include <linux/module.h> /** +* kref_set - initialize object and set refcount to requested number. +* @kref: object in question. +* @num: initial reference counter +*/ +void kref_set (struct kref *kref,int num) +{ +atomic_set (&kref->refcount, num); +smp_mb (); +} + +/** * kref_init - initialize object. * @kref: object in question. */ void kref_init (struct kref *kref) { -atomic_set (&kref->refcount,1 ); -smp_mb (); +kref_set (kref,1 ); }/**
Signature · 1 line
Evgeniy Polyakov
Re: [1/4] DST: Distributed storage documentation.
Quoted 14 lines · Evgeniy Polyakov
Quoted 13 lines · Kay Sievers
Quoted 12 lines
Quoted 7 lines
Quoted 4 lines
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.Yeah, a loop would look pretty ugly. How about just adding
kref_set(), if you need it.Well, then it distributed storage will not be able to build as standalone module, and
kref_set()itself will not be accepted as a single patch, since there are no in-kernel users :)
It is easily doable though.
Quoted 4 lines
Quoted 2 lines
Most rules have exceptions. :) Send a patch, so we can see how it looks like.
It looks really non-trivial :)
Yeah, it does. :)
We miss an EXPORT_SYMBOL(), right?
Kay
Re: [1/4] DST: Distributed storage documentation.
Quoted 15 lines · Kay Sievers
Quoted 14 lines · Evgeniy Polyakov
Quoted 13 lines · Kay Sievers
Quoted 12 lines
Quoted 7 lines
Quoted 4 lines
Actually not - I have to set reference counter to something other than 1 or +/- 1, and thus will have to call
kref_get()in a loop, which is a very ugly step. Is therekref_set()or somethinglike that? At least not in 2.6.22 what I'm using for now.Yeah, a loop would look pretty ugly. How about just adding
kref_set(), if you need it.Well, then it distributed storage will not be able to build as standalone module, and
kref_set()itself will not be accepted as a single patch, since there are no in-kernel users :)
It is easily doable though.
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
Most rules have exceptions. :) Send a patch, so we can see how it looks like.
It looks really non-trivial :)
Yeah, it does. :)
We miss anEXPORT_SYMBOL(), right?
Yep :)
include/linux/kref.h +1 -0
@@ -24,6 +24,7 @@ struct kref {atomic_t refcount; }; +void kref_set (struct kref *kref,int num);void kref_init (struct kref *kref);void kref_get (struct kref *kref);int kref_put (struct kref *kref,void (*release) (struct kref *kref));
lib/kref.c +13 -2
@@ -15,13 +15,23 @@#include <linux/module.h> /** +* kref_set - initialize object and set refcount to requested number. +* @kref: object in question. +* @num: initial reference counter +*/ +void kref_set (struct kref *kref,int num) +{ +atomic_set (&kref->refcount, num); +smp_mb (); +} + +/** * kref_init - initialize object. * @kref: object in question. */ void kref_init (struct kref *kref) { -atomic_set (&kref->refcount,1 ); -smp_mb (); +kref_set (kref,1 ); }/**
@@ -61,6 +71,7 @@ int kref_put(struct kref *kref, void (*release)(struct kref *kref))return 0 ; } +EXPORT_SYMBOL (kref_set);EXPORT_SYMBOL (kref_init);EXPORT_SYMBOL (kref_get);EXPORT_SYMBOL (kref_put);
Signature · 1 line
Evgeniy Polyakov