#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "list.h"
#include "smb_conn.h"
#include "samba.h"
#include "common.h"
struct samba_ctx{
LIST entries;
int ref_count;
struct smb_conn_ctx smb_ctx;
char name[128];
};
#define smb_conn_ctx_to_samba_ctx(ptr) \
(struct samba_ctx *)((char*)(ptr) - offsetof(struct samba_ctx, smb_ctx))
static size_t samba_max_rw_block_size = (128 * 1024);
static int samba_ctx_count = 0;
static int samba_ctx_max_count = 15;
static LIST samba_ctx_list = STATIC_LIST_INITIALIZER(samba_ctx_list);
static pthread_mutex_t m_samba = PTHREAD_MUTEX_INITIALIZER;
int samba_init(size_t max_rw_block_size){
size_t page_size;
page_size = getpagesize();
max_rw_block_size -= (max_rw_block_size % page_size);
if (max_rw_block_size == 0) max_rw_block_size = page_size;
samba_max_rw_block_size = max_rw_block_size;
DPRINTF(7, "max_rw_block_size=%zd\n", samba_max_rw_block_size);
return 1;
}
static void samba_set_context_name(struct samba_ctx *ctx, const char *name, size_t len){
if (len >= sizeof(ctx->name)) len = sizeof(ctx->name) - 1;
if (len > 0) strncpy(ctx->name, name, len);
ctx->name[len] = '\0';
}
static struct samba_ctx * samba_add_new_context(const char *name, size_t len){
struct samba_ctx *ctx;
ctx = malloc(sizeof(struct samba_ctx));
if (ctx == NULL) return NULL;
memset(ctx, 0, sizeof(struct samba_ctx));
if (smb_conn_ctx_init(&ctx->smb_ctx, samba_max_rw_block_size) != 0){
free(ctx);
return NULL;
}
samba_set_context_name(ctx, name, len);
add_to_list_back(&samba_ctx_list, &ctx->entries);
samba_ctx_count++;
return ctx;
}
static int samba_try_to_remove_context(struct samba_ctx *ctx){
if (ctx->ref_count != 0) return -1;
if (smb_conn_ctx_destroy(&ctx->smb_ctx) != 0) return -1;
samba_ctx_count--;
remove_from_list(&samba_ctx_list, &ctx->entries);
return 0;
}
static struct samba_ctx * samba_find_by_name(const char *name, size_t len){
struct samba_ctx *ctx;
LIST *elem;
if (len >= sizeof(ctx->name)) len = sizeof(ctx->name) - 1;
elem = first_list_elem(&samba_ctx_list);
while(is_valid_list_elem(&samba_ctx_list, elem)){
ctx = list_entry(elem, struct samba_ctx, entries);
if ((strncasecmp(ctx->name, name, len) == 0) &&
(ctx->name[len] == '\0')) return ctx;
elem = elem->next;
};
return NULL;
}
static struct samba_ctx * samba_find_oldest(void){
/* our list is sorted by the usage time, so the last element is oldest */
LIST *elem = last_list_elem(&samba_ctx_list);
if (is_valid_list_elem(&samba_ctx_list, elem))
return list_entry(elem, struct samba_ctx, entries);
return NULL;
}
static const char* samba_get_context_status_string(void){
static char buffer[4096];
LIST *elem;
int ret;
size_t len;
char *pos, *ptn;
memset(buffer, 0, sizeof(buffer));
len = sizeof(buffer); pos = buffer; ptn = "%s[%d], ";
*pos++ = '('; len--;
elem = first_list_elem(&samba_ctx_list);
while(is_valid_list_elem(&samba_ctx_list, elem)){
struct samba_ctx *ctx;
ctx = list_entry(elem, struct samba_ctx, entries);
if (!is_valid_list_elem(&samba_ctx_list, elem->next)) ptn = "%s[%d]";
ret = snprintf(pos, len, ptn, ctx->name, ctx->ref_count);
if (ret < 0) goto error;
if ((size_t) ret >= len) goto out_of_space;
pos += ret; len -= ret;
elem = elem->next;
};
if (len < 2) goto out_of_space;
*pos++ = ')';
*pos = '\0';
return buffer;
out_of_space:
strcpy(buffer + sizeof(buffer) - 5, "...)");
return buffer;
error:
return "(?error?)";
}
/* our list is sorted by the usage time, so touching is equivalent */
/* to the moving of element to the top of the list */
static inline void samba_touch_ctx_without_lock(struct samba_ctx *ctx){
remove_from_list(&samba_ctx_list, &ctx->entries);
add_to_list(&samba_ctx_list, &ctx->entries);
}
/* the same as above, but with locking */
static void samba_touch_ctx(struct samba_ctx *ctx){
pthread_mutex_lock(&m_samba);
samba_touch_ctx_without_lock(ctx);
pthread_mutex_unlock(&m_samba);
}
int samba_set_max_ctx_count(int count){
if (count < 3) return 0;
DPRINTF(7, "count=%d\n", count);
pthread_mutex_lock(&m_samba);
samba_ctx_max_count = count;
pthread_mutex_unlock(&m_samba);
return 1;
}
void samba_allocate_ctxs(void){
struct samba_ctx *ctx;
pthread_mutex_lock(&m_samba);
while(samba_ctx_count < samba_ctx_max_count){
if ((ctx = samba_add_new_context("", 0)) == NULL) break;
}
pthread_mutex_unlock(&m_samba);
}
void samba_destroy_unused_ctxs(void){
LIST *elem;
struct samba_ctx *ctx;
pthread_mutex_lock(&m_samba);
elem = first_list_elem(&samba_ctx_list);
while(is_valid_list_elem(&samba_ctx_list, elem)){
ctx = list_entry(elem, struct samba_ctx, entries);
elem = elem->next;
if (ctx->ref_count == 0) samba_try_to_remove_context(ctx);
}
DPRINTF(6, "ctx_total=%d, list=%s\n",
samba_ctx_count, samba_get_context_status_string());
pthread_mutex_unlock(&m_samba);
}
static struct samba_ctx * samba_get_ctx(const char *url){
size_t len;
struct samba_ctx *ctx;
/* find a length of first component of url's path */
for(len = 0; url[len] == '/'; len++);
for(; (url[len] != '\0') && (url[len] != '/'); len++);
DPRINTF(6, "name='%.*s'\n", (int) len, url);
pthread_mutex_lock(&m_samba);
if ((ctx = samba_find_by_name(url, len)) != NULL) goto exist;
if (samba_ctx_count < samba_ctx_max_count)
if ((ctx = samba_add_new_context(url, len)) != NULL) goto exist;
if ((ctx = samba_find_oldest()) == NULL) goto shit_happens;
/* reuse oldest context for new purpose */
samba_set_context_name(ctx, url, len);
exist:
/* touch ctx and increase its ref_count */
samba_touch_ctx_without_lock(ctx);
ctx->ref_count++;
shit_happens:
DPRINTF(6, "ctx_total=%d, list=%s\n",
samba_ctx_count, samba_get_context_status_string());
pthread_mutex_unlock(&m_samba);
return ctx;
}
static void samba_release_ctx(struct samba_ctx *ctx){
pthread_mutex_lock(&m_samba);
DPRINTF(6, "ctx->name=%s[%d]\n", ctx->name, ctx->ref_count);
if (ctx->ref_count > 0){
ctx->ref_count--;
if ((samba_ctx_count > samba_ctx_max_count) && (ctx->ref_count == 0))
samba_try_to_remove_context(ctx);
}else{
DPRINTF(0, "WARNING! trying to release an unused context!\n");
}
DPRINTF(6, "ctx_total=%d, list=%s\n",
samba_ctx_count, samba_get_context_status_string());
pthread_mutex_unlock(&m_samba);
}
samba_fd samba_open(const char *url, int flags, mode_t mode){
int error;
struct samba_ctx *ctx;
samba_fd fd;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return NULL;
}
fd = smb_conn_open(&ctx->smb_ctx, url, flags, mode);
error = errno;
if (fd == NULL) samba_release_ctx(ctx);
errno = error;
return fd;
}
samba_fd samba_creat(const char *url, mode_t mode){
int error;
struct samba_ctx *ctx;
samba_fd fd;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return NULL;
}
fd = smb_conn_creat(&ctx->smb_ctx, url, mode);
error = errno;
if (fd == NULL) samba_release_ctx(ctx);
errno = error;
return fd;
}
ssize_t samba_read(samba_fd fd, off_t offset, char *buf, size_t bufsize){
ssize_t result = 0;
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
samba_touch_ctx(smb_conn_ctx_to_samba_ctx(fd->ctx));
while(bufsize > 0){
ssize_t res;
size_t count;
count = (bufsize <= samba_max_rw_block_size) ?
bufsize : samba_max_rw_block_size;
res = smb_conn_read(fd->ctx, fd, offset, buf, count);
if (res == (ssize_t) (-1)) return res;
buf += res; offset += res; bufsize -= res;
result += res;
if (res != (ssize_t) count) break;
}
return result;
}
ssize_t samba_write(samba_fd fd, off_t offset, const char *buf, size_t bufsize){
ssize_t result = 0;
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
samba_touch_ctx(smb_conn_ctx_to_samba_ctx(fd->ctx));
while(bufsize > 0){
ssize_t res;
size_t count;
count = (bufsize <= samba_max_rw_block_size) ?
bufsize : samba_max_rw_block_size;
res = smb_conn_write(fd->ctx, fd, offset, buf, count);
if (res == (ssize_t) (-1)) return res;
buf += res; offset += res; bufsize -= res;
result += res;
if (res != (ssize_t) count) break;
}
return result;
}
int samba_close(samba_fd fd){
int error, result;
struct smb_conn_ctx *ctx;
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
ctx = fd->ctx;
result = smb_conn_close(ctx, fd);
error = errno;
if (result == 0) samba_release_ctx(smb_conn_ctx_to_samba_ctx(ctx));
errno = error;
return result;
}
int samba_unlink(const char *url){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_unlink(&ctx->smb_ctx, url);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_rename(const char *old_url, const char *new_url){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(old_url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_rename(&ctx->smb_ctx, old_url, new_url);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
samba_fd samba_opendir(const char *url){
int error;
struct samba_ctx *ctx;
samba_fd fd;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return NULL;
}
fd = smb_conn_opendir(&ctx->smb_ctx, url);
error = errno;
if (fd == NULL) samba_release_ctx(ctx);
errno = error;
return fd;
}
int samba_closedir(samba_fd fd){
int error, result;
struct smb_conn_ctx *ctx;
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
ctx = fd->ctx;
result = smb_conn_closedir(ctx, fd);
error = errno;
if (result == 0) samba_release_ctx(smb_conn_ctx_to_samba_ctx(ctx));
errno = error;
return result;
}
ssize_t samba_readdir(samba_fd fd, void *buf, size_t bufsize){
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
samba_touch_ctx(smb_conn_ctx_to_samba_ctx(fd->ctx));
return smb_conn_readdir(fd->ctx, fd, buf, bufsize);
}
int samba_mkdir(const char *url, mode_t mode){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_mkdir(&ctx->smb_ctx, url, mode);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_rmdir(const char *url){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_rmdir(&ctx->smb_ctx, url);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_stat (const char *url, struct stat *st){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_stat(&ctx->smb_ctx, url, st);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_fstat(samba_fd fd, struct stat *st){
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
samba_touch_ctx(smb_conn_ctx_to_samba_ctx(fd->ctx));
return smb_conn_fstat(fd->ctx, fd, st);
}
int samba_ftruncate(samba_fd fd, off_t size){
if ((fd == NULL) || (fd->ctx == NULL)){
errno = EINVAL;
return -1;
}
samba_touch_ctx(smb_conn_ctx_to_samba_ctx(fd->ctx));
return smb_conn_ftruncate(fd->ctx, fd, size);
}
int samba_chmod(const char *url, mode_t mode){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_chmod(&ctx->smb_ctx, url, mode);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_utimes(const char *url, struct timeval *tbuf){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_utimes(&ctx->smb_ctx, url, tbuf);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_setxattr(const char *url, const char *name,
const void *value, size_t size, int flags){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_setxattr(&ctx->smb_ctx, url, name, value, size, flags);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_getxattr(const char *url, const char *name,
void *value, size_t size){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_getxattr(&ctx->smb_ctx, url, name, value, size);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_listxattr(const char *url, char *list, size_t size){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_listxattr(&ctx->smb_ctx, url, list, size);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}
int samba_removexattr(const char *url, const char *name){
int error, result;
struct samba_ctx *ctx;
if ((ctx = samba_get_ctx(url)) == NULL){
errno = ENOMEM;
return -1;
}
result = smb_conn_removexattr(&ctx->smb_ctx, url, name);
error = errno;
samba_release_ctx(ctx);
errno = error;
return result;
}