aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator')
-rw-r--r--erts/emulator/beam/erl_alloc.c7
-rw-r--r--erts/emulator/beam/erl_nif.c30
-rwxr-xr-x[-rw-r--r--]erts/emulator/drivers/win32/win_efile.c131
-rw-r--r--erts/emulator/sys/win32/sys.c116
-rw-r--r--erts/emulator/test/port_SUITE.erl24
-rw-r--r--erts/emulator/test/port_SUITE_data/dead_port.c6
6 files changed, 259 insertions, 55 deletions
diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 07b4167b27..7793f60f4f 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -1348,6 +1348,13 @@ handle_args(int *argc, char **argv, erts_alc_hndl_args_init_t *init)
argv[j++] = argv[i];
}
*argc = j;
+#if HALFWORD_HEAP
+ /* If halfword heap, silently ignore any disabling of internal
+ allocators */
+ for (i = 0; i < aui_sz; ++i)
+ aui[i]->enable = 1;
+#endif
+
}
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 1dd9c8bd4a..a680097c2d 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -99,6 +99,16 @@ static Eterm* alloc_heap_heavy(ErlNifEnv* env, unsigned need, Eterm* hp)
return hp;
}
+#if SIZEOF_LONG != ERTS_SIZEOF_ETERM
+static ERTS_INLINE void ensure_heap(ErlNifEnv* env, unsigned may_need)
+{
+ if (env->hp + may_need > env->hp_end) {
+ alloc_heap_heavy(env, may_need, env->hp);
+ env->hp -= may_need;
+ }
+}
+#endif
+
void erts_pre_nif(ErlNifEnv* env, Process* p, struct erl_module_nif* mod_nif)
{
env->mod_nif = mod_nif;
@@ -730,9 +740,8 @@ int enif_get_long(ErlNifEnv* env, Eterm term, long* ip)
{
#if SIZEOF_LONG == ERTS_SIZEOF_ETERM
return term_to_Sint(term, ip);
-#elif SIZEOF_INT == ERTS_SIZEOF_ETERM
- Sint i;
- return term_to_Sint(term, &i) ? (*ip = (long) i, 1) : 0;
+#elif SIZEOF_LONG == 8
+ return term_to_Sint64(term, ip);
#else
# error Unknown long word size
#endif
@@ -742,9 +751,8 @@ int enif_get_ulong(ErlNifEnv* env, Eterm term, unsigned long* ip)
{
#if SIZEOF_LONG == ERTS_SIZEOF_ETERM
return term_to_Uint(term, ip);
-#elif SIZEOF_INT == ERTS_SIZEOF_ETERM
- Uint u;
- return term_to_Uint(term, &u) ? (*ip = (unsigned long) u, 1) : 0;
+#elif SIZEOF_LONG == 8
+ return term_to_Uint64(term, ip);
#else
# error Unknown long word size
#endif
@@ -821,12 +829,22 @@ ERL_NIF_TERM enif_make_uint(ErlNifEnv* env, unsigned i)
ERL_NIF_TERM enif_make_long(ErlNifEnv* env, long i)
{
+#if SIZEOF_LONG == ERTS_SIZEOF_ETERM
return IS_SSMALL(i) ? make_small(i) : small_to_big(i, alloc_heap(env,2));
+#elif SIZEOF_LONG == 8
+ ensure_heap(env,3);
+ return erts_sint64_to_big(i, &env->hp);
+#endif
}
ERL_NIF_TERM enif_make_ulong(ErlNifEnv* env, unsigned long i)
{
+#if SIZEOF_LONG == ERTS_SIZEOF_ETERM
return IS_USMALL(0,i) ? make_small(i) : uint_to_big(i,alloc_heap(env,2));
+#elif SIZEOF_LONG == 8
+ ensure_heap(env,3);
+ return erts_uint64_to_big(i, &env->hp);
+#endif
}
#if HAVE_INT64 && SIZEOF_LONG != 8
diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c
index 04bd1139f5..6de08e2fa6 100644..100755
--- a/erts/emulator/drivers/win32/win_efile.c
+++ b/erts/emulator/drivers/win32/win_efile.c
@@ -862,6 +862,7 @@ efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo,
findbuf.nFileSizeLow = 0;
findbuf.cFileName[0] = '\0';
+ pInfo->links = 1;
pInfo->modifyTime.year = 1980;
pInfo->modifyTime.month = 1;
pInfo->modifyTime.day = 1;
@@ -874,6 +875,33 @@ efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo,
SYSTEMTIME SystemTime;
FILETIME LocalFTime;
+ /*first check if we are a symlink */
+ if (!info_for_link && (findbuf.dwFileAttributes &
+ FILE_ATTRIBUTE_REPARSE_POINT)){
+ /*
+ * given that we know this is a symlink,
+ we should be able to find its target */
+ char target_name[256];
+ if (efile_readlink(errInfo, name, target_name,256) == 1) {
+ return efile_fileinfo(errInfo, pInfo,
+ target_name, info_for_link);
+ }
+ }
+
+#if 0
+ /* number of links: */
+ {
+ HANDLE handle; /* Handle returned by CreateFile() */
+ BY_HANDLE_FILE_INFORMATION fileInfo; /* from CreateFile() */
+ if (handle = CreateFile(name, GENERIC_READ, 0,NULL,
+ OPEN_EXISTING, 0, NULL)) {
+ GetFileInformationByHandle(handle, &fileInfo);
+ pInfo->links = fileInfo.nNumberOfLinks;
+ CloseHandle(handle);
+ }
+ }
+#endif
+
#define GET_TIME(dst, src) \
if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \
!FileTimeToSystemTime(&LocalFTime, &SystemTime)) { \
@@ -908,7 +936,10 @@ if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \
pInfo->size_low = findbuf.nFileSizeLow;
pInfo->size_high = findbuf.nFileSizeHigh;
- if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ if (info_for_link && (findbuf.dwFileAttributes &
+ FILE_ATTRIBUTE_REPARSE_POINT))
+ pInfo->type = FT_SYMLINK;
+ else if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
pInfo->type = FT_DIRECTORY;
else
pInfo->type = FT_REGULAR;
@@ -919,7 +950,6 @@ if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \
pInfo->access = FA_READ|FA_WRITE;
pInfo->mode = dos_to_posix_mode(findbuf.dwFileAttributes, name);
- pInfo->links = 1;
pInfo->major_device = drive;
pInfo->minor_device = 0;
pInfo->inode = 0;
@@ -1082,12 +1112,17 @@ char* buf; /* Buffer to write. */
size_t count; /* Number of bytes to write. */
{
DWORD written; /* Bytes written in last operation. */
+ OVERLAPPED overlapped;
+ OVERLAPPED* pOverlapped = NULL;
if (flags & EFILE_MODE_APPEND) {
- (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END);
+ memset(&overlapped, 0, sizeof(overlapped));
+ overlapped.Offset = 0xffffffff;
+ overlapped.OffsetHigh = 0xffffffff;
+ pOverlapped = &overlapped;
}
while (count > 0) {
- if (!WriteFile((HANDLE) fd, buf, count, &written, NULL))
+ if (!WriteFile((HANDLE) fd, buf, count, &written, pOverlapped))
return set_error(errInfo);
buf += written;
count -= written;
@@ -1107,11 +1142,16 @@ efile_writev(Efile_error* errInfo, /* Where to return error codes */
size_t size) /* Number of bytes to write */
{
int cnt; /* Buffers so far written */
+ OVERLAPPED overlapped;
+ OVERLAPPED* pOverlapped = NULL;
ASSERT(iovcnt >= 0);
if (flags & EFILE_MODE_APPEND) {
- (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END);
+ memset(&overlapped, 0, sizeof(overlapped));
+ overlapped.Offset = 0xffffffff;
+ overlapped.OffsetHigh = 0xffffffff;
+ pOverlapped = &overlapped;
}
for (cnt = 0; cnt < iovcnt; cnt++) {
if (iov[cnt].iov_base && iov[cnt].iov_len > 0) {
@@ -1123,7 +1163,7 @@ efile_writev(Efile_error* errInfo, /* Where to return error codes */
iov[cnt].iov_base + p,
iov[cnt].iov_len - p,
&w,
- NULL))
+ pOverlapped))
return set_error(errInfo);
}
}
@@ -1343,6 +1383,48 @@ dos_to_posix_mode(int attr, const char *name)
int
efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size)
{
+ /*
+ * load dll and see if we have CreateSymbolicLink at runtime:
+ * (Vista only)
+ */
+ HINSTANCE hModule = NULL;
+ if ((hModule = LoadLibrary("kernel32.dll")) != NULL) {
+ typedef DWORD (WINAPI * GETFINALPATHNAMEBYHANDLEPTR)(
+ HANDLE hFile,
+ LPCSTR lpFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+
+ GETFINALPATHNAMEBYHANDLEPTR pGetFinalPathNameByHandle =
+ (GETFINALPATHNAMEBYHANDLEPTR)GetProcAddress(hModule, "GetFinalPathNameByHandleA");
+
+ if (pGetFinalPathNameByHandle == NULL) {
+ FreeLibrary(hModule);
+ } else {
+ /* first check if file is a symlink; {error, einval} otherwise */
+ DWORD fileAttributes = GetFileAttributes(name);
+ if ((fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ BOOLEAN success = 0;
+ HANDLE h = CreateFile(name, GENERIC_READ, 0,NULL, OPEN_EXISTING, 0, NULL);
+ if(h != INVALID_HANDLE_VALUE) {
+ success = pGetFinalPathNameByHandle(h, buffer, size,0);
+ /* GetFinalPathNameByHandle prepends path with "\\?\": */
+ sprintf(buffer, buffer+4);
+ CloseHandle(h);
+ }
+ FreeLibrary(hModule);
+ if (success) {
+ return 1;
+ } else {
+ return set_error(errInfo);
+ }
+ } else {
+ FreeLibrary(hModule);
+ errno = EINVAL;
+ return check_error(-1, errInfo);
+ }
+ }
+ }
errno = ENOTSUP;
return check_error(-1, errInfo);
}
@@ -1427,13 +1509,46 @@ efile_altname(Efile_error* errInfo, char* orig_name, char* buffer, size_t size)
int
efile_link(Efile_error* errInfo, char* old, char* new)
{
- errno = ENOTSUP;
- return check_error(-1, errInfo);
+ if(!CreateHardLink(new, old, NULL)) {
+ return set_error(errInfo);
+ }
+ return 1;
}
int
efile_symlink(Efile_error* errInfo, char* old, char* new)
{
+ /*
+ * Load dll and see if we have CreateSymbolicLink at runtime:
+ * (Vista only)
+ */
+ HINSTANCE hModule = NULL;
+ if ((hModule = LoadLibrary("kernel32.dll")) != NULL) {
+ typedef BOOLEAN (WINAPI * CREATESYMBOLICLINKFUNCPTR) (
+ LPCSTR lpSymlinkFileName,
+ LPCSTR lpTargetFileName,
+ DWORD dwFlags);
+
+ CREATESYMBOLICLINKFUNCPTR pCreateSymbolicLink =
+ (CREATESYMBOLICLINKFUNCPTR) GetProcAddress(hModule,
+ "CreateSymbolicLinkA");
+ /* A for MBCS, W for UNICODE... char* above implies 'A'! */
+ if (pCreateSymbolicLink != NULL) {
+ DWORD attr = GetFileAttributes(old);
+ int flag = (attr != INVALID_FILE_ATTRIBUTES &&
+ attr & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
+ /* SYMBOLIC_LINK_FLAG_DIRECTORY = 1 */
+ BOOLEAN success = pCreateSymbolicLink(new, old, flag);
+ FreeLibrary(hModule);
+
+ if (success) {
+ return 1;
+ } else {
+ return set_error(errInfo);
+ }
+ } else
+ FreeLibrary(hModule);
+ }
errno = ENOTSUP;
return check_error(-1, errInfo);
}
diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index d24347b3aa..39b04b26a9 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -97,7 +97,7 @@ static int driver_write(long, HANDLE, byte*, int);
static void common_stop(int);
static int create_file_thread(struct async_io* aio, int mode);
#ifdef ERTS_SMP
-static void close_active_handles(ErlDrvPort, const HANDLE* handles, int cnt);
+static void close_active_handle(ErlDrvPort, HANDLE handle);
static DWORD WINAPI threaded_handle_closer(LPVOID param);
#endif
static DWORD WINAPI threaded_reader(LPVOID param);
@@ -137,7 +137,11 @@ static BOOL win_console = FALSE;
static OSVERSIONINFO int_os_version; /* Version information for Win32. */
-#ifdef ERTS_SMP
+/*#define USE_CANCELIOEX
+ Disabled the use of CancelIoEx as its been seen to cause problem with some
+ drivers. Not sure what to blame; faulty drivers or some form of invalid use.
+*/
+#if defined(ERTS_SMP) && defined(USE_CANCELIOEX)
static BOOL (WINAPI *fpCancelIoEx)(HANDLE,LPOVERLAPPED);
#endif
@@ -684,6 +688,7 @@ release_driver_data(DriverData* dp)
erts_smp_mtx_lock(&sys_driver_data_lock);
#ifdef ERTS_SMP
+#ifdef USE_CANCELIOEX
if (fpCancelIoEx != NULL) {
if (dp->in.thread == (HANDLE) -1 && dp->in.fd != INVALID_HANDLE_VALUE) {
(*fpCancelIoEx)(dp->in.fd, NULL);
@@ -692,10 +697,12 @@ release_driver_data(DriverData* dp)
(*fpCancelIoEx)(dp->out.fd, NULL);
}
}
- else {
+ else
+#endif
+ {
/* This is a workaround for the fact that CancelIo cant cancel
requests issued by another thread and that we cant use
- CancelIoEx as that's only availabele in Vista etc.
+ CancelIoEx as that's only available in Vista etc.
R14: Avoid scheduler deadlock by only wait for 10ms, and then spawn
a thread that will keep waiting in in order to close handles. */
HANDLE handles[2];
@@ -706,7 +713,7 @@ release_driver_data(DriverData* dp)
dp->in.fd = INVALID_HANDLE_VALUE;
DEBUGF(("Waiting for the in event thingie"));
if (WaitForSingleObject(dp->in.ov.hEvent,timeout) == WAIT_TIMEOUT) {
- handles[i++] = dp->in.ov.hEvent;
+ close_active_handle(dp->port_num, dp->in.ov.hEvent);
dp->in.ov.hEvent = NULL;
timeout = 0;
}
@@ -717,14 +724,11 @@ release_driver_data(DriverData* dp)
dp->out.fd = INVALID_HANDLE_VALUE;
DEBUGF(("Waiting for the out event thingie"));
if (WaitForSingleObject(dp->out.ov.hEvent,timeout) == WAIT_TIMEOUT) {
- handles[i++] = dp->out.ov.hEvent;
+ close_active_handle(dp->port_num, dp->out.ov.hEvent);
dp->out.ov.hEvent = NULL;
}
DEBUGF(("...done\n"));
}
- if (i > 0) {
- close_active_handles(dp->port_num, handles, i);
- }
}
#else
if (dp->in.thread == (HANDLE) -1 && dp->in.fd != INVALID_HANDLE_VALUE) {
@@ -772,42 +776,82 @@ release_driver_data(DriverData* dp)
#ifdef ERTS_SMP
-struct handles_to_be_closed
-{
- int cnt;
- HANDLE handles[2];
+struct handles_to_be_closed {
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ unsigned cnt;
};
+static struct handles_to_be_closed* htbc_curr = NULL;
+CRITICAL_SECTION htbc_lock;
-static void close_active_handles(ErlDrvPort port_num, const HANDLE* handles, int cnt)
+static void close_active_handle(ErlDrvPort port_num, HANDLE handle)
{
- DWORD tid;
- HANDLE thread;
+ struct handles_to_be_closed* htbc;
int i;
- struct handles_to_be_closed* htbc = erts_alloc(ERTS_ALC_T_DRV_TAB,
- sizeof(struct handles_to_be_closed));
- htbc->cnt = cnt;
- for (i=0; i < cnt; ++i) {
- htbc->handles[i] = handles[i];
- (void) driver_select(port_num, (ErlDrvEvent)handles[i],
- ERL_DRV_USE_NO_CALLBACK, 0);
+ EnterCriticalSection(&htbc_lock);
+ htbc = htbc_curr;
+ if (htbc == NULL || htbc->cnt >= MAXIMUM_WAIT_OBJECTS) {
+ DWORD tid;
+ HANDLE thread;
+
+ htbc = (struct handles_to_be_closed*) erts_alloc(ERTS_ALC_T_DRV_TAB,
+ sizeof(*htbc));
+ htbc->handles[0] = CreateAutoEvent(FALSE);
+ htbc->cnt = 1;
+ thread = (HANDLE *) _beginthreadex(NULL, 0, threaded_handle_closer, htbc, 0, &tid);
+ CloseHandle(thread);
}
- thread = (HANDLE *) _beginthreadex(NULL, 0, threaded_handle_closer, htbc, 0, &tid);
- CloseHandle(thread);
+ htbc->handles[htbc->cnt++] = handle;
+ driver_select(port_num, (ErlDrvEvent)handle, ERL_DRV_USE_NO_CALLBACK, 0);
+ SetEvent(htbc->handles[0]);
+ htbc_curr = htbc;
+ LeaveCriticalSection(&htbc_lock);
}
-
static DWORD WINAPI
threaded_handle_closer(LPVOID param)
{
struct handles_to_be_closed* htbc = (struct handles_to_be_closed*) param;
- int i;
- DEBUGF(("threaded_handle_closer waiting for %d handles\r\n",htbc->cnt));
- WaitForMultipleObjects(htbc->cnt, htbc->handles, TRUE, INFINITE);
- for (i=0; i < htbc->cnt; ++i) {
- CloseHandle(htbc->handles[i]);
+ unsigned ix;
+ DWORD res;
+ DEBUGF(("threaded_handle_closer %p started\r\n", htbc));
+ EnterCriticalSection(&htbc_lock);
+ for (;;) {
+ {
+ HANDLE* handles = htbc->handles;
+ unsigned cnt = htbc->cnt;
+ DWORD timeout = (htbc == htbc_curr) ? INFINITE : 10*1000;
+
+ LeaveCriticalSection(&htbc_lock);
+ DEBUGF(("threaded_handle_closer %p waiting for %d handles\r\n", htbc, cnt));
+ res = WaitForMultipleObjects(cnt, handles, FALSE, timeout);
+ }
+ EnterCriticalSection(&htbc_lock);
+ switch (res) {
+ case WAIT_OBJECT_0:
+ case WAIT_TIMEOUT:
+ break; /* got some more handles to wait for maybe */
+ default:
+ ix = res - WAIT_OBJECT_0;
+ if (ix > 0 && ix < htbc->cnt) {
+ CloseHandle(htbc->handles[ix]);
+ htbc->handles[ix] = htbc->handles[--htbc->cnt];
+ }
+ }
+ if (htbc != htbc_curr) {
+ if (htbc->cnt == 1) { /* no real handles left */
+ break;
+ }
+ /* The thread with most free slots will be "current" */
+ if (htbc->cnt < htbc_curr->cnt) {
+ htbc_curr = htbc;
+ DEBUGF(("threaded_handle_closer %p made current\r\n", htbc));
+ }
+ }
}
+ LeaveCriticalSection(&htbc_lock);
+ CloseHandle(htbc->handles[0]);
erts_free(ERTS_ALC_T_DRV_TAB, htbc);
- DEBUGF(("threaded_handle_closer terminating\r\n"));
+ DEBUGF(("threaded_handle_closer %p terminating\r\n", htbc));
return 0;
}
#endif /* ERTS_SMP */
@@ -1101,11 +1145,10 @@ static int
spawn_init()
{
int i;
-#ifdef ERTS_SMP
+#if defined(ERTS_SMP) && defined(USE_CANCELIOEX)
HMODULE module = GetModuleHandle("kernel32");
- fpCancelIoEx = (module != NULL) ?
- (BOOL (WINAPI *)(HANDLE,LPOVERLAPPED))
- GetProcAddress(module,"CancelIoEx") : NULL;
+ fpCancelIoEx = (BOOL (WINAPI *)(HANDLE,LPOVERLAPPED))
+ ((module != NULL) ? GetProcAddress(module,"CancelIoEx") : NULL);
DEBUGF(("fpCancelIoEx = %p\r\n", fpCancelIoEx));
#endif
driver_data = (struct driver_data *)
@@ -3068,6 +3111,7 @@ void erl_sys_init(void)
#ifdef ERTS_SMP
erts_smp_tsd_key_create(&win32_errstr_key);
+ InitializeCriticalSection(&htbc_lock);
#endif
erts_smp_atomic_init(&pipe_creation_counter,0);
/*
diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl
index a7476ca9bb..6d89653623 100644
--- a/erts/emulator/test/port_SUITE.erl
+++ b/erts/emulator/test/port_SUITE.erl
@@ -2308,8 +2308,28 @@ close_deaf_port(Config) when is_list(Config) ->
?line Dog = test_server:timetrap(test_server:seconds(100)),
?line DataDir = ?config(data_dir, Config),
?line DeadPort = os:find_executable("dead_port", DataDir),
-
?line Port = open_port({spawn,DeadPort++" 60"},[]),
?line erlang:port_command(Port,"Hello, can you hear me!?!?"),
?line port_close(Port),
- ok.
+
+ close_deaf_port_1(0, DeadPort).
+
+close_deaf_port_1(1000, _) ->
+ ok;
+close_deaf_port_1(N, Cmd) ->
+ Timeout = integer_to_list(random:uniform(10*1000)),
+ try open_port({spawn,Cmd++" "++Timeout},[]) of
+ Port ->
+ ?line erlang:port_command(Port,"Hello, can you hear me!?!?"),
+ ?line port_close(Port),
+ close_deaf_port_1(N+1, Cmd)
+ catch
+ exit:eagain ->
+ {comment, "Could not spawn more than " ++ integer_to_list(N) ++ " OS processes."}
+ end.
+
+
+repeat(0, _) -> ok;
+repeat(Cnt, Fun) ->
+ Fun(),
+ repeat(Cnt-1, Fun).
diff --git a/erts/emulator/test/port_SUITE_data/dead_port.c b/erts/emulator/test/port_SUITE_data/dead_port.c
index 6fa77112be..68e96fbf14 100644
--- a/erts/emulator/test/port_SUITE_data/dead_port.c
+++ b/erts/emulator/test/port_SUITE_data/dead_port.c
@@ -72,14 +72,14 @@ char *argv[];
{
int x;
if (argc < 2) {
- fprintf(stderr,"Usage %s <seconds>\n",argv[0]);
+ fprintf(stderr,"Usage %s <milliseconds>\n",argv[0]);
return 1;
}
if ((x = atoi(argv[1])) <= 0) {
- fprintf(stderr,"Usage %s <seconds>\n",argv[0]);
+ fprintf(stderr,"Usage %s <milliseconds>\n",argv[0]);
return 1;
}
- delay(x*1000);
+ delay(x);
return 0;
}