/* Unix SMB/CIFS implementation. generic testing tool Copyright (C) Andrew Tridgell 2003 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "includes.h" #include "dynconfig.h" #include "system/time.h" #include "system/filesys.h" #include "request.h" #include "libcli/raw/libcliraw.h" #include "librpc/gen_ndr/ndr_security.h" #define NSERVERS 2 #define NINSTANCES 2 /* global options */ static struct gentest_options { BOOL showall; BOOL analyze; BOOL analyze_always; BOOL analyze_continuous; uint_t max_open_handles; uint_t seed; uint_t numops; BOOL use_oplocks; char **ignore_patterns; const char *seeds_file; BOOL use_preset_seeds; BOOL fast_reconnect; } options; /* mapping between open handles on the server and local handles */ static struct { BOOL active; uint_t instance; uint_t server_fnum[NSERVERS]; const char *name; } *open_handles; static uint_t num_open_handles; /* state information for the servers. We open NINSTANCES connections to each server */ static struct { struct smbcli_state *cli[NINSTANCES]; char *server_name; char *share_name; struct cli_credentials *credentials; } servers[NSERVERS]; /* the seeds and flags for each operation */ static struct { uint_t seed; BOOL disabled; } *op_parms; /* oplock break info */ static struct { BOOL got_break; uint16_t fnum; uint16_t handle; uint8_t level; BOOL do_close; } oplocks[NSERVERS][NINSTANCES]; /* change notify reply info */ static struct { int notify_count; NTSTATUS status; struct smb_notify notify; } notifies[NSERVERS][NINSTANCES]; /* info relevant to the current operation */ static struct { const char *name; uint_t seed; NTSTATUS status; uint_t opnum; TALLOC_CTX *mem_ctx; } current_op; #define BAD_HANDLE 0xFFFE static BOOL oplock_handler(struct smbcli_transport *transport, uint16_t tid, uint16_t fnum, uint8_t level, void *private); static void idle_func(struct smbcli_transport *transport, void *private); /* check if a string should be ignored. This is used as the basis for all error ignore settings */ static BOOL ignore_pattern(const char *str) { int i; if (!options.ignore_patterns) return False; for (i=0;options.ignore_patterns[i];i++) { if (strcmp(options.ignore_patterns[i], str) == 0 || gen_fnmatch(options.ignore_patterns[i], str) == 0) { DEBUG(2,("Ignoring '%s'\n", str)); return True; } } return False; } /***************************************************** connect to the servers *******************************************************/ static BOOL connect_servers_fast(void) { int h, i; /* close all open files */ for (h=0;h<options.max_open_handles;h++) { if (!open_handles[h].active) continue; for (i=0;i<NSERVERS;i++) { if (NT_STATUS_IS_ERR((smbcli_close(servers[i].cli[open_handles[h].instance]->tree, open_handles[h].server_fnum[i])))) { return False; } open_handles[h].active = False; } } return True; } /***************************************************** connect to the servers *******************************************************/ static BOOL connect_servers(void) { int i, j; if (options.fast_reconnect && servers[0].cli[0]) { if (connect_servers_fast()) { return True; } } /* close any existing connections */ for (i=0;i<NSERVERS;i++) { for (j=0;j<NINSTANCES;j++) { if (servers[i].cli[j]) { smbcli_tdis(servers[i].cli[j]); talloc_free(servers[i].cli[j]); servers[i].cli[j] = NULL; } } } for (i=0;i<NSERVERS;i++) { for (j=0;j<NINSTANCES;j++) { NTSTATUS status; printf("Connecting to \\\\%s\\%s as %s - instance %d\n", servers[i].server_name, servers[i].share_name, servers[i].credentials->username, j); cli_credentials_set_workstation(servers[i].credentials, "gentest", CRED_SPECIFIED); status = smbcli_full_connection(NULL, &servers[i].cli[j], servers[i].server_name, servers[i].share_name, NULL, servers[i].credentials, NULL); if (!NT_STATUS_IS_OK(status)) { printf("Failed to connect to \\\\%s\\%s - %s\n", servers[i].server_name, servers[i].share_name, nt_errstr(status)); return False; } smbcli_oplock_handler(servers[i].cli[j]->transport, oplock_handler, NULL); smbcli_transport_idle_handler(servers[i].cli[j]->transport, idle_func, 50000, NULL); } } return True; } /* work out the time skew between the servers - be conservative */ static uint_t time_skew(void) { uint_t ret; ret = ABS(servers[0].cli[0]->transport->negotiate.server_time - servers[1].cli[0]->transport->negotiate.server_time); return ret + 300; } /* turn an fnum for an instance into a handle */ static uint_t fnum_to_handle(int server, int instance, uint16_t fnum) { uint_t i; for (i=0;i<options.max_open_handles;i++) { if (!open_handles[i].active || instance != open_handles[i].instance) continue; if (open_handles[i].server_fnum[server] == fnum) { return i; } } printf("Invalid fnum %d in fnum_to_handle on server %d instance %d\n", fnum, server, instance); return BAD_HANDLE; } /* add some newly opened handles */ static void gen_add_handle(int instance, const char *name, uint16_t fnums[NSERVERS]) { int i, h; for (h=0;h<options.max_open_handles;h++) { if (!open_handles[h].active) break; } if (h == options.max_open_handles) { /* we have to force close a random handle */ h = random() % options.max_open_handles; for (i=0;i<NSERVERS;i++) { if (NT_STATUS_IS_ERR((smbcli_close(servers[i].cli[open_handles[h].instance]->tree, open_handles[h].server_fnum[i])))) { printf("INTERNAL ERROR: Close failed when recovering handle! - %s\n", smbcli_errstr(servers[i].cli[open_handles[h].instance]->tree)); } } printf("Recovered handle %d\n", h); num_open_handles--; } for (i=0;i<NSERVERS;i++) { open_handles[h].server_fnum[i] = fnums[i]; open_handles[h].instance = instance; open_handles[h].active = True; open_handles[h].name = name; } num_open_handles++; printf("OPEN num_open_handles=%d h=%d s1=0x%x s2=0x%x (%s)\n", num_open_handles, h, open_handles[h].server_fnum[0], open_handles[h].server_fnum[1], name); } /* remove a closed handle */ static void gen_remove_handle(int instance, uint16_t fnums[NSERVERS]) { int h; for (h=0;h<options.max_open_handles;h++) { if (instance == open_handles[h].instance && open_handles[h].server_fnum[0] == fnums[0]) { open_handles[h].active = False; num_open_handles--; printf("CLOSE num_open_handles=%d h=%d s1=0x%x s2=0x%x (%s)\n", num_open_handles, h, open_handles[h].server_fnum[0], open_handles[h].server_fnum[1], open_handles[h].name); return; } } printf("Removing invalid handle!?\n"); exit(1); } /* return True with 'chance' probability as a percentage */ static BOOL gen_chance(uint_t chance) { return ((random() % 100) <= chance); } /* map an internal handle number to a server fnum */ static uint16_t gen_lookup_fnum(int server, uint16_t handle) { if (handle == BAD_HANDLE) return handle; return open_handles[handle].server_fnum[server]; } /* return a file handle */ static uint16_t gen_fnum(int instance) { uint16_t h; int count = 0; if (gen_chance(20)) return BAD_HANDLE; while (num_open_handles > 0 && count++ < 10*options.max_open_handles) { h = random() % options.max_open_handles; if (open_handles[h].active && open_handles[h].instance == instance) { return h; } } return BAD_HANDLE; } /* return a file handle, but skewed so we don't close the last couple of handles too readily */ static uint16_t gen_fnum_close(int instance) { if (num_open_handles < 3) { if (gen_chance(80)) return BAD_HANDLE; } return gen_fnum(instance); } /* generate an integer in a specified range */ static int gen_int_range(uint_t min, uint_t max) { uint_t r = random(); return min + (r % (1+max-min)); } /* return a fnum for use as a root fid be careful to call GEN_SET_FNUM() when you use this! */ static uint16_t gen_root_fid(int instance) { if (gen_chance(5)) return gen_fnum(instance); return 0; } /* generate a file offset */ static int gen_offset(void) { if (gen_chance(20)) return 0; return gen_int_range(0, 1024*1024); } /* generate a io count */ static int gen_io_count(void) { if (gen_chance(20)) return 0; return gen_int_range(0, 4096); } /* generate a filename */ static const char *gen_fname(void) { const char *names[] = {"\\gentest\\gentest.dat", "\\gentest\\foo", "\\gentest\\foo2.sym", "\\gentest\\foo3.dll", "\\gentest\\foo4", "\\gentest\\foo4:teststream1", "\\gentest\\foo4:teststream2", "\\gentest\\foo5.exe", "\\gentest\\foo5.exe:teststream3", "\\gentest\\foo5.exe:teststream4", "\\gentest\\foo6.com", "\\gentest\\blah", "\\gentest\\blah\\blergh.txt", "\\gentest\\blah\\blergh2", "\\gentest\\blah\\blergh3.txt", "\\gentest\\blah\\blergh4", "\\gentest\\blah\\blergh5.txt", "\\gentest\\blah\\blergh5", "\\gentest\\blah\\.", #if 0 /* this causes problem with w2k3 */ "\\gentest\\blah\\..", #endif "\\gentest\\a_very_long_name.bin", "\\gentest\\x.y", "\\gentest\\blah"}; int i; do { i = gen_int_range(0, ARRAY_SIZE(names)-1); } while (ignore_pattern(names[i])); return names[i]; } /* generate a filename with a higher chance of choosing an already open file */ static const char *gen_fname_open(int instance) { uint16_t h; h = gen_fnum(instance); if (h == BAD_HANDLE) { return gen_fname(); } return open_handles[h].name; } /* generate a wildcard pattern */ static const char *gen_pattern(void) { int i; const char *names[] = {"\\gentest\\*.dat", "\\gentest\\*", "\\gentest\\*.*", "\\gentest\\blah\\*.*", "\\gentest\\blah\\*", "\\gentest\\?"}; if (gen_chance(50)) return gen_fname(); do { i = gen_int_range(0, ARRAY_SIZE(names)-1); } while (ignore_pattern(names[i])); return names[i]; } /* generate a bitmask */ static uint32_t gen_bits_mask(uint_t mask) { uint_t ret = random(); return ret & mask; } /* generate a bitmask with high probability of the first mask and low of the second */ static uint32_t gen_bits_mask2(uint32_t mask1, uint32_t mask2) { if (gen_chance(10)) return gen_bits_mask(mask2); return gen_bits_mask(mask1); } /* generate a boolean */ static BOOL gen_bool(void) { return gen_bits_mask2(0x1, 0xFF); } /* generate ntrename flags */ static uint16_t gen_rename_flags(void) { if (gen_chance(30)) return RENAME_FLAG_RENAME; if (gen_chance(30)) return RENAME_FLAG_HARD_LINK; if (gen_chance(30)) return RENAME_FLAG_COPY; return gen_bits_mask(0xFFFF); } /* return a lockingx lock mode */ static uint16_t gen_lock_mode(void) { if (gen_chance(5)) return gen_bits_mask(0xFFFF); if (gen_chance(20)) return gen_bits_mask(0x1F); return gen_bits_mask(LOCKING_ANDX_SHARED_LOCK | LOCKING_ANDX_LARGE_FILES); } /* generate a pid */ static uint16_t gen_pid(void) { if (gen_chance(10)) return gen_bits_mask(0xFFFF); return getpid(); } /* generate a lock count */ static off_t gen_lock_count(void) { return gen_int_range(0, 3); } /* generate a ntcreatex flags field */ static uint32_t gen_ntcreatex_flags(void) { if (gen_chance(70)) return NTCREATEX_FLAGS_EXTENDED; return gen_bits_mask2(0x1F, 0xFFFFFFFF); } /* generate a NT access mask */ static uint32_t gen_access_mask(void) { if (gen_chance(50)) return SEC_FLAG_MAXIMUM_ALLOWED; if (gen_chance(20)) return SEC_FILE_ALL; return gen_bits_mask(0xFFFFFFFF); } /* generate a ntcreatex create options bitfield */ static uint32_t gen_create_options(void) { if (gen_chance(20)) return gen_bits_mask(0xFFFFFFFF); if (gen_chance(50)) return 0; return gen_bits_mask(NTCREATEX_OPTIONS_DELETE_ON_CLOSE | NTCREATEX_OPTIONS_DIRECTORY); } /* generate a ntcreatex open disposition */ static uint32_t gen_open_disp(void) { if (gen_chance(10)) return gen_bits_mask(0xFFFFFFFF); return gen_int_range(0, 5); } /* generate an openx open mode */ static uint16_t gen_openx_mode(void) { if (gen_chance(20)) return gen_bits_mask(0xFFFF); if (gen_chance(20)) return gen_bits_mask(0xFF); return OPENX_MODE_DENY_NONE | gen_bits_mask(0x3); } /* generate an openx flags field */ static uint16_t gen_openx_flags(void) { if (gen_chance(20)) return gen_bits_mask(0xFFFF); return gen_bits_mask(0x7); } /* generate an openx open function */ static uint16_t gen_openx_func(void) { if (gen_chance(20)) return gen_bits_mask(0xFFFF); return gen_bits_mask(0x13); } /* generate a file attrib combination */ static uint32_t gen_attrib(void) { if (gen_chance(20)) return gen_bits_mask(0xFFFFFFFF); return gen_bits_mask(FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_DIRECTORY); } /* generate a unix timestamp */ static time_t gen_timet(void) { if (gen_chance(30)) return 0; return (time_t)random(); } /* generate a unix timestamp */ static NTTIME gen_nttime(void) { NTTIME ret; unix_to_nt_time(&ret, gen_timet()); return ret; } /* generate a milliseconds protocol timeout */ static uint32_t gen_timeout(void) { if (gen_chance(98)) return 0; return random() % 50; } /* generate a file allocation size */ static uint_t gen_alloc_size(void) { uint_t ret; if (gen_chance(30)) return 0; ret = random() % 4*1024*1024; /* give a high chance of a round number */ if (gen_chance(60)) { ret &= ~(1024*1024 - 1); } return ret; } /* generate an ea_struct */ static struct ea_struct gen_ea_struct(void) { struct ea_struct ea; const char *names[] = {"EAONE", "", "FOO!", " WITH SPACES ", ".", "AVERYLONGATTRIBUTENAME"}; const char *values[] = {"VALUE1", "", "NOT MUCH FOO", " LEADING SPACES ", ":", "ASOMEWHATLONGERATTRIBUTEVALUE"}; int i; do { i = gen_int_range(0, ARRAY_SIZE(names)-1); } while (ignore_pattern(names[i])); ea.name.s = names[i]; do { i = gen_int_range(0, ARRAY_SIZE(values)-1); } while (ignore_pattern(values[i])); ea.value = data_blob(values[i], strlen(values[i])); if (gen_chance(10)) ea.flags = gen_bits_mask(0xFF); ea.flags = 0; return ea; } /* this is called when a change notify reply comes in */ static void async_notify(struct smbcli_request *req) { struct smb_notify notify; NTSTATUS status; int i, j; uint16_t tid; struct smbcli_transport *transport = req->transport; tid = SVAL(req->in.hdr, HDR_TID); status = smb_raw_changenotify_recv(req, current_op.mem_ctx, ¬ify); if (NT_STATUS_IS_OK(status)) { printf("notify tid=%d num_changes=%d action=%d name=%s\n", tid, notify.out.num_changes, notify.out.changes[0].action, notify.out.changes[0].name.s); } for (i=0;i<NSERVERS;i++) { for (j=0;j<NINSTANCES;j++) { if (transport == servers[i].cli[j]->transport && tid == servers[i].cli[j]->tree->tid) { notifies[i][j].notify_count++; notifies[i][j].status = status; notifies[i][j].notify = notify; } } } } /* the oplock handler will either ack the break or close the file */ static BOOL oplock_handler(struct smbcli_transport *transport, uint16_t tid, uint16_t fnum, uint8_t level, void *private) { union smb_close io; NTSTATUS status; int i, j; BOOL do_close; struct smbcli_tree *tree = NULL; srandom(current_op.seed); do_close = gen_chance(50); for (i=0;i<NSERVERS;i++) { for (j=0;j<NINSTANCES;j++) { if (transport == servers[i].cli[j]->transport && tid == servers[i].cli[j]->tree->tid) { oplocks[i][j].got_break = True; oplocks[i][j].fnum = fnum; oplocks[i][j].handle = fnum_to_handle(i, j, fnum); oplocks[i][j].level = level; oplocks[i][j].do_close = do_close; tree = servers[i].cli[j]->tree; } } } if (!tree) { printf("Oplock break not for one of our trees!?\n"); return False; } if (!do_close) { printf("oplock ack fnum=%d\n", fnum); return smbcli_oplock_ack(tree, fnum, level); } printf("oplock close fnum=%d\n", fnum); io.close.level = RAW_CLOSE_CLOSE; io.close.in.fnum = fnum; io.close.in.write_time = 0; status = smb_raw_close(tree, &io); if (!NT_STATUS_IS_OK(status)) { printf("WARNING: close failed in oplock_handler_close - %s\n", nt_errstr(status)); } return True; } /* the idle function tries to cope with getting an oplock break on a connection, and an operation on another connection blocking until that break is acked we check for operations on all transports in the idle function */ static void idle_func(struct smbcli_transport *transport, void *private) { int i, j; for (i=0;i<NSERVERS;i++) { for (j=0;j<NINSTANCES;j++) { if (servers[i].cli[j] && transport != servers[i].cli[j]->transport) { smbcli_transport_process(servers[i].cli[j]->transport); } } } } /* compare NTSTATUS, using checking ignored patterns */ static BOOL compare_status(NTSTATUS status1, NTSTATUS status2) { if (NT_STATUS_EQUAL(status1, status2)) return True; /* one code being an error and the other OK is always an error */ if (NT_STATUS_IS_OK(status1) || NT_STATUS_IS_OK(status2)) return False; /* if we are ignoring one of the status codes then consider this a match */ if (ignore_pattern(nt_errstr(status1)) || ignore_pattern(nt_errstr(status2))) { return True; } return False; } /* check for pending packets on all connections */ static void check_pending(void) { int i, j; msleep(20); for (j=0;j<NINSTANCES;j++) { for (i=0;i<NSERVERS;i++) { smbcli_transport_process(servers[i].cli[j]->transport); } } } /* check that the same oplock breaks have been received by all instances */ static BOOL check_oplocks(const char *call) { int i, j; int tries = 0; again: check_pending(); for (j=0;j<NINSTANCES;j++) { for (i=1;i<NSERVERS;i++) { if (oplocks[0][j].got_break != oplocks[i][j].got_break || oplocks[0][j].handle != oplocks[i][j].handle || oplocks[0][j].level != oplocks[i][j].level) { if (tries++ < 10) goto again; printf("oplock break inconsistent - %d/%d/%d vs %d/%d/%d\n", oplocks[0][j].got_break, oplocks[0][j].handle, oplocks[0][j].level, oplocks[i][j].got_break, oplocks[i][j].handle, oplocks[i][j].level); return False; } } } /* if we got a break and closed then remove the handle */ for (j=0;j<NINSTANCES;j++) { if (oplocks[0][j].got_break && oplocks[0][j].do_close) { uint16_t fnums[NSERVERS]; for (i=0;i<NSERVERS;i++) { fnums[i] = oplocks[i][j].fnum; } gen_remove_handle(j, fnums); break; } } return True; } /* check that the same change notify info has been received by all instances */ static BOOL check_notifies(const char *call) { int i, j; int tries = 0; again: check_pending(); for (j=0;j<NINSTANCES;j++) { for (i=1;i<NSERVERS;i++) { int n; struct smb_notify not1, not2; if (notifies[0][j].notify_count != notifies[i][j].notify_count) { if (tries++ < 10) goto again; printf("Notify count inconsistent %d %d\n", notifies[0][j].notify_count, notifies[i][j].notify_count); return False; } if (notifies[0][j].notify_count == 0) continue; if (!NT_STATUS_EQUAL(notifies[0][j].status, notifies[i][j].status)) { printf("Notify status mismatch - %s - %s\n", nt_errstr(notifies[0][j].status), nt_errstr(notifies[i][j].status)); return False; } if (!NT_STATUS_IS_OK(notifies[0][j].status)) { continue; } not1 = notifies[0][j].notify; not2 = notifies[i][j].notify; for (n=0;n<not1.out.num_changes;n++) { if (not1.out.changes[n].action != not2.out.changes[n].action) { printf("Notify action %d inconsistent %d %d\n", n, not1.out.changes[n].action, not2.out.changes[n].action); return False; } if (strcmp(not1.out.changes[n].name.s, not2.out.changes[n].name.s)) { printf("Notify name %d inconsistent %s %s\n", n, not1.out.changes[n].name.s, not2.out.changes[n].name.s); return False; } if (not1.out.changes[n].name.private_length != not2.out.changes[n].name.private_length) { printf("Notify name length %d inconsistent %d %d\n", n, not1.out.changes[n].name.private_length, not2.out.changes[n].name.private_length); return False; } } } } ZERO_STRUCT(notifies); return True; } #define GEN_COPY_PARM do { \ int i; \ for (i=1;i<NSERVERS;i++) { \ parm[i] = parm[0]; \ } \ } while (0) #define GEN_CALL(call) do { \ int i; \ ZERO_STRUCT(oplocks); \ ZERO_STRUCT(notifies); \ for (i=0;i<NSERVERS;i++) { \ struct smbcli_tree *tree = servers[i].cli[instance]->tree; \ status[i] = call; \ } \ current_op.status = status[0]; \ for (i=1;i<NSERVERS;i++) { \ if (!compare_status(status[i], status[0])) { \ printf("status different in %s - %s %s\n", #call, \ nt_errstr(status[0]), nt_errstr(status[i])); \ return False; \ } \ } \ if (!check_oplocks(#call)) return False; \ if (!check_notifies(#call)) return False; \ if (!NT_STATUS_IS_OK(status[0])) { \ return True; \ } \ } while(0) #define ADD_HANDLE(name, field) do { \ uint16_t fnums[NSERVERS]; \ int i; \ for (i=0;i<NSERVERS;i++) { \ fnums[i] = parm[i].field; \ } \ gen_add_handle(instance, name, fnums); \ } while(0) #define REMOVE_HANDLE(field) do { \ uint16_t fnums[NSERVERS]; \ int i; \ for (i=0;i<NSERVERS;i++) { \ fnums[i] = parm[i].field; \ } \ gen_remove_handle(instance, fnums); \ } while(0) #define GEN_SET_FNUM(field) do { \ int i; \ for (i=0;i<NSERVERS;i++) { \ parm[i].field = gen_lookup_fnum(i, parm[i].field); \ } \ } while(0) #define CHECK_EQUAL(field) do { \ if (parm[0].field != parm[1].field && !ignore_pattern(#field)) { \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)parm[0].field, (int)parm[1].field); \ return False; \ } \ } while(0) #define CHECK_WSTR_EQUAL(field) do { \ if ((!parm[0].field.s && parm[1].field.s) || (parm[0].field.s && !parm[1].field.s)) { \ printf("%s is NULL!\n", #field); \ return False; \ } \ if (parm[0].field.s && strcmp(parm[0].field.s, parm[1].field.s) != 0 && !ignore_pattern(#field)) { \ printf("Mismatch in %s - %s %s\n", #field, \ parm[0].field.s, parm[1].field.s); \ return False; \ } \ CHECK_EQUAL(field.private_length); \ } while(0) #define CHECK_BLOB_EQUAL(field) do { \ if (memcmp(parm[0].field.data, parm[1].field.data, parm[0].field.length) != 0 && !ignore_pattern(#field)) { \ printf("Mismatch in %s\n", #field); \ return False; \ } \ CHECK_EQUAL(field.length); \ } while(0) #define CHECK_TIMES_EQUAL(field) do { \ if (ABS(parm[0].field - parm[1].field) > time_skew() && \ !ignore_pattern(#field)) { \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)parm[0].field, (int)parm[1].field); \ return False; \ } \ } while(0) #define CHECK_NTTIMES_EQUAL(field) do { \ if (ABS(nt_time_to_unix(parm[0].field) - \ nt_time_to_unix(parm[1].field)) > time_skew() && \ !ignore_pattern(#field)) { \ printf("Mismatch in %s - 0x%x 0x%x\n", #field, \ (int)nt_time_to_unix(parm[0].field), \ (int)nt_time_to_unix(parm[1].field)); \ return False; \ } \ } while(0) /* generate openx operations */ static BOOL handler_openx(int instance) { union smb_open parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].openx.level = RAW_OPEN_OPENX; parm[0].openx.in.flags = gen_openx_flags(); parm[0].openx.in.open_mode = gen_openx_mode(); parm[0].openx.in.search_attrs = gen_attrib(); parm[0].openx.in.file_attrs = gen_attrib(); parm[0].openx.in.write_time = gen_timet(); parm[0].openx.in.open_func = gen_openx_func(); parm[0].openx.in.size = gen_io_count(); parm[0].openx.in.timeout = gen_timeout(); parm[0].openx.in.fname = gen_fname_open(instance); if (!options.use_oplocks) { /* mask out oplocks */ parm[0].openx.in.flags &= ~(OPENX_FLAGS_REQUEST_OPLOCK| OPENX_FLAGS_REQUEST_BATCH_OPLOCK); } GEN_COPY_PARM; GEN_CALL(smb_raw_open(tree, current_op.mem_ctx, &parm[i])); CHECK_EQUAL(openx.out.attrib); CHECK_EQUAL(openx.out.size); CHECK_EQUAL(openx.out.access); CHECK_EQUAL(openx.out.ftype); CHECK_EQUAL(openx.out.devstate); CHECK_EQUAL(openx.out.action); CHECK_EQUAL(openx.out.access_mask); CHECK_EQUAL(openx.out.unknown); CHECK_TIMES_EQUAL(openx.out.write_time); /* open creates a new file handle */ ADD_HANDLE(parm[0].openx.in.fname, openx.out.fnum); return True; } /* generate open operations */ static BOOL handler_open(int instance) { union smb_open parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].openold.level = RAW_OPEN_OPEN; parm[0].openold.in.open_mode = gen_bits_mask2(0xF, 0xFFFF); parm[0].openold.in.search_attrs = gen_attrib(); parm[0].openold.in.fname = gen_fname_open(instance); if (!options.use_oplocks) { /* mask out oplocks */ parm[0].openold.in.open_mode &= ~(OPENX_FLAGS_REQUEST_OPLOCK| OPENX_FLAGS_REQUEST_BATCH_OPLOCK); } GEN_COPY_PARM; GEN_CALL(smb_raw_open(tree, current_op.mem_ctx, &parm[i])); CHECK_EQUAL(openold.out.attrib); CHECK_TIMES_EQUAL(openold.out.write_time); CHECK_EQUAL(openold.out.size); CHECK_EQUAL(openold.out.rmode); /* open creates a new file handle */ ADD_HANDLE(parm[0].openold.in.fname, openold.out.fnum); return True; } /* generate ntcreatex operations */ static BOOL handler_ntcreatex(int instance) { union smb_open parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].ntcreatex.level = RAW_OPEN_NTCREATEX; parm[0].ntcreatex.in.flags = gen_ntcreatex_flags(); parm[0].ntcreatex.in.root_fid = gen_root_fid(instance); parm[0].ntcreatex.in.access_mask = gen_access_mask(); parm[0].ntcreatex.in.alloc_size = gen_alloc_size(); parm[0].ntcreatex.in.file_attr = gen_attrib(); parm[0].ntcreatex.in.share_access = gen_bits_mask2(0x7, 0xFFFFFFFF); parm[0].ntcreatex.in.open_disposition = gen_open_disp(); parm[0].ntcreatex.in.create_options = gen_create_options(); parm[0].ntcreatex.in.impersonation = gen_bits_mask2(0, 0xFFFFFFFF); parm[0].ntcreatex.in.security_flags = gen_bits_mask2(0, 0xFF); parm[0].ntcreatex.in.fname = gen_fname_open(instance); if (!options.use_oplocks) { /* mask out oplocks */ parm[0].ntcreatex.in.flags &= ~(NTCREATEX_FLAGS_REQUEST_OPLOCK| NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK); } GEN_COPY_PARM; if (parm[0].ntcreatex.in.root_fid != 0) { GEN_SET_FNUM(ntcreatex.in.root_fid); } GEN_CALL(smb_raw_open(tree, current_op.mem_ctx, &parm[i])); CHECK_EQUAL(ntcreatex.out.oplock_level); CHECK_EQUAL(ntcreatex.out.create_action); CHECK_NTTIMES_EQUAL(ntcreatex.out.create_time); CHECK_NTTIMES_EQUAL(ntcreatex.out.access_time); CHECK_NTTIMES_EQUAL(ntcreatex.out.write_time); CHECK_NTTIMES_EQUAL(ntcreatex.out.change_time); CHECK_EQUAL(ntcreatex.out.attrib); CHECK_EQUAL(ntcreatex.out.alloc_size); CHECK_EQUAL(ntcreatex.out.size); CHECK_EQUAL(ntcreatex.out.file_type); CHECK_EQUAL(ntcreatex.out.ipc_state); CHECK_EQUAL(ntcreatex.out.is_directory); /* ntcreatex creates a new file handle */ ADD_HANDLE(parm[0].ntcreatex.in.fname, ntcreatex.out.fnum); return True; } /* generate close operations */ static BOOL handler_close(int instance) { union smb_close parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].close.level = RAW_CLOSE_CLOSE; parm[0].close.in.fnum = gen_fnum_close(instance); parm[0].close.in.write_time = gen_timet(); GEN_COPY_PARM; GEN_SET_FNUM(close.in.fnum); GEN_CALL(smb_raw_close(tree, &parm[i])); REMOVE_HANDLE(close.in.fnum); return True; } /* generate unlink operations */ static BOOL handler_unlink(int instance) { struct smb_unlink parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.pattern = gen_pattern(); parm[0].in.attrib = gen_attrib(); GEN_COPY_PARM; GEN_CALL(smb_raw_unlink(tree, &parm[i])); return True; } /* generate chkpath operations */ static BOOL handler_chkpath(int instance) { struct smb_chkpath parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.path = gen_fname_open(instance); GEN_COPY_PARM; GEN_CALL(smb_raw_chkpath(tree, &parm[i])); return True; } /* generate mkdir operations */ static BOOL handler_mkdir(int instance) { union smb_mkdir parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].mkdir.level = RAW_MKDIR_MKDIR; parm[0].mkdir.in.path = gen_fname_open(instance); GEN_COPY_PARM; GEN_CALL(smb_raw_mkdir(tree, &parm[i])); return True; } /* generate rmdir operations */ static BOOL handler_rmdir(int instance) { struct smb_rmdir parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.path = gen_fname_open(instance); GEN_COPY_PARM; GEN_CALL(smb_raw_rmdir(tree, &parm[i])); return True; } /* generate rename operations */ static BOOL handler_rename(int instance) { union smb_rename parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.level = RAW_RENAME_RENAME; parm[0].rename.in.pattern1 = gen_pattern(); parm[0].rename.in.pattern2 = gen_pattern(); parm[0].rename.in.attrib = gen_attrib(); GEN_COPY_PARM; GEN_CALL(smb_raw_rename(tree, &parm[i])); return True; } /* generate ntrename operations */ static BOOL handler_ntrename(int instance) { union smb_rename parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.level = RAW_RENAME_NTRENAME; parm[0].ntrename.in.old_name = gen_fname(); parm[0].ntrename.in.new_name = gen_fname(); parm[0].ntrename.in.attrib = gen_attrib(); parm[0].ntrename.in.cluster_size = gen_bits_mask2(0, 0xFFFFFFF); parm[0].ntrename.in.flags = gen_rename_flags(); GEN_COPY_PARM; GEN_CALL(smb_raw_rename(tree, &parm[i])); return True; } /* generate seek operations */ static BOOL handler_seek(int instance) { struct smb_seek parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].in.fnum = gen_fnum(instance); parm[0].in.mode = gen_bits_mask2(0x3, 0xFFFF); parm[0].in.offset = gen_offset(); GEN_COPY_PARM; GEN_SET_FNUM(in.fnum); GEN_CALL(smb_raw_seek(tree, &parm[i])); CHECK_EQUAL(out.offset); return True; } /* generate readx operations */ static BOOL handler_readx(int instance) { union smb_read parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].readx.level = RAW_READ_READX; parm[0].readx.in.fnum = gen_fnum(instance); parm[0].readx.in.offset = gen_offset(); parm[0].readx.in.mincnt = gen_io_count(); parm[0].readx.in.maxcnt = gen_io_count(); parm[0].readx.in.remaining = gen_io_count(); parm[0].readx.out.data = talloc_size(current_op.mem_ctx, MAX(parm[0].readx.in.mincnt, parm[0].readx.in.maxcnt)); GEN_COPY_PARM; GEN_SET_FNUM(readx.in.fnum); GEN_CALL(smb_raw_read(tree, &parm[i])); CHECK_EQUAL(readx.out.remaining); CHECK_EQUAL(readx.out.compaction_mode); CHECK_EQUAL(readx.out.nread); return True; } /* generate writex operations */ static BOOL handler_writex(int instance) { union smb_write parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].writex.level = RAW_WRITE_WRITEX; parm[0].writex.in.fnum = gen_fnum(instance); parm[0].writex.in.offset = gen_offset(); parm[0].writex.in.wmode = gen_bits_mask(0xFFFF); parm[0].writex.in.remaining = gen_io_count(); parm[0].writex.in.count = gen_io_count(); parm[0].writex.in.data = talloc_zero_size(current_op.mem_ctx, parm[0].writex.in.count); GEN_COPY_PARM; GEN_SET_FNUM(writex.in.fnum); GEN_CALL(smb_raw_write(tree, &parm[i])); CHECK_EQUAL(writex.out.nwritten); CHECK_EQUAL(writex.out.remaining); return True; } /* generate lockingx operations */ static BOOL handler_lockingx(int instance) { union smb_lock parm[NSERVERS]; NTSTATUS status[NSERVERS]; int n, nlocks; parm[0].lockx.level = RAW_LOCK_LOCKX; parm[0].lockx.in.fnum = gen_fnum(instance); parm[0].lockx.in.mode = gen_lock_mode(); parm[0].lockx.in.timeout = gen_timeout(); do { /* make sure we don't accidentially generate an oplock break ack - otherwise the server can just block forever */ parm[0].lockx.in.ulock_cnt = gen_lock_count(); parm[0].lockx.in.lock_cnt = gen_lock_count(); nlocks = parm[0].lockx.in.ulock_cnt + parm[0].lockx.in.lock_cnt; } while (nlocks == 0); if (nlocks > 0) { parm[0].lockx.in.locks = talloc_array(current_op.mem_ctx, struct smb_lock_entry, nlocks); for (n=0;n<nlocks;n++) { parm[0].lockx.in.locks[n].pid = gen_pid(); parm[0].lockx.in.locks[n].offset = gen_offset(); parm[0].lockx.in.locks[n].count = gen_io_count(); } } GEN_COPY_PARM; GEN_SET_FNUM(lockx.in.fnum); GEN_CALL(smb_raw_lock(tree, &parm[i])); return True; } /* generate a fileinfo query structure */ static void gen_fileinfo(int instance, union smb_fileinfo *info) { int i; #define LVL(v) {RAW_FILEINFO_ ## v, "RAW_FILEINFO_" #v} struct { enum smb_fileinfo_level level; const char *name; } levels[] = { LVL(GETATTR), LVL(GETATTRE), LVL(STANDARD), LVL(EA_SIZE), LVL(ALL_EAS), LVL(IS_NAME_VALID), LVL(BASIC_INFO), LVL(STANDARD_INFO), LVL(EA_INFO), LVL(NAME_INFO), LVL(ALL_INFO), LVL(ALT_NAME_INFO), LVL(STREAM_INFO), LVL(COMPRESSION_INFO), LVL(BASIC_INFORMATION), LVL(STANDARD_INFORMATION), LVL(INTERNAL_INFORMATION), LVL(EA_INFORMATION), LVL(ACCESS_INFORMATION), LVL(NAME_INFORMATION), LVL(POSITION_INFORMATION), LVL(MODE_INFORMATION), LVL(ALIGNMENT_INFORMATION), LVL(ALL_INFORMATION), LVL(ALT_NAME_INFORMATION), LVL(STREAM_INFORMATION), LVL(COMPRESSION_INFORMATION), LVL(NETWORK_OPEN_INFORMATION), LVL(ATTRIBUTE_TAG_INFORMATION) }; do { i = gen_int_range(0, ARRAY_SIZE(levels)-1); } while (ignore_pattern(levels[i].name)); info->generic.level = levels[i].level; } /* compare returned fileinfo structures */ static BOOL cmp_fileinfo(int instance, union smb_fileinfo parm[NSERVERS], NTSTATUS status[NSERVERS]) { int i; switch (parm[0].generic.level) { case RAW_FILEINFO_GENERIC: return False; case RAW_FILEINFO_GETATTR: CHECK_EQUAL(getattr.out.attrib); CHECK_EQUAL(getattr.out.size); CHECK_TIMES_EQUAL(getattr.out.write_time); break; case RAW_FILEINFO_GETATTRE: CHECK_TIMES_EQUAL(getattre.out.create_time); CHECK_TIMES_EQUAL(getattre.out.access_time); CHECK_TIMES_EQUAL(getattre.out.write_time); CHECK_EQUAL(getattre.out.size); CHECK_EQUAL(getattre.out.alloc_size); CHECK_EQUAL(getattre.out.attrib); break; case RAW_FILEINFO_STANDARD: CHECK_TIMES_EQUAL(standard.out.create_time); CHECK_TIMES_EQUAL(standard.out.access_time); CHECK_TIMES_EQUAL(standard.out.write_time); CHECK_EQUAL(standard.out.size); CHECK_EQUAL(standard.out.alloc_size); CHECK_EQUAL(standard.out.attrib); break; case RAW_FILEINFO_EA_SIZE: CHECK_TIMES_EQUAL(ea_size.out.create_time); CHECK_TIMES_EQUAL(ea_size.out.access_time); CHECK_TIMES_EQUAL(ea_size.out.write_time); CHECK_EQUAL(ea_size.out.size); CHECK_EQUAL(ea_size.out.alloc_size); CHECK_EQUAL(ea_size.out.attrib); CHECK_EQUAL(ea_size.out.ea_size); break; case RAW_FILEINFO_ALL_EAS: CHECK_EQUAL(all_eas.out.num_eas); for (i=0;i<parm[0].all_eas.out.num_eas;i++) { CHECK_EQUAL(all_eas.out.eas[i].flags); CHECK_WSTR_EQUAL(all_eas.out.eas[i].name); CHECK_BLOB_EQUAL(all_eas.out.eas[i].value); } break; case RAW_FILEINFO_IS_NAME_VALID: break; case RAW_FILEINFO_BASIC_INFO: case RAW_FILEINFO_BASIC_INFORMATION: CHECK_NTTIMES_EQUAL(basic_info.out.create_time); CHECK_NTTIMES_EQUAL(basic_info.out.access_time); CHECK_NTTIMES_EQUAL(basic_info.out.write_time); CHECK_NTTIMES_EQUAL(basic_info.out.change_time); CHECK_EQUAL(basic_info.out.attrib); break; case RAW_FILEINFO_STANDARD_INFO: case RAW_FILEINFO_STANDARD_INFORMATION: CHECK_EQUAL(standard_info.out.alloc_size); CHECK_EQUAL(standard_info.out.size); CHECK_EQUAL(standard_info.out.nlink); CHECK_EQUAL(standard_info.out.delete_pending); CHECK_EQUAL(standard_info.out.directory); break; case RAW_FILEINFO_EA_INFO: case RAW_FILEINFO_EA_INFORMATION: CHECK_EQUAL(ea_info.out.ea_size); break; case RAW_FILEINFO_NAME_INFO: case RAW_FILEINFO_NAME_INFORMATION: CHECK_WSTR_EQUAL(name_info.out.fname); break; case RAW_FILEINFO_ALL_INFO: case RAW_FILEINFO_ALL_INFORMATION: CHECK_NTTIMES_EQUAL(all_info.out.create_time); CHECK_NTTIMES_EQUAL(all_info.out.access_time); CHECK_NTTIMES_EQUAL(all_info.out.write_time); CHECK_NTTIMES_EQUAL(all_info.out.change_time); CHECK_EQUAL(all_info.out.attrib); CHECK_EQUAL(all_info.out.alloc_size); CHECK_EQUAL(all_info.out.size); CHECK_EQUAL(all_info.out.nlink); CHECK_EQUAL(all_info.out.delete_pending); CHECK_EQUAL(all_info.out.directory); CHECK_EQUAL(all_info.out.ea_size); CHECK_WSTR_EQUAL(all_info.out.fname); break; case RAW_FILEINFO_ALT_NAME_INFO: case RAW_FILEINFO_ALT_NAME_INFORMATION: CHECK_WSTR_EQUAL(alt_name_info.out.fname); break; case RAW_FILEINFO_STREAM_INFO: case RAW_FILEINFO_STREAM_INFORMATION: CHECK_EQUAL(stream_info.out.num_streams); for (i=0;i<parm[0].stream_info.out.num_streams;i++) { CHECK_EQUAL(stream_info.out.streams[i].size); CHECK_EQUAL(stream_info.out.streams[i].alloc_size); CHECK_WSTR_EQUAL(stream_info.out.streams[i].stream_name); } break; case RAW_FILEINFO_COMPRESSION_INFO: case RAW_FILEINFO_COMPRESSION_INFORMATION: CHECK_EQUAL(compression_info.out.compressed_size); CHECK_EQUAL(compression_info.out.format); CHECK_EQUAL(compression_info.out.unit_shift); CHECK_EQUAL(compression_info.out.chunk_shift); CHECK_EQUAL(compression_info.out.cluster_shift); break; case RAW_FILEINFO_INTERNAL_INFORMATION: CHECK_EQUAL(internal_information.out.file_id); break; case RAW_FILEINFO_ACCESS_INFORMATION: CHECK_EQUAL(access_information.out.access_flags); break; case RAW_FILEINFO_POSITION_INFORMATION: CHECK_EQUAL(position_information.out.position); break; case RAW_FILEINFO_MODE_INFORMATION: CHECK_EQUAL(mode_information.out.mode); break; case RAW_FILEINFO_ALIGNMENT_INFORMATION: CHECK_EQUAL(alignment_information.out.alignment_requirement); break; case RAW_FILEINFO_NETWORK_OPEN_INFORMATION: CHECK_NTTIMES_EQUAL(network_open_information.out.create_time); CHECK_NTTIMES_EQUAL(network_open_information.out.access_time); CHECK_NTTIMES_EQUAL(network_open_information.out.write_time); CHECK_NTTIMES_EQUAL(network_open_information.out.change_time); CHECK_EQUAL(network_open_information.out.alloc_size); CHECK_EQUAL(network_open_information.out.size); CHECK_EQUAL(network_open_information.out.attrib); break; case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION: CHECK_EQUAL(attribute_tag_information.out.attrib); CHECK_EQUAL(attribute_tag_information.out.reparse_tag); break; } return True; } /* generate qpathinfo operations */ static BOOL handler_qpathinfo(int instance) { union smb_fileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.fname = gen_fname_open(instance); gen_fileinfo(instance, &parm[0]); GEN_COPY_PARM; GEN_CALL(smb_raw_pathinfo(tree, current_op.mem_ctx, &parm[i])); return cmp_fileinfo(instance, parm, status); } /* generate qfileinfo operations */ static BOOL handler_qfileinfo(int instance) { union smb_fileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.in.fnum = gen_fnum(instance); gen_fileinfo(instance, &parm[0]); GEN_COPY_PARM; GEN_SET_FNUM(generic.in.fnum); GEN_CALL(smb_raw_fileinfo(tree, current_op.mem_ctx, &parm[i])); return cmp_fileinfo(instance, parm, status); } /* generate a fileinfo query structure */ static void gen_setfileinfo(int instance, union smb_setfileinfo *info) { int i; #undef LVL #define LVL(v) {RAW_SFILEINFO_ ## v, "RAW_SFILEINFO_" #v} struct { enum smb_setfileinfo_level level; const char *name; } levels[] = { #if 0 /* disabled until win2003 can handle them ... */ LVL(EA_SET), LVL(BASIC_INFO), LVL(DISPOSITION_INFO), LVL(STANDARD), LVL(ALLOCATION_INFO), LVL(END_OF_FILE_INFO), #endif LVL(SETATTR), LVL(SETATTRE), LVL(BASIC_INFORMATION), LVL(RENAME_INFORMATION), LVL(DISPOSITION_INFORMATION), LVL(POSITION_INFORMATION), LVL(MODE_INFORMATION), LVL(ALLOCATION_INFORMATION), LVL(END_OF_FILE_INFORMATION), LVL(1023), LVL(1025), LVL(1029), LVL(1032), LVL(1039), LVL(1040) }; do { i = gen_int_range(0, ARRAY_SIZE(levels)-1); } while (ignore_pattern(levels[i].name)); info->generic.level = levels[i].level; switch (info->generic.level) { case RAW_SFILEINFO_SETATTR: info->setattr.in.attrib = gen_attrib(); info->setattr.in.write_time = gen_timet(); break; case RAW_SFILEINFO_SETATTRE: info->setattre.in.create_time = gen_timet(); info->setattre.in.access_time = gen_timet(); info->setattre.in.write_time = gen_timet(); break; case RAW_SFILEINFO_STANDARD: info->standard.in.create_time = gen_timet(); info->standard.in.access_time = gen_timet(); info->standard.in.write_time = gen_timet(); break; case RAW_SFILEINFO_EA_SET: { static struct ea_struct ea; info->ea_set.in.num_eas = 1; info->ea_set.in.eas = &ea; info->ea_set.in.eas[0] = gen_ea_struct(); } break; case RAW_SFILEINFO_BASIC_INFO: case RAW_SFILEINFO_BASIC_INFORMATION: info->basic_info.in.create_time = gen_nttime(); info->basic_info.in.access_time = gen_nttime(); info->basic_info.in.write_time = gen_nttime(); info->basic_info.in.change_time = gen_nttime(); info->basic_info.in.attrib = gen_attrib(); break; case RAW_SFILEINFO_DISPOSITION_INFO: case RAW_SFILEINFO_DISPOSITION_INFORMATION: info->disposition_info.in.delete_on_close = gen_bool(); break; case RAW_SFILEINFO_ALLOCATION_INFO: case RAW_SFILEINFO_ALLOCATION_INFORMATION: info->allocation_info.in.alloc_size = gen_alloc_size(); break; case RAW_SFILEINFO_END_OF_FILE_INFO: case RAW_SFILEINFO_END_OF_FILE_INFORMATION: info->end_of_file_info.in.size = gen_offset(); break; case RAW_SFILEINFO_RENAME_INFORMATION: info->rename_information.in.overwrite = gen_bool(); info->rename_information.in.root_fid = gen_root_fid(instance); info->rename_information.in.new_name = gen_fname_open(instance); break; case RAW_SFILEINFO_POSITION_INFORMATION: info->position_information.in.position = gen_offset(); break; case RAW_SFILEINFO_MODE_INFORMATION: info->mode_information.in.mode = gen_bits_mask(0xFFFFFFFF); break; } } /* generate setpathinfo operations */ static BOOL handler_spathinfo(int instance) { union smb_setfileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.file.fname = gen_fname_open(instance); gen_setfileinfo(instance, &parm[0]); GEN_COPY_PARM; /* a special case for the fid in a RENAME */ if (parm[0].generic.level == RAW_SFILEINFO_RENAME_INFORMATION && parm[0].rename_information.in.root_fid != 0) { GEN_SET_FNUM(rename_information.in.root_fid); } GEN_CALL(smb_raw_setpathinfo(tree, &parm[i])); return True; } /* generate setfileinfo operations */ static BOOL handler_sfileinfo(int instance) { union smb_setfileinfo parm[NSERVERS]; NTSTATUS status[NSERVERS]; parm[0].generic.file.fnum = gen_fnum(instance); gen_setfileinfo(instance, &parm[0]); GEN_COPY_PARM; GEN_SET_FNUM(generic.file.fnum); GEN_CALL(smb_raw_setfileinfo(tree, &parm[i])); return True; } /* generate change notify operations */ static BOOL handler_notify(int instance) { struct smb_notify parm[NSERVERS]; int n; parm[0].in.buffer_size = gen_io_count(); parm[0].in.completion_filter = gen_bits_mask(0xFF); parm[0].in.fnum = gen_fnum(instance); parm[0].in.recursive = gen_bool(); GEN_COPY_PARM; GEN_SET_FNUM(in.fnum); for (n=0;n<NSERVERS;n++) { struct smbcli_request *req; req = smb_raw_changenotify_send(servers[n].cli[instance]->tree, &parm[n]); req->async.fn = async_notify; } return True; } /* wipe any relevant files */ static void wipe_files(void) { int i; for (i=0;i<NSERVERS;i++) { int n = smbcli_deltree(servers[i].cli[0]->tree, "\\gentest"); if (n == -1) { printf("Failed to wipe tree on server %d\n", i); exit(1); } if (NT_STATUS_IS_ERR(smbcli_mkdir(servers[i].cli[0]->tree, "\\gentest"))) { printf("Failed to create \\gentest - %s\n", smbcli_errstr(servers[i].cli[0]->tree)); exit(1); } if (n > 0) { printf("Deleted %d files on server %d\n", n, i); } } } /* dump the current seeds - useful for continuing a backtrack */ static void dump_seeds(void) { int i; FILE *f; if (!options.seeds_file) { return; } f = fopen("seeds.tmp", "w"); if (!f) return; for (i=0;i<options.numops;i++) { fprintf(f, "%u\n", op_parms[i].seed); } fclose(f); rename("seeds.tmp", options.seeds_file); } /* the list of top-level operations that we will generate */ static struct { const char *name; BOOL (*handler)(int instance); int count, success_count; } gen_ops[] = { {"OPEN", handler_open}, {"OPENX", handler_openx}, {"NTCREATEX", handler_ntcreatex}, {"CLOSE", handler_close}, {"UNLINK", handler_unlink}, {"MKDIR", handler_mkdir}, {"RMDIR", handler_rmdir}, {"RENAME", handler_rename}, {"NTRENAME", handler_ntrename}, {"READX", handler_readx}, {"WRITEX", handler_writex}, {"CHKPATH", handler_chkpath}, {"LOCKINGX", handler_lockingx}, {"QPATHINFO", handler_qpathinfo}, {"QFILEINFO", handler_qfileinfo}, {"SPATHINFO", handler_spathinfo}, {"SFILEINFO", handler_sfileinfo}, {"NOTIFY", handler_notify}, {"SEEK", handler_seek}, }; /* run the test with the current set of op_parms parameters return the number of operations that completed successfully */ static int run_test(void) { int op, i; if (!connect_servers()) { printf("Failed to connect to servers\n"); exit(1); } dump_seeds(); /* wipe any leftover files from old runs */ wipe_files(); /* reset the open handles array */ memset(open_handles, 0, options.max_open_handles * sizeof(open_handles[0])); num_open_handles = 0; for (i=0;i<ARRAY_SIZE(gen_ops);i++) { gen_ops[i].count = 0; gen_ops[i].success_count = 0; } for (op=0; op<options.numops; op++) { int instance, which_op; BOOL ret; if (op_parms[op].disabled) continue; srandom(op_parms[op].seed); instance = gen_int_range(0, NINSTANCES-1); /* generate a non-ignored operation */ do { which_op = gen_int_range(0, ARRAY_SIZE(gen_ops)-1); } while (ignore_pattern(gen_ops[which_op].name)); DEBUG(3,("Generating op %s on instance %d\n", gen_ops[which_op].name, instance)); current_op.seed = op_parms[op].seed; current_op.opnum = op; current_op.name = gen_ops[which_op].name; current_op.status = NT_STATUS_OK; current_op.mem_ctx = talloc_init("%s", current_op.name); ret = gen_ops[which_op].handler(instance); talloc_free(current_op.mem_ctx); gen_ops[which_op].count++; if (NT_STATUS_IS_OK(current_op.status)) { gen_ops[which_op].success_count++; } if (!ret) { printf("Failed at operation %d - %s\n", op, gen_ops[which_op].name); return op; } if (op % 100 == 0) { printf("%d\n", op); } } for (i=0;i<ARRAY_SIZE(gen_ops);i++) { printf("Op %-10s got %d/%d success\n", gen_ops[i].name, gen_ops[i].success_count, gen_ops[i].count); } return op; } /* perform a backtracking analysis of the minimal set of operations to generate an error */ static void backtrack_analyze(void) { int chunk, ret; chunk = options.numops / 2; do { int base; for (base=0; chunk > 0 && base+chunk < options.numops && options.numops > 1; ) { int i, max; chunk = MIN(chunk, options.numops / 2); /* mark this range as disabled */ max = MIN(options.numops, base+chunk); for (i=base;i<max; i++) { op_parms[i].disabled = True; } printf("Testing %d ops with %d-%d disabled\n", options.numops, base, max-1); ret = run_test(); printf("Completed %d of %d ops\n", ret, options.numops); for (i=base;i<max; i++) { op_parms[i].disabled = False; } if (ret == options.numops) { /* this chunk is needed */ base += chunk; } else if (ret < base) { printf("damn - inconsistent errors! found early error\n"); options.numops = ret+1; base = 0; } else { /* it failed - this chunk isn't needed for a failure */ memmove(&op_parms[base], &op_parms[max], sizeof(op_parms[0]) * (options.numops - max)); options.numops = (ret+1) - (max - base); } } if (chunk == 2) { chunk = 1; } else { chunk *= 0.4; } if (options.analyze_continuous && chunk == 0 && options.numops != 1) { chunk = 1; } } while (chunk > 0); printf("Reduced to %d ops\n", options.numops); ret = run_test(); if (ret != options.numops - 1) { printf("Inconsistent result? ret=%d numops=%d\n", ret, options.numops); } } /* start the main gentest process */ static BOOL start_gentest(void) { int op; int ret; /* allocate the open_handles array */ open_handles = calloc(options.max_open_handles, sizeof(open_handles[0])); srandom(options.seed); op_parms = calloc(options.numops, sizeof(op_parms[0])); /* generate the seeds - after this everything is deterministic */ if (options.use_preset_seeds) { int numops; char **preset = file_lines_load(options.seeds_file, &numops); if (!preset) { printf("Failed to load %s - %s\n", options.seeds_file, strerror(errno)); exit(1); } if (numops < options.numops) { options.numops = numops; } for (op=0;op<options.numops;op++) { if (!preset[op]) { printf("Not enough seeds in %s\n", options.seeds_file); exit(1); } op_parms[op].seed = atoi(preset[op]); } printf("Loaded %d seeds from %s\n", options.numops, options.seeds_file); } else { for (op=0; op<options.numops; op++) { op_parms[op].seed = random(); } } ret = run_test(); if (ret != options.numops && options.analyze) { options.numops = ret+1; backtrack_analyze(); } else if (options.analyze_always) { backtrack_analyze(); } else if (options.analyze_continuous) { while (run_test() == options.numops) ; } return ret == options.numops; } static void usage(void) { printf( "Usage:\n\ gentest2 //server1/share1 //server2/share2 [options..]\n\ options:\n\ -U user%%pass (can be specified twice)\n\ -s seed\n\ -o numops\n\ -a (show all ops)\n\ -A backtrack to find minimal ops\n\ -i FILE add a list of wildcard exclusions\n\ -O enable oplocks\n\ -S FILE set preset seeds file\n\ -L use preset seeds\n\ -F fast reconnect (just close files)\n\ -C continuous analysis mode\n\ -X analyse even when test OK\n\ "); } /**************************************************************************** main program ****************************************************************************/ int main(int argc, char *argv[]) { int opt; int i, username_count=0; BOOL ret; setlinebuf(stdout); setup_logging("gentest", DEBUG_STDOUT); if (argc < 3 || argv[1][0] == '-') { usage(); exit(1); } setup_logging(argv[0], DEBUG_STDOUT); for (i=0;i<NSERVERS;i++) { const char *share = argv[1+i]; servers[i].credentials = cli_credentials_init(NULL); if (!split_unc_name(share, &servers[i].server_name, &servers[i].share_name)) { printf("Invalid share name '%s'\n", share); return -1; } } argc -= NSERVERS; argv += NSERVERS; lp_load(dyn_CONFIGFILE); load_interfaces(); servers[0].credentials = cli_credentials_init(talloc_autofree_context()); servers[1].credentials = cli_credentials_init(talloc_autofree_context()); cli_credentials_guess(servers[0].credentials); cli_credentials_guess(servers[1].credentials); options.seed = time(NULL); options.numops = 1000; options.max_open_handles = 20; options.seeds_file = "gentest_seeds.dat"; while ((opt = getopt(argc, argv, "U:s:o:ad:i:AOhS:LFXC")) != EOF) { switch (opt) { case 'U': if (username_count == 2) { usage(); exit(1); } cli_credentials_parse_string(servers[username_count].credentials, optarg, CRED_SPECIFIED); username_count++; break; case 'd': DEBUGLEVEL = atoi(optarg); setup_logging(NULL, DEBUG_STDOUT); break; case 's': options.seed = atoi(optarg); break; case 'S': options.seeds_file = optarg; break; case 'L': options.use_preset_seeds = True; break; case 'F': options.fast_reconnect = True; break; case 'o': options.numops = atoi(optarg); break; case 'O': options.use_oplocks = True; break; case 'a': options.showall = True; break; case 'A': options.analyze = True; break; case 'X': options.analyze_always = True; break; case 'C': options.analyze_continuous = True; break; case 'i': options.ignore_patterns = file_lines_load(optarg, NULL); break; case 'h': usage(); exit(1); default: printf("Unknown option %c (%d)\n", (char)opt, opt); exit(1); } } gentest_init_subsystems; if (username_count == 0) { usage(); return -1; } if (username_count == 1) { servers[1].credentials = servers[0].credentials; } printf("seed=%u\n", options.seed); ret = start_gentest(); if (ret) { printf("gentest completed - no errors\n"); } else { printf("gentest failed\n"); } return ret?0:-1; }