mirror of
https://github.com/obarthel/amiga-smbfs.git
synced 2025-12-08 14:58:35 +00:00
Please keep in mind that this is still a development version and might surprise you (not necessarily in a good way). Do not let me discourage you to build and test this version, although there will be some risks involved such as data corruption or loss of data.
1966 lines
48 KiB
C
1966 lines
48 KiB
C
/*
|
|
* :ts=4
|
|
*
|
|
* Name: smb_abstraction.c
|
|
* Description: Smb abstraction layer.
|
|
* Author: Christian Starkjohann <cs -at- hal -dot- kph -dot- tuwien -dot- ac -dot- at>
|
|
* Date: 1996-12-31
|
|
* Copyright: GNU-GPL
|
|
*
|
|
* Modified for use with AmigaOS by Olaf Barthel <obarthel -at- gmx -dot- net>
|
|
*/
|
|
|
|
#include "smbfs.h"
|
|
#include "errors.h"
|
|
#include "quad_math.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
#include <smb/smb_fs.h>
|
|
#include <smb/smb.h>
|
|
#include <smb/smbno.h>
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define ATTR_CACHE_TIME 5 /* cache attributes for this time (seconds) */
|
|
#define DIR_CACHE_TIME 5 /* cache directories for this time (seconds) */
|
|
#define DIRCACHE_SIZE 170
|
|
#define DOS_PATHSEP '\\'
|
|
|
|
/*****************************************************************************/
|
|
|
|
#include "smb_abstraction.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void smba_cleanup_dircache(struct smba_server * server);
|
|
static int smba_setup_dircache (struct smba_server * server,int cache_size, int * error_ptr);
|
|
|
|
/*****************************************************************************/
|
|
|
|
static int
|
|
smba_connect (
|
|
smba_connect_parameters_t * p,
|
|
in_addr_t ip_addr,
|
|
int use_E,
|
|
const char * workgroup_name,
|
|
int cache_size,
|
|
int max_transmit,
|
|
int timeout,
|
|
int opt_raw_smb,
|
|
int opt_unicode,
|
|
int opt_prefer_write_raw,
|
|
int opt_write_behind,
|
|
int opt_prefer_read_raw,
|
|
int * error_ptr,
|
|
int * smb_error_class_ptr,
|
|
int * smb_error_ptr,
|
|
smba_server_t ** smba_server_ptr)
|
|
{
|
|
smba_server_t *res;
|
|
struct smb_mount_data data;
|
|
char hostname[MAXHOSTNAMELEN], *s;
|
|
struct servent * servent;
|
|
int result = -1;
|
|
|
|
(*smba_server_ptr) = NULL;
|
|
|
|
res = malloc (sizeof(*res));
|
|
if(res == NULL)
|
|
{
|
|
report_error("Not enough memory.");
|
|
|
|
(*error_ptr) = ENOMEM;
|
|
goto error_occured;
|
|
}
|
|
|
|
memset (res, 0, sizeof(*res));
|
|
memset (&data, 0, sizeof (data));
|
|
memset (hostname, 0, sizeof (hostname));
|
|
|
|
/* Use raw SMB over TCP rather than NetBIOS. */
|
|
if(opt_raw_smb)
|
|
res->server.raw_smb = TRUE;
|
|
|
|
/* Timeout for send/receive operations in seconds. */
|
|
res->server.timeout = timeout;
|
|
|
|
/* Enable Unicode support if the server supports it, too. */
|
|
res->server.use_unicode = opt_unicode;
|
|
|
|
/* Prefer SMB_COM_WRITE_RAW over SMB_COM_WRITE_ANDX? */
|
|
res->server.prefer_write_raw = opt_prefer_write_raw;
|
|
|
|
/* Enable asynchronous SMB_COM_WRITE_RAW operations? */
|
|
res->server.write_behind = opt_write_behind;
|
|
|
|
/* Prefer SMB_COM_READ_RAW over SMB_COM_READ_ANDX? */
|
|
res->server.prefer_read_raw = opt_prefer_read_raw;
|
|
|
|
if(smba_setup_dircache (res,cache_size,error_ptr) < 0)
|
|
{
|
|
report_error("Directory cache initialization failed (%ld, %s).",(*error_ptr),posix_strerror(*error_ptr));
|
|
goto error_occured;
|
|
}
|
|
|
|
strlcpy(data.workgroup_name,workgroup_name,sizeof(data.workgroup_name));
|
|
|
|
res->server.abstraction = res;
|
|
|
|
if(gethostname (hostname, MAXHOSTNAMELEN) < 0)
|
|
{
|
|
report_error("Could not look up local host name (%ld, %s).",(*error_ptr),posix_strerror(*error_ptr));
|
|
goto error_occured;
|
|
}
|
|
|
|
/* Only retain the host name, drop any domain names following it. */
|
|
if ((s = strchr (hostname, '.')) != NULL)
|
|
(*s) = '\0';
|
|
|
|
data.addr.sin_family = AF_INET;
|
|
data.addr.sin_addr.s_addr = ip_addr;
|
|
|
|
if(res->server.raw_smb)
|
|
{
|
|
servent = getservbyname("microsoft-ds","tcp");
|
|
if(servent != NULL)
|
|
data.addr.sin_port = servent->s_port;
|
|
else
|
|
data.addr.sin_port = htons (445);
|
|
}
|
|
else
|
|
{
|
|
servent = getservbyname("netbios-ssn","tcp");
|
|
if(servent != NULL)
|
|
data.addr.sin_port = servent->s_port;
|
|
else
|
|
data.addr.sin_port = htons (139);
|
|
}
|
|
|
|
data.fd = socket (AF_INET, SOCK_STREAM, 0);
|
|
if (data.fd < 0)
|
|
{
|
|
report_error("socket() call failed (%ld, %s).", errno, posix_strerror(errno));
|
|
|
|
(*error_ptr) = errno;
|
|
goto error_occured;
|
|
}
|
|
|
|
strlcpy (data.service, p->service, sizeof(data.service));
|
|
string_toupper (data.service);
|
|
strlcpy (data.username, p->username, sizeof(data.username));
|
|
strlcpy (data.password, p->password, sizeof(data.password));
|
|
|
|
data.given_max_xmit = max_transmit;
|
|
|
|
strlcpy (data.server_name, p->server_name, sizeof(data.server_name));
|
|
strlcpy (data.client_name, p->client_name, sizeof(data.client_name));
|
|
|
|
if (data.server_name[0] == '\0')
|
|
{
|
|
if (!res->server.raw_smb && strlen (p->server_ipname) > 16)
|
|
{
|
|
report_error("Server name '%s' is too long for NetBIOS (max %ld characters).",p->server_ipname,16);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto error_occured;
|
|
}
|
|
|
|
strlcpy (data.server_name, p->server_ipname, sizeof(data.server_name));
|
|
}
|
|
|
|
string_toupper (data.server_name);
|
|
|
|
if (data.client_name[0] == '\0')
|
|
{
|
|
if (!res->server.raw_smb && strlen (hostname) > 16)
|
|
{
|
|
report_error("Local host name '%s' is too long for NetBIOS (max %ld characters).", hostname, 16);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto error_occured;
|
|
}
|
|
|
|
strlcpy (data.client_name, hostname, sizeof(data.client_name));
|
|
string_toupper (data.client_name);
|
|
}
|
|
|
|
res->server.mount_data = data;
|
|
|
|
NewList((struct List *)&res->open_files);
|
|
|
|
if (smb_proc_connect (&res->server, error_ptr) < 0)
|
|
goto error_occured;
|
|
|
|
if (!use_E)
|
|
res->supports_E_known = TRUE;
|
|
|
|
(*smba_server_ptr) = res;
|
|
res = NULL;
|
|
|
|
result = 0;
|
|
|
|
error_occured:
|
|
|
|
if(res != NULL)
|
|
{
|
|
(*smb_error_class_ptr) = res->server.rcls;
|
|
(*smb_error_ptr) = res->server.err;
|
|
|
|
smba_cleanup_dircache (res);
|
|
free (res);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
smba_disconnect (smba_server_t * server)
|
|
{
|
|
if(server->server.mount_data.fd >= 0)
|
|
CloseSocket (server->server.mount_data.fd);
|
|
|
|
smba_cleanup_dircache(server);
|
|
|
|
free (server);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static int
|
|
make_open (smba_file_t * f, int need_fid, int writable, int truncate_file, int * error_ptr)
|
|
{
|
|
smba_server_t *s;
|
|
int result;
|
|
|
|
if (!f->is_valid || (need_fid && !f->dirent.opened))
|
|
{
|
|
ULONG now = get_current_time();
|
|
|
|
s = f->server;
|
|
|
|
if (!f->is_valid || f->attr_time == 0 || (now > f->attr_time && now - f->attr_time > ATTR_CACHE_TIME))
|
|
{
|
|
if (f->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
result = smb_query_path_information (&s->server, f->dirent.complete_path, f->dirent.len, 0, &f->dirent, error_ptr);
|
|
else
|
|
result = smb_proc_getattr_core (&s->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr);
|
|
|
|
if (result < 0)
|
|
goto out;
|
|
}
|
|
|
|
if ((f->dirent.attr & SMB_FILE_ATTRIBUTE_DIRECTORY) == 0) /* a regular file */
|
|
{
|
|
if (f->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
{
|
|
if(need_fid)
|
|
{
|
|
if(!f->dirent.opened)
|
|
{
|
|
LOG (("opening file '%s'\n", escape_name(f->dirent.complete_path)));
|
|
|
|
result = smb_proc_open (&s->server, f->dirent.complete_path, f->dirent.len, writable, truncate_file, &f->dirent, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
LOG (("file '%s' is already open (fileid=0x%04lx)\n", escape_name(f->dirent.complete_path), f->dirent.fileid));
|
|
}
|
|
}
|
|
}
|
|
else if (need_fid || !s->supports_E_known || s->supports_E)
|
|
{
|
|
if(!f->dirent.opened)
|
|
{
|
|
LOG (("opening file '%s'\n", escape_name(f->dirent.complete_path)));
|
|
|
|
result = smb_proc_open (&s->server, f->dirent.complete_path, f->dirent.len, writable, truncate_file, &f->dirent, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
LOG (("file '%s' is already open (fileid=0x%04lx)\n", escape_name(f->dirent.complete_path), f->dirent.fileid));
|
|
}
|
|
|
|
if (s->supports_E || !s->supports_E_known)
|
|
{
|
|
if (smb_proc_getattrE (&s->server, &f->dirent, error_ptr) < 0)
|
|
{
|
|
if (!s->supports_E_known)
|
|
{
|
|
s->supports_E_known = TRUE;
|
|
s->supports_E = FALSE;
|
|
} /* ignore errors here */
|
|
}
|
|
else
|
|
{
|
|
s->supports_E_known = TRUE;
|
|
s->supports_E = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* don't open directory, initialize directory cache */
|
|
if (f->dircache != NULL)
|
|
{
|
|
f->dircache->cache_for = NULL;
|
|
f->dircache->len = 0;
|
|
f->dircache = NULL;
|
|
}
|
|
}
|
|
|
|
f->attr_time = get_current_time();
|
|
f->is_valid = TRUE;
|
|
}
|
|
|
|
result = 0;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_open (smba_server_t * s, const char *name, int writable, int truncate_file, smba_file_t ** file, int * error_ptr)
|
|
{
|
|
smba_file_t *f;
|
|
int result;
|
|
|
|
(*file) = NULL;
|
|
|
|
f = malloc (sizeof(*f));
|
|
if(f == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
result = -1;
|
|
goto out;
|
|
}
|
|
|
|
memset(f,0,sizeof(*f));
|
|
|
|
f->dirent.complete_path = (char *)name;
|
|
f->dirent.len = strlen (name);
|
|
f->server = s;
|
|
|
|
LOG(("open '%s' (writable=%s, truncate_file=%s)\n", escape_name(name), writable ? "yes" : "no", truncate_file ? "yes" : "no"));
|
|
|
|
result = make_open (f, open_dont_need_fid, writable, truncate_file, error_ptr);
|
|
if (result < 0)
|
|
{
|
|
LOG(("open failed\n"));
|
|
goto out;
|
|
}
|
|
|
|
AddTail ((struct List *)&s->open_files, (struct Node *)f);
|
|
s->num_open_files++;
|
|
|
|
LOG(("file is ready (but hasn't actually been opened yet), number of open files = %ld\n",s->num_open_files));
|
|
|
|
(*file) = f;
|
|
f = NULL;
|
|
|
|
out:
|
|
|
|
if (f != NULL)
|
|
free (f);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static int
|
|
write_attr (smba_file_t * f, int * error_ptr)
|
|
{
|
|
int result;
|
|
|
|
LOG (("file '%s'\n", escape_name(f->dirent.complete_path)));
|
|
|
|
if(f->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
{
|
|
result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
result = smb_set_file_information (&f->server->server, &f->dirent, NULL, error_ptr);
|
|
}
|
|
else
|
|
{
|
|
result = make_open (f, open_dont_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
if (f->dirent.opened && f->server->supports_E)
|
|
result = smb_proc_setattrE (&f->server->server, f->dirent.fileid, &f->dirent, error_ptr);
|
|
else
|
|
result = smb_proc_setattr_core (&f->server->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr);
|
|
}
|
|
|
|
if (result < 0)
|
|
{
|
|
f->attr_time = 0;
|
|
goto out;
|
|
}
|
|
|
|
f->attr_dirty = FALSE;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
smba_close (smba_server_t * s, smba_file_t * f)
|
|
{
|
|
smba_file_t * found;
|
|
smba_file_t * file;
|
|
|
|
found = NULL;
|
|
|
|
for(file = (smba_file_t *)s->open_files.mlh_Head ;
|
|
file->node.mln_Succ != NULL ;
|
|
file = (smba_file_t *)file->node.mln_Succ)
|
|
{
|
|
if(file == f)
|
|
{
|
|
found = f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(found != NULL)
|
|
{
|
|
int ignored_error;
|
|
|
|
Remove((struct Node *)found);
|
|
|
|
if(found->attr_dirty)
|
|
write_attr(found, &ignored_error);
|
|
|
|
if (found->dirent.opened)
|
|
{
|
|
LOG (("closing file '%s' (fileid=0x%04lx)\n", escape_name(found->dirent.complete_path), found->dirent.fileid));
|
|
|
|
/* Don't change the modification time unless the contents
|
|
* of the file were changed.
|
|
*/
|
|
smb_proc_close (&found->server->server, found->dirent.fileid, found->modified ? found->dirent.mtime : 0, &ignored_error);
|
|
}
|
|
|
|
if (found->dircache != NULL)
|
|
{
|
|
found->dircache->cache_for = NULL;
|
|
found->dircache->len = 0;
|
|
found->dircache = NULL;
|
|
}
|
|
|
|
found->server->num_open_files--;
|
|
|
|
LOG(("file closed, number of open files = %ld\n",found->server->num_open_files));
|
|
|
|
free (found);
|
|
}
|
|
else
|
|
{
|
|
LOG(("file seems to be invalid\n"));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_read (smba_file_t * f, char *data, long len, const QUAD * const offset, int * error_ptr)
|
|
{
|
|
int max_receive = f->server->server.max_recv;
|
|
int num_bytes_read = 0;
|
|
int result;
|
|
|
|
result = make_open (f, open_need_fid, open_read_only, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
D(("read %ld bytes from offset %ld",len,offset));
|
|
|
|
/* SMB_COM_READ_ANDX supported? */
|
|
if (f->server->server.protocol >= PROTOCOL_LANMAN1 && !f->server->server.prefer_read_raw)
|
|
{
|
|
QUAD position_quad = (*offset);
|
|
int max_readx_size;
|
|
int count;
|
|
|
|
/* Calculate maximum number of bytes that could be transferred with
|
|
* a single SMBreadX packet...
|
|
*
|
|
* The message header accounts for
|
|
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
|
|
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
|
|
* = 32 bytes
|
|
*
|
|
* The parameters of a SMB_COM_READ_ANDX response account for
|
|
* 1(wordcount)+1(andxcommand)+1(andxreserved)+2(andxoffset)+
|
|
* 2(available)+2(datacompactionmode)+2(reserved)+2(datalength)+
|
|
* 2(dataoffset)+10(reserved2)
|
|
* = 25 bytes
|
|
*
|
|
* The data part of a SMB_COM_READ_ANDX response account for
|
|
* 2(bytecount)+1(pad) = 3 bytes,
|
|
* not including the actual payload
|
|
*
|
|
* This leaves 'max_receive' - 60 for the payload.
|
|
*/
|
|
|
|
max_readx_size = f->server->server.max_buffer_size - 60;
|
|
|
|
if((f->server->server.capabilities & CAP_LARGE_READX) != 0)
|
|
max_readx_size = 65535;
|
|
|
|
/* ZZZ SMB_COM_READ_ANDX uses the packet buffer to receive
|
|
* the data, which is why there is another limit to how
|
|
* much data can be received.
|
|
*/
|
|
if(max_readx_size > f->server->server.transmit_buffer_size - 62)
|
|
max_readx_size = f->server->server.transmit_buffer_size - 62;
|
|
|
|
LOG (("len = %ld, max_readx_size = %ld\n", len, max_readx_size));
|
|
|
|
do
|
|
{
|
|
count = min(len, max_readx_size);
|
|
|
|
result = smb_proc_readx (&f->server->server, &f->dirent, &position_quad, count, data, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
num_bytes_read += result;
|
|
len -= result;
|
|
add_64_plus_32_to_64(&position_quad,result,&position_quad);
|
|
data += result;
|
|
|
|
if(result < count)
|
|
{
|
|
D(("read returned fewer characters than expected (%ld < %ld)",result,count));
|
|
break;
|
|
}
|
|
}
|
|
while (len > 0);
|
|
}
|
|
/* SMB_COM_READ_RAW and SMB_COM_WRITE_RAW supported? */
|
|
else if ((f->server->server.capabilities & CAP_RAW_MODE) != 0)
|
|
{
|
|
int max_raw_size = f->server->server.max_raw_size;
|
|
QUAD position_quad = (*offset);
|
|
int n;
|
|
|
|
do
|
|
{
|
|
/* SMB_COM_READ_RAW can only read up to 65535 bytes. */
|
|
n = min(len, 65535);
|
|
|
|
/* The maximum number of bytes to be read in raw
|
|
* mode may be limited, too.
|
|
*/
|
|
if(n > max_raw_size)
|
|
n = max_raw_size;
|
|
|
|
/* Limit how much data we are prepared to receive? */
|
|
if(n > max_receive)
|
|
n = max_receive;
|
|
|
|
result = smb_proc_read_raw (&f->server->server, &f->dirent, &position_quad, n, data, error_ptr);
|
|
if(result <= 0)
|
|
{
|
|
D(("!!! wanted to read %ld bytes, got %ld",n,result));
|
|
break;
|
|
}
|
|
|
|
num_bytes_read += result;
|
|
len -= result;
|
|
add_64_plus_32_to_64(&position_quad,result,&position_quad);
|
|
data += result;
|
|
|
|
if(result < n)
|
|
{
|
|
D(("read returned fewer characters than expected (%ld < %ld)",result,n));
|
|
break;
|
|
}
|
|
}
|
|
while(len > 0);
|
|
}
|
|
else
|
|
{
|
|
int max_size_smb_com_read;
|
|
off_t position = offset->Low;
|
|
int count;
|
|
|
|
/* Calculate maximum number of bytes that could be transferred with
|
|
* a single SMBread packet...
|
|
*
|
|
* 'max_buffer_size' is the maximum size of a complete SMB message
|
|
* including the message header, the parameter and data blocks.
|
|
*
|
|
* The message header accounts for
|
|
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
|
|
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
|
|
* = 32 bytes
|
|
*
|
|
* The parameters of a SMB_COM_READ response account for
|
|
* 1(wordcount)+2(countofbytesreturned)+8(reserved)
|
|
* = 11 bytes
|
|
*
|
|
* The data part of a SMB_COM_READ response account for
|
|
* 2(bytecount)+1(bufferformat)+2(countofbytesread) = 5 bytes,
|
|
* not including the actual payload
|
|
*
|
|
* This leaves 'max_buffer_size' - 48 for the payload.
|
|
*/
|
|
/*max_size_smb_com_read = f->server->server.max_buffer_size - SMB_HEADER_LEN - 5 * 2 - 5;*/
|
|
max_size_smb_com_read = f->server->server.max_buffer_size - 48;
|
|
|
|
/* ZZZ SMB_COM_READ uses the packet buffer to receive
|
|
* the data, which is why there is another limit to how
|
|
* much data can be received.
|
|
*/
|
|
if(max_size_smb_com_read > f->server->server.transmit_buffer_size - 48)
|
|
max_size_smb_com_read = f->server->server.transmit_buffer_size - 48;
|
|
|
|
do
|
|
{
|
|
/* SMB_COM_READ can read only up to 65535 bytes anyway. */
|
|
count = min(len, 65535);
|
|
if(count == 0)
|
|
break;
|
|
|
|
if(count > max_size_smb_com_read)
|
|
count = max_size_smb_com_read;
|
|
|
|
/* Limit how much data we are prepared to receive? */
|
|
if(count > max_receive)
|
|
count = max_receive;
|
|
|
|
result = smb_proc_read (&f->server->server, &f->dirent, position, count, data, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
num_bytes_read += result;
|
|
len -= result;
|
|
position += result;
|
|
data += result;
|
|
|
|
if(result < count)
|
|
{
|
|
D(("read returned fewer characters than expected (%ld < %ld)",result,count));
|
|
break;
|
|
}
|
|
}
|
|
while (len > 0);
|
|
}
|
|
|
|
result = num_bytes_read;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_write (smba_file_t * f, const char *data, long len, const QUAD * const offset, int * error_ptr)
|
|
{
|
|
int num_bytes_written = 0;
|
|
int max_buffer_size;
|
|
int result;
|
|
|
|
result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
SHOWVALUE(f->server->server.max_buffer_size);
|
|
|
|
max_buffer_size = f->server->server.max_buffer_size;
|
|
|
|
/* SMB_COM_WRITE_ANDX supported? */
|
|
if (f->server->server.protocol >= PROTOCOL_LANMAN1 && !f->server->server.prefer_write_raw)
|
|
{
|
|
QUAD position_quad = (*offset);
|
|
int max_writex_size;
|
|
int n;
|
|
|
|
/* Calculate maximum number of bytes that could be transferred with
|
|
* a single SMB_COM_WRITE_ANDX packet...
|
|
*
|
|
* 'max_buffer_size' is the maximum size of a complete SMB message
|
|
* including the message header, the parameter and data blocks.
|
|
*
|
|
* The message header accounts for
|
|
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
|
|
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
|
|
* = 32 bytes
|
|
*
|
|
* The parameters of a SMB_COM_WRITE_ANDX command account for
|
|
* 1(word_count)+1(and_x_command)+1(and_x_reserved)+2(and_x_offset)+
|
|
* 2(fid)+4(offset)+4(timeout)+2(write_mode)+2(remaining)+2(reserved)+
|
|
* 2(data_length)+2(data_offset)+4(offset_high)
|
|
* = 29 bytes
|
|
*
|
|
* The data part of a SMB_COM_WRITE_ANDX command account for
|
|
* 2(byte_count)+1(pad) = 3 bytes, not including
|
|
* the actual payload
|
|
*
|
|
* This leaves 'max_buffer_size' - 64 for the payload.
|
|
*/
|
|
|
|
if(f->server->server.protocol >= PROTOCOL_NT1)
|
|
max_writex_size = max_buffer_size - 64;
|
|
else
|
|
max_writex_size = max_buffer_size - 60;
|
|
|
|
if((f->server->server.capabilities & CAP_LARGE_WRITEX) != 0)
|
|
max_writex_size = 65535;
|
|
|
|
LOG (("len = %ld, max_writex_size = %ld\n", len, max_writex_size));
|
|
|
|
do
|
|
{
|
|
n = min(len, max_writex_size);
|
|
|
|
ASSERT( n > 0 );
|
|
|
|
LOG(("writing %ld bytes; offset=%lu, len=%ld\n", n, offset, len));
|
|
|
|
result = smb_proc_writex(&f->server->server, &f->dirent, &position_quad, n, data, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
LOG(("number of bytes written = %ld\n", result));
|
|
|
|
data += result;
|
|
add_64_plus_32_to_64(&position_quad,result,&position_quad);
|
|
len -= result;
|
|
num_bytes_written += result;
|
|
}
|
|
while(len > 0);
|
|
}
|
|
else if ((f->server->server.capabilities & CAP_RAW_MODE) != 0)
|
|
{
|
|
int max_raw_size = f->server->server.max_raw_size;
|
|
int max_size_smb_com_write_raw;
|
|
QUAD position_quad = (*offset);
|
|
int n;
|
|
|
|
/* Try to send the maximum number of bytes with the two SMBwritebraw packets.
|
|
* This is how it breaks down:
|
|
*
|
|
* The message header accounts for
|
|
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
|
|
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
|
|
* = 32 bytes
|
|
*
|
|
* The parameters of a SMB_COM_WRITE_RAW command account for
|
|
* 1(wordcount)+2(fid)+2(countofbytes)+2(reserved1)+4(offset)+
|
|
* 4(timeout)+2(writemode)+4(reserved2)+2(datalength)+
|
|
* 2(dataoffset) = 25 bytes
|
|
*
|
|
* The data part of a SMB_COM_WRITE_RAW command accounts for
|
|
* 2(bytecount) = 2 bytes
|
|
*
|
|
* This leaves 'max_buffer_size' - 59 for the payload.
|
|
*/
|
|
/*max_size_smb_com_write_raw = 2 * f->server->server.max_buffer_size - (SMB_HEADER_LEN + 12 * sizeof (word) + 4) - 8;*/
|
|
max_size_smb_com_write_raw = max(max_buffer_size, max_raw_size) - 59;
|
|
|
|
/* SMB_COM_WRITE_RAW cannot transmit more than 65535 bytes. */
|
|
if(max_size_smb_com_write_raw > 65535)
|
|
max_size_smb_com_write_raw = 65535;
|
|
|
|
LOG (("len = %ld, max_size_smb_com_write_raw = %ld\n", len, max_size_smb_com_write_raw));
|
|
|
|
do
|
|
{
|
|
n = min(len, max_size_smb_com_write_raw);
|
|
|
|
ASSERT( n > 0 );
|
|
|
|
if(n > max_raw_size)
|
|
n = max_raw_size;
|
|
|
|
ASSERT( n <= 65535 );
|
|
|
|
result = smb_proc_write_raw (&f->server->server, &f->dirent, &position_quad, n, data, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
data += result;
|
|
add_64_plus_32_to_64(&position_quad,result,&position_quad);
|
|
len -= result;
|
|
num_bytes_written += result;
|
|
}
|
|
while(len > 0);
|
|
}
|
|
else
|
|
{
|
|
int max_size_smb_com_write, count;
|
|
off_t position = offset->Low;
|
|
|
|
/* Calculate maximum number of bytes that could be transferred with
|
|
* a single SMBwrite packet...
|
|
*
|
|
* 'max_buffer_size' is the maximum size of a complete SMB message
|
|
* including the message header, the parameter and data blocks.
|
|
*
|
|
* The message header accounts for
|
|
* 4(protocol)+1(command)+4(status)+1(flags)+2(flags2)+2(pidhigh)+
|
|
* 8(securityfeatures)+2(reserved)+2(tid)+2(pidlow)+2(uid)+2(mid)
|
|
* = 32 bytes
|
|
*
|
|
* The parameters of a SMB_COM_WRITE command account for
|
|
* 1(wordcount)+2(fid)+2(countofbytestowrite)+4(writeoffsetinbytes)+
|
|
* 2(estimateofremainingbytestobewritten) = 11 bytes
|
|
*
|
|
* The data part of a SMB_COM_WRITE command account for
|
|
* 2(bytecount)+1(bufferformat)+2(datalength) = 5 bytes, not including
|
|
* the actual payload
|
|
*
|
|
* This leaves 'max_buffer_size' - 48 for the payload.
|
|
*/
|
|
/*max_size_smb_com_write = f->server->server.max_buffer_size - (SMB_HEADER_LEN + 5 * sizeof (word) + 5) - 4;*/
|
|
max_size_smb_com_write = max_buffer_size - 48;
|
|
|
|
/* SMB_COM_WRITE cannot transmit more than 65535 bytes. */
|
|
if(max_size_smb_com_write > 65535)
|
|
max_size_smb_com_write = 65535;
|
|
|
|
LOG (("len = %ld, max_size_smb_com_write = %ld\n", len, max_size_smb_com_write));
|
|
|
|
do
|
|
{
|
|
count = min(len, max_size_smb_com_write);
|
|
if(count == 0)
|
|
break;
|
|
|
|
ASSERT( count <= 65535 );
|
|
|
|
result = smb_proc_write (&f->server->server, &f->dirent, position, count, data, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
len -= result;
|
|
position += result;
|
|
data += result;
|
|
num_bytes_written += result;
|
|
}
|
|
while (len > 0);
|
|
}
|
|
|
|
LOG(("num_bytes_written=%ld\n", num_bytes_written));
|
|
|
|
result = num_bytes_written;
|
|
|
|
out:
|
|
|
|
if (result < 0)
|
|
f->attr_time = 0;
|
|
|
|
/* Even if one write access failed, we may have succeeded
|
|
* at writing some data. Hence we update the cached file
|
|
* size here and take note of the change made.
|
|
*/
|
|
if(num_bytes_written > 0)
|
|
{
|
|
QUAD size_quad;
|
|
QUAD new_position_quad;
|
|
|
|
/* This file was modified. */
|
|
f->dirent.mtime = get_current_time();
|
|
f->modified = TRUE;
|
|
|
|
size_quad.Low = f->dirent.size_low;
|
|
size_quad.High = f->dirent.size_high;
|
|
|
|
add_64_plus_32_to_64(offset,num_bytes_written,&new_position_quad);
|
|
|
|
if(compare_64_to_64(&new_position_quad,&size_quad) > 0)
|
|
{
|
|
f->dirent.size_low = new_position_quad.Low;
|
|
f->dirent.size_high = new_position_quad.High;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* perform a single record-lock */
|
|
int
|
|
smba_lockrec (smba_file_t *f, long offset, long len, long mode, int unlocked, long timeout, int * error_ptr)
|
|
{
|
|
struct smb_lkrng *rec_lock = NULL;
|
|
int result;
|
|
|
|
result = make_open (f, open_need_fid, open_read_only, open_dont_truncate, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
if (unlocked)
|
|
mode |= 2;
|
|
|
|
rec_lock = malloc (sizeof (*rec_lock));
|
|
if (rec_lock == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
result = -1;
|
|
goto out;
|
|
}
|
|
|
|
rec_lock->offset = offset,
|
|
rec_lock->len = len;
|
|
|
|
result = smb_proc_lockingX (&f->server->server, &f->dirent, rec_lock, 1, mode, timeout, error_ptr);
|
|
|
|
out:
|
|
|
|
if(rec_lock != NULL)
|
|
free (rec_lock);
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_getattr (smba_file_t * f, smba_stat_t * data, int * error_ptr)
|
|
{
|
|
const struct smb_dirent * dirent;
|
|
int result;
|
|
ULONG now;
|
|
|
|
result = make_open (f, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
now = get_current_time();
|
|
|
|
if (f->attr_time == 0 || (now > f->attr_time && now - f->attr_time > ATTR_CACHE_TIME))
|
|
{
|
|
LOG (("file '%s'\n", escape_name(f->dirent.complete_path)));
|
|
|
|
if (f->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
{
|
|
if (f->dirent.opened)
|
|
result = smb_query_path_information (&f->server->server, NULL, 0, f->dirent.fileid, &f->dirent, error_ptr);
|
|
else
|
|
result = smb_query_path_information (&f->server->server, f->dirent.complete_path, f->dirent.len, 0, &f->dirent, error_ptr);
|
|
}
|
|
else
|
|
{
|
|
if (f->dirent.opened && f->server->supports_E)
|
|
result = smb_proc_getattrE (&f->server->server, &f->dirent, error_ptr);
|
|
else
|
|
result = smb_proc_getattr_core (&f->server->server, f->dirent.complete_path, f->dirent.len, &f->dirent, error_ptr);
|
|
}
|
|
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
f->attr_time = now;
|
|
}
|
|
|
|
dirent = &f->dirent;
|
|
|
|
data->is_dir = (dirent->attr & SMB_FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
data->is_read_only = (dirent->attr & SMB_FILE_ATTRIBUTE_READONLY) != 0;
|
|
data->is_hidden = (dirent->attr & SMB_FILE_ATTRIBUTE_HIDDEN) != 0;
|
|
data->is_system = (dirent->attr & SMB_FILE_ATTRIBUTE_SYSTEM) != 0;
|
|
data->is_changed_since_last_archive = (dirent->attr & SMB_FILE_ATTRIBUTE_ARCHIVE) != 0;
|
|
|
|
data->size_low = dirent->size_low;
|
|
data->size_high = dirent->size_high;
|
|
|
|
data->atime = dirent->atime;
|
|
data->ctime = dirent->ctime;
|
|
data->mtime = dirent->mtime;
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_setattr (smba_file_t * f, const smba_stat_t * data, const QUAD * const size, int * error_ptr)
|
|
{
|
|
BOOL times_changed = FALSE;
|
|
int result = 0;
|
|
dword attrs;
|
|
|
|
if (data != NULL)
|
|
{
|
|
if (data->atime != (time_t)-1 && f->dirent.atime != data->atime)
|
|
{
|
|
f->dirent.atime = data->atime;
|
|
times_changed = TRUE;
|
|
}
|
|
|
|
if (data->ctime != (time_t)-1 && f->dirent.ctime != data->ctime)
|
|
{
|
|
f->dirent.ctime = data->ctime;
|
|
times_changed = TRUE;
|
|
}
|
|
|
|
if (data->mtime != (time_t)-1 && f->dirent.mtime != data->mtime)
|
|
{
|
|
f->dirent.mtime = data->mtime;
|
|
times_changed = TRUE;
|
|
}
|
|
|
|
attrs = f->dirent.attr;
|
|
|
|
if (data->is_read_only)
|
|
attrs |= SMB_FILE_ATTRIBUTE_READONLY;
|
|
else
|
|
attrs &= ~SMB_FILE_ATTRIBUTE_READONLY;
|
|
|
|
if (data->is_changed_since_last_archive)
|
|
attrs |= SMB_FILE_ATTRIBUTE_ARCHIVE;
|
|
else
|
|
attrs &= ~SMB_FILE_ATTRIBUTE_ARCHIVE;
|
|
|
|
if(f->dirent.attr != attrs)
|
|
{
|
|
f->dirent.attr = attrs;
|
|
f->attr_dirty = TRUE;
|
|
}
|
|
|
|
if(f->attr_dirty || times_changed)
|
|
{
|
|
result = write_attr (f, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (size != NULL && (size->Low != f->dirent.size_low || size->High != f->dirent.size_high))
|
|
{
|
|
result = make_open (f, open_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
if(f->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
result = smb_set_file_information (&f->server->server, &f->dirent, size, error_ptr);
|
|
else
|
|
result = smb_proc_trunc (&f->server->server, f->dirent.fileid, size->Low, error_ptr);
|
|
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
f->modified = TRUE;
|
|
f->dirent.size_low = size->Low;
|
|
f->dirent.size_high = size->High;
|
|
}
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_readdir (smba_file_t * f, int offs, void *callback_data, smba_callback_t callback, int * error_ptr)
|
|
{
|
|
const struct smb_dirent * dirent;
|
|
int cache_index, o, eof, count = 0;
|
|
int num_entries;
|
|
smba_stat_t data;
|
|
int result;
|
|
ULONG now;
|
|
|
|
result = make_open (f, open_dont_need_fid, open_read_only, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
now = get_current_time();
|
|
|
|
if (f->dircache == NULL) /* get a cache */
|
|
{
|
|
dircache_t * dircache = f->server->dircache;
|
|
|
|
if (dircache->cache_for != NULL)
|
|
dircache->cache_for->dircache = NULL; /* steal it */
|
|
|
|
dircache->eof = dircache->len = dircache->base = 0;
|
|
dircache->cache_for = f;
|
|
|
|
f->dircache = dircache;
|
|
|
|
LOG (("stealing cache\n"));
|
|
}
|
|
else
|
|
{
|
|
if (now > f->dircache->created_at && now - f->dircache->created_at >= DIR_CACHE_TIME)
|
|
{
|
|
f->dircache->eof = f->dircache->len = f->dircache->base = 0;
|
|
|
|
LOG (("cache outdated\n"));
|
|
}
|
|
}
|
|
|
|
for (cache_index = offs ; ; cache_index++)
|
|
{
|
|
if (cache_index < f->dircache->base /* fill cache if necessary */
|
|
|| cache_index >= f->dircache->base + f->dircache->len)
|
|
{
|
|
if (cache_index >= f->dircache->base + f->dircache->len && f->dircache->eof)
|
|
break; /* nothing more to read */
|
|
|
|
LOG (("cachefill for '%s'\n", escape_name(f->dirent.complete_path)));
|
|
LOG (("\tbase was: %ld, len was: %ld, newbase=%ld\n", f->dircache->base, f->dircache->len, cache_index));
|
|
|
|
f->dircache->len = 0;
|
|
f->dircache->base = cache_index;
|
|
|
|
num_entries = smb_proc_readdir (&f->server->server, f->dirent.complete_path, cache_index, f->dircache->cache_size, f->dircache->cache, error_ptr);
|
|
|
|
/* We stop on error, or if the directory is empty. */
|
|
if (num_entries <= 0)
|
|
{
|
|
result = num_entries;
|
|
goto out;
|
|
}
|
|
|
|
/* Avoid some hits if restart/retry occured. Should fix the real root
|
|
* of this problem really, but I am not bored enough atm. -Piru
|
|
*/
|
|
if (f->dircache == NULL)
|
|
{
|
|
LOG (("lost dircache due to an error, bailing out!\n"));
|
|
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
result = -1;
|
|
goto out;
|
|
}
|
|
|
|
f->dircache->len = num_entries;
|
|
f->dircache->eof = (num_entries < f->dircache->cache_size);
|
|
f->dircache->created_at = now;
|
|
|
|
LOG (("cachefill with %ld entries\n", num_entries));
|
|
}
|
|
|
|
o = cache_index - f->dircache->base;
|
|
eof = (o >= (f->dircache->len - 1) && f->dircache->eof);
|
|
count++;
|
|
|
|
dirent = &f->dircache->cache[o];
|
|
|
|
LOG (("delivering '%s', cache_index=%ld, eof=%ld\n", escape_name(dirent->complete_path), cache_index, eof));
|
|
|
|
data.is_dir = (dirent->attr & SMB_FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
data.is_read_only = (dirent->attr & SMB_FILE_ATTRIBUTE_READONLY) != 0;
|
|
data.is_hidden = (dirent->attr & SMB_FILE_ATTRIBUTE_HIDDEN) != 0;
|
|
data.is_system = (dirent->attr & SMB_FILE_ATTRIBUTE_SYSTEM) != 0;
|
|
data.is_changed_since_last_archive = (dirent->attr & SMB_FILE_ATTRIBUTE_ARCHIVE) != 0;
|
|
data.size_low = dirent->size_low;
|
|
data.size_high = dirent->size_high;
|
|
data.atime = dirent->atime;
|
|
data.ctime = dirent->ctime;
|
|
data.mtime = dirent->mtime;
|
|
|
|
if ((*callback) (callback_data, cache_index, cache_index + 1, dirent->complete_path, eof, &data))
|
|
break;
|
|
}
|
|
|
|
result = count;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
invalidate_dircache (struct smba_server * server)
|
|
{
|
|
dircache_t * dircache = server->dircache;
|
|
|
|
ENTER();
|
|
|
|
if(dircache->cache_for != NULL)
|
|
D(("dircache->cache_for->dirent.complete_path = '%s'", escape_name(dircache->cache_for->dirent.complete_path)));
|
|
else
|
|
SHOWMSG("-- directory cache is empty --");
|
|
|
|
dircache->eof = dircache->len = dircache->base = 0;
|
|
|
|
if(dircache->cache_for != NULL)
|
|
{
|
|
dircache->cache_for->dircache = NULL;
|
|
dircache->cache_for = NULL;
|
|
}
|
|
|
|
LEAVE();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_create (smba_file_t * dir, const char *name, int * error_ptr)
|
|
{
|
|
struct smb_dirent entry;
|
|
char *path = NULL;
|
|
size_t len;
|
|
int result;
|
|
|
|
result = make_open (dir, open_dont_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
memset (&entry, 0, sizeof (entry));
|
|
|
|
entry.atime = entry.mtime = entry.ctime = get_current_time();
|
|
|
|
len = strlen(name);
|
|
|
|
path = malloc (len + 1 + dir->dirent.len + 1);
|
|
if(path == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
result = -1;
|
|
goto out;
|
|
}
|
|
|
|
memcpy (path, dir->dirent.complete_path, dir->dirent.len);
|
|
path[dir->dirent.len] = DOS_PATHSEP;
|
|
memcpy(&path[dir->dirent.len+1], name, len+1); /* Length includes terminating NUL byte. */
|
|
|
|
/* At least one Samba version crashed when using SMB_COM_NT_CREATE_ANDX
|
|
* to create a new file. Let's see if the old way works better.
|
|
*/
|
|
if (FALSE && dir->server->server.protocol >= PROTOCOL_LANMAN2)
|
|
{
|
|
int ignored_error;
|
|
|
|
result = smb_proc_open (&dir->server->server, path, strlen(path), open_writable, open_truncate, &entry, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
/* Close the file again, we don't really need it right now.
|
|
* Don't change the modification time.
|
|
*/
|
|
smb_proc_close(&dir->server->server,entry.fileid,0,&ignored_error);
|
|
}
|
|
else
|
|
{
|
|
result = smb_proc_create (&dir->server->server, path, strlen (path), &entry, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
|
|
if(path != NULL)
|
|
free (path);
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_mkdir (smba_file_t * dir, const char *name, int * error_ptr)
|
|
{
|
|
char *path = NULL;
|
|
int path_len;
|
|
int name_len;
|
|
int result;
|
|
|
|
result = make_open (dir, open_dont_need_fid, open_writable, open_dont_truncate, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
name_len = strlen (name);
|
|
path_len = dir->dirent.len + 1 + name_len;
|
|
|
|
path = malloc (path_len + 1);
|
|
if(path == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
result = -1;
|
|
goto out;
|
|
}
|
|
|
|
memcpy (path, dir->dirent.complete_path, dir->dirent.len);
|
|
path[dir->dirent.len] = DOS_PATHSEP;
|
|
memcpy (&path[dir->dirent.len + 1], name, name_len);
|
|
path[path_len] = '\0';
|
|
|
|
result = smb_proc_mkdir (&dir->server->server, path, path_len, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
out:
|
|
|
|
if(path != NULL)
|
|
free (path);
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static int
|
|
close_path (smba_server_t * s, const char *path, int * error_ptr)
|
|
{
|
|
int result = 0;
|
|
smba_file_t *p;
|
|
|
|
for (p = (smba_file_t *)s->open_files.mlh_Head;
|
|
p->node.mln_Succ != NULL;
|
|
p = (smba_file_t *)p->node.mln_Succ)
|
|
{
|
|
if (p->is_valid && compare_names(p->dirent.complete_path, path) == SAME)
|
|
{
|
|
if (p->dirent.opened)
|
|
{
|
|
/* Don't change the modification time unless the file was modified. */
|
|
result = smb_proc_close (&s->server, p->dirent.fileid, p->modified ? p->dirent.mtime : 0, error_ptr);
|
|
if(result < 0)
|
|
{
|
|
LOG(("closing '%s' with file id %ld failed\n", escape_name(path), p->dirent.fileid));
|
|
break;
|
|
}
|
|
|
|
p->dirent.opened = FALSE;
|
|
}
|
|
|
|
p->is_valid = FALSE;
|
|
}
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_remove (smba_server_t * s, const char *path, int * error_ptr)
|
|
{
|
|
int result;
|
|
|
|
result = close_path (s, path, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
result = smb_proc_unlink (&s->server, path, strlen (path), error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_rmdir (smba_server_t * s, const char *path, int * error_ptr)
|
|
{
|
|
int result;
|
|
|
|
result = close_path (s, path, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
result = smb_proc_rmdir (&s->server, path, strlen (path), error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
out:
|
|
|
|
return result;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_rename (smba_server_t * s, const char *from, const char *to, int * error_ptr)
|
|
{
|
|
int result;
|
|
|
|
result = close_path (s, from, error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
result = smb_proc_mv (&s->server, from, strlen (from), to, strlen (to), error_ptr);
|
|
if(result < 0)
|
|
goto out;
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_statfs (smba_server_t * s, long *bsize, long *blocks, long *bfree, int * error_ptr)
|
|
{
|
|
struct smb_dskattr dskattr;
|
|
int result;
|
|
|
|
result = smb_proc_dskattr (&s->server, &dskattr, error_ptr);
|
|
if (result < 0)
|
|
goto out;
|
|
|
|
(*bsize) = dskattr.blocksize * dskattr.allocblocks;
|
|
(*blocks) = dskattr.total;
|
|
(*bfree) = dskattr.free;
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
smb_invalidate_all_inodes (struct smb_server *server)
|
|
{
|
|
smba_file_t *f;
|
|
|
|
ENTER();
|
|
|
|
invalidate_dircache (server->abstraction);
|
|
|
|
for (f = (smba_file_t *)server->abstraction->open_files.mlh_Head;
|
|
f->node.mln_Succ != NULL;
|
|
f = (smba_file_t *)f->node.mln_Succ)
|
|
{
|
|
f->dirent.opened = FALSE;
|
|
f->is_valid = FALSE;
|
|
}
|
|
|
|
LEAVE();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
free_dircache(dircache_t * the_dircache)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < the_dircache->cache_size; i++)
|
|
{
|
|
if(the_dircache->cache[i].complete_path != NULL)
|
|
free(the_dircache->cache[i].complete_path);
|
|
}
|
|
|
|
free(the_dircache);
|
|
}
|
|
|
|
static void
|
|
smba_cleanup_dircache(struct smba_server * server)
|
|
{
|
|
dircache_t * the_dircache;
|
|
|
|
the_dircache = server->dircache;
|
|
if(the_dircache != NULL)
|
|
{
|
|
free_dircache(the_dircache);
|
|
server->dircache = NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
smba_setup_dircache (struct smba_server * server,int cache_size, int * error_ptr)
|
|
{
|
|
const int complete_path_size = SMB_MAXNAMELEN + 1;
|
|
dircache_t * the_dircache;
|
|
int result = -1;
|
|
int i;
|
|
|
|
the_dircache = malloc(sizeof(*the_dircache) + (cache_size-1) * sizeof(the_dircache->cache));
|
|
if(the_dircache == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
memset(the_dircache,0,sizeof(*the_dircache));
|
|
the_dircache->cache_size = cache_size;
|
|
|
|
for (i = 0; i < the_dircache->cache_size; i++)
|
|
{
|
|
the_dircache->cache[i].complete_path = malloc (complete_path_size);
|
|
if(the_dircache->cache[i].complete_path == NULL)
|
|
{
|
|
(*error_ptr) = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
the_dircache->cache[i].complete_path_size = complete_path_size;
|
|
}
|
|
|
|
server->dircache = the_dircache;
|
|
the_dircache = NULL;
|
|
|
|
result = 0;
|
|
|
|
out:
|
|
|
|
if(the_dircache != NULL)
|
|
free_dircache(the_dircache);
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static int
|
|
extract_service (const char *service, char *server, size_t server_size, char *share, size_t share_size,int * error_ptr)
|
|
{
|
|
char * share_start;
|
|
char * root_start;
|
|
char * complete_service;
|
|
char * service_copy;
|
|
int result = -1;
|
|
|
|
service_copy = malloc(strlen(service)+1);
|
|
if(service_copy == NULL)
|
|
{
|
|
report_error("Not enough memory.");
|
|
|
|
(*error_ptr) = ENOMEM;
|
|
|
|
goto out;
|
|
}
|
|
|
|
strcpy (service_copy, service);
|
|
complete_service = service_copy;
|
|
|
|
if (strlen (complete_service) < 4)
|
|
{
|
|
report_error("Service name '%s' is too short.",complete_service);
|
|
|
|
(*error_ptr) = EINVAL;
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (complete_service[0] != '/')
|
|
{
|
|
report_error("Service name '%s' must begin with '/'.",complete_service);
|
|
|
|
(*error_ptr) = EINVAL;
|
|
|
|
goto out;
|
|
}
|
|
|
|
while (complete_service[0] == '/')
|
|
complete_service += 1;
|
|
|
|
share_start = strchr (complete_service, '/');
|
|
if (share_start == NULL)
|
|
{
|
|
report_error("Share name '%s' must contain a '/'.",complete_service);
|
|
|
|
(*error_ptr) = EINVAL;
|
|
|
|
goto out;
|
|
}
|
|
|
|
(*share_start++) = '\0';
|
|
root_start = strchr (share_start, '/');
|
|
|
|
if (root_start != NULL)
|
|
(*root_start) = '\0';
|
|
|
|
if (strlen (complete_service) > 63)
|
|
{
|
|
report_error("Server name is too long in '%s' (max %ld characters).",service,63);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (strlen (share_start) > 255)
|
|
{
|
|
report_error("Share name is too long in '%s' (max %ld characters).",service,255);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
|
|
goto out;
|
|
}
|
|
|
|
strlcpy (server, complete_service, server_size);
|
|
strlcpy (share, share_start, share_size);
|
|
|
|
result = 0;
|
|
|
|
out:
|
|
|
|
if(service_copy != NULL)
|
|
free(service_copy);
|
|
|
|
return(result);
|
|
}
|
|
|
|
int
|
|
smba_start(
|
|
const char * service,
|
|
const char * opt_workgroup,
|
|
const char * opt_username,
|
|
const char * opt_password,
|
|
const char * opt_clientname,
|
|
const char * opt_servername,
|
|
int opt_cachesize,
|
|
int opt_max_transmit,
|
|
int opt_timeout,
|
|
int opt_raw_smb,
|
|
int opt_unicode,
|
|
int opt_prefer_write_raw,
|
|
int opt_write_behind,
|
|
int opt_prefer_read_raw,
|
|
int * error_ptr,
|
|
int * smb_error_class_ptr,
|
|
int * smb_error_ptr,
|
|
smba_server_t ** smba_server_ptr)
|
|
{
|
|
smba_connect_parameters_t par;
|
|
smba_server_t *the_server = NULL;
|
|
int i;
|
|
struct hostent *h;
|
|
int use_extended = 0;
|
|
char server_name[17], client_name[17]; /* Maximum length appears to be 16 characters for NetBIOS */
|
|
char username[64], password[64];
|
|
char workgroup[64]; /* Maximum length appears to be 15 characters */
|
|
char server[64], share[256];
|
|
in_addr_t server_ip_address;
|
|
int result = -1;
|
|
|
|
ASSERT( error_ptr != NULL );
|
|
ASSERT( smb_error_class_ptr != NULL );
|
|
ASSERT( smb_error_ptr != NULL );
|
|
|
|
(*error_ptr) = (*smb_error_class_ptr) = (*smb_error_ptr) = 0;
|
|
(*smba_server_ptr) = NULL;
|
|
(*username) = (*password) = (*server_name) = (*client_name) = '\0';
|
|
|
|
if(extract_service (service, server, sizeof(server), share, sizeof(share), error_ptr) < 0)
|
|
goto out;
|
|
|
|
server_ip_address = inet_addr (server);
|
|
if (server_ip_address == INADDR_NONE) /* name was given, not numeric */
|
|
{
|
|
int lookup_error;
|
|
|
|
LOG(("server name is '%s'\n", server));
|
|
|
|
h_errno = 0;
|
|
|
|
h = gethostbyname (server);
|
|
lookup_error = h_errno;
|
|
|
|
if (h != NULL)
|
|
{
|
|
server_ip_address = ((struct in_addr *)(h->h_addr))->s_addr;
|
|
}
|
|
else if (strlen(server) >= 16 || BroadcastNameQuery(server,"",(UBYTE *)&server_ip_address) != 0)
|
|
{
|
|
if(lookup_error == 0)
|
|
report_error("Could not look up network address of '%s' (%ld, %s).",server,errno,posix_strerror(errno));
|
|
else
|
|
report_error("Could not look up network address of '%s' (%ld, %s).",server,lookup_error,host_strerror(lookup_error));
|
|
|
|
(*error_ptr) = ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
LOG(("server network address found (%s)\n", Inet_NtoA(server_ip_address)));
|
|
}
|
|
else
|
|
{
|
|
char host_name[MAXHOSTNAMELEN+1];
|
|
|
|
LOG(("server network address is %s\n", server));
|
|
|
|
h_errno = 0;
|
|
|
|
h = gethostbyaddr ((char *) &server_ip_address, sizeof (server_ip_address), AF_INET);
|
|
if (h == NULL)
|
|
{
|
|
if(h_errno == 0)
|
|
{
|
|
report_error("Could not look up name of server with network address %s (%ld, %s).",server,errno,posix_strerror(errno));
|
|
|
|
if(errno != 0)
|
|
(*error_ptr) = errno;
|
|
else
|
|
(*error_ptr) = ENOENT;
|
|
}
|
|
else
|
|
{
|
|
report_error("Could not look up name of server with network address %s (%ld, %s).",server,h_errno,host_strerror(h_errno));
|
|
(*error_ptr) = ENOENT;
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
LOG(("server host name found (%s)\n",h->h_name));
|
|
|
|
/* Brian Willette: Now we will set the server name to the DNS
|
|
* hostname, hopefully this will be the same as the NetBIOS name for
|
|
* the server.
|
|
*
|
|
* We do this because the user supplied no hostname, and we
|
|
* need one for NetBIOS, this is the best guess choice we have
|
|
* NOTE: If the names are different between DNS and NetBIOS on
|
|
* the windows side, the user MUST use the -s option.
|
|
*/
|
|
for (i = 0; h->h_name[i] != '.' && h->h_name[i] != '\0' && i < 255; i++)
|
|
host_name[i] = h->h_name[i];
|
|
|
|
host_name[i] = '\0';
|
|
|
|
/* Make sure the hostname is 16 characters or less (for NetBIOS) */
|
|
if (!opt_raw_smb && strlen (host_name) > 16)
|
|
{
|
|
report_error("Server name '%s' is too long (max %ld characters).", host_name, 16);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy (server_name, host_name, sizeof(server_name));
|
|
}
|
|
|
|
if(opt_password != NULL)
|
|
{
|
|
if(strlen(opt_password) >= sizeof(password))
|
|
{
|
|
report_error("Password is too long (max %ld characters).", sizeof(password)-1);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy(password,opt_password,sizeof(password));
|
|
}
|
|
|
|
if(strlen(opt_username) >= sizeof(username))
|
|
{
|
|
report_error("User name '%s' is too long (max %ld characters).", username,sizeof(username)-1);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy(username,opt_username,sizeof(username));
|
|
string_toupper(username);
|
|
|
|
strlcpy (workgroup, opt_workgroup, sizeof(workgroup));
|
|
string_toupper (workgroup);
|
|
|
|
if(opt_servername != NULL)
|
|
{
|
|
if (!opt_raw_smb && strlen (opt_servername) > 16)
|
|
{
|
|
report_error("Server name '%s' is too long (max %ld characters).", opt_servername,16);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy (server_name, opt_servername, sizeof(server_name));
|
|
}
|
|
|
|
if(opt_clientname != NULL)
|
|
{
|
|
if (!opt_raw_smb && strlen (opt_clientname) > 16)
|
|
{
|
|
report_error("Client name '%s' is too long (max %ld characters).", opt_clientname,16);
|
|
|
|
(*error_ptr) = ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy (client_name, opt_clientname, sizeof(client_name));
|
|
}
|
|
|
|
if(opt_cachesize < 1)
|
|
opt_cachesize = DIRCACHE_SIZE;
|
|
else if (opt_cachesize < 10)
|
|
opt_cachesize = 10;
|
|
|
|
strlcpy(par.server_ipname,server,sizeof(par.server_ipname));
|
|
par.server_name = server_name;
|
|
par.client_name = client_name;
|
|
|
|
strlcpy(par.service,share,sizeof(par.service));
|
|
par.username = username;
|
|
par.password = password;
|
|
|
|
if(smba_connect (
|
|
&par,
|
|
server_ip_address,
|
|
use_extended,
|
|
workgroup,
|
|
opt_cachesize,
|
|
opt_max_transmit,
|
|
opt_timeout,
|
|
opt_raw_smb,
|
|
opt_unicode,
|
|
opt_prefer_write_raw,
|
|
opt_write_behind,
|
|
opt_prefer_read_raw,
|
|
error_ptr,
|
|
smb_error_class_ptr,
|
|
smb_error_ptr,
|
|
&the_server) < 0)
|
|
{
|
|
if((*error_ptr) == error_check_smb_error)
|
|
{
|
|
char * smb_class_name;
|
|
char * smb_code_text;
|
|
|
|
smb_translate_error_class_and_code((*smb_error_class_ptr),(*smb_error_ptr),&smb_class_name,&smb_code_text);
|
|
|
|
report_error("Could not connect to server '%s' (%ld/%ld, %s/%s).",server_name,(*smb_error_class_ptr),(*smb_error_ptr),smb_class_name,smb_code_text);
|
|
}
|
|
else
|
|
{
|
|
report_error("Could not connect to server '%s' (%ld, %s).",server_name,(*error_ptr),posix_strerror(*error_ptr));
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
(*smba_server_ptr) = the_server;
|
|
|
|
result = 0;
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_get_dircache_size(struct smba_server * server)
|
|
{
|
|
int result;
|
|
|
|
result = server->dircache->cache_size;
|
|
|
|
return(result);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
int
|
|
smba_change_dircache_size(struct smba_server * server,int cache_size)
|
|
{
|
|
const int complete_path_size = SMB_MAXNAMELEN + 1;
|
|
dircache_t * new_cache;
|
|
dircache_t * old_dircache = server->dircache;
|
|
int result;
|
|
int i;
|
|
|
|
result = old_dircache->cache_size;
|
|
|
|
/* We have to have a minimum cache size. */
|
|
if(cache_size < 10)
|
|
cache_size = 10;
|
|
|
|
/* Don't do anything if the cache size has not changed. */
|
|
if(cache_size == old_dircache->cache_size)
|
|
goto out;
|
|
|
|
/* Allocate a new cache and set it up with defaults. Note that
|
|
* the file name pointers in the cache are still not initialized.
|
|
*/
|
|
new_cache = malloc(sizeof(*new_cache) + (cache_size-1) * sizeof(new_cache->cache));
|
|
if(new_cache == NULL)
|
|
goto out;
|
|
|
|
memset(new_cache,0,sizeof(*new_cache));
|
|
new_cache->cache_size = cache_size;
|
|
|
|
/* If the new cache is to be larger than the old one, allocate additional file name slots. */
|
|
if(cache_size > old_dircache->cache_size)
|
|
{
|
|
/* Initialize the file name pointers so that free_dircache()
|
|
* can be called safely, if necessary.
|
|
*/
|
|
for(i = 0 ; i < cache_size ; i++)
|
|
new_cache->cache[i].complete_path = NULL;
|
|
|
|
/* Allocate memory for the file names. */
|
|
for(i = old_dircache->cache_size ; i < cache_size ; i++)
|
|
{
|
|
new_cache->cache[i].complete_path = malloc (complete_path_size);
|
|
if(new_cache->cache[i].complete_path == NULL)
|
|
{
|
|
free_dircache(new_cache);
|
|
goto out;
|
|
}
|
|
|
|
new_cache->cache[i].complete_path_size = complete_path_size;
|
|
}
|
|
|
|
/* Reuse the file name buffers allocated for the old cache. */
|
|
for(i = 0 ; i < old_dircache->cache_size ; i++)
|
|
{
|
|
new_cache->cache[i].complete_path = old_dircache->cache[i].complete_path;
|
|
new_cache->cache[i].complete_path_size = old_dircache->cache[i].complete_path_size;
|
|
|
|
old_dircache->cache[i].complete_path = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Reuse the file name buffers allocated for the old cache. */
|
|
for(i = 0 ; i < cache_size ; i++)
|
|
{
|
|
new_cache->cache[i].complete_path = old_dircache->cache[i].complete_path;
|
|
new_cache->cache[i].complete_path_size = old_dircache->cache[i].complete_path_size;
|
|
|
|
old_dircache->cache[i].complete_path = NULL;
|
|
}
|
|
}
|
|
|
|
invalidate_dircache(server);
|
|
|
|
free_dircache(old_dircache);
|
|
|
|
server->dircache = new_cache;
|
|
result = cache_size;
|
|
|
|
out:
|
|
|
|
return(result);
|
|
}
|