Exemplo n.º 1
0
/*
 *	Stop all threads in the pool.
 */
void thread_pool_stop(void)
{
#ifndef WITH_GCD
	int i;
	int total_threads;
	THREAD_HANDLE *handle;
	THREAD_HANDLE *next;

	/*
	 *	Set pool stop flag.
	 */
	thread_pool.stop_flag = 1;

	/*
	 *	Wakeup all threads to make them see stop flag.
	 */
	total_threads = thread_pool.total_threads;
	for (i = 0; i != total_threads; i++) {
		sem_post(&thread_pool.semaphore);
	}

	/*
	 *	Join and free all threads.
	 */
	for (handle = thread_pool.head; handle; handle = next) {
		next = handle->next;
		pthread_join(handle->pthread_id, NULL);
		delete_thread(handle);
	}
#endif
}
Exemplo n.º 2
0
int main()
{
#ifdef USE_DYNAMO
    dynamorio_app_init();
    dynamorio_app_start();
#endif

    sleeptime.tv_sec = 0;
    sleeptime.tv_nsec = 10*1000*1000; /* 10ms */

    child_exit = false;
    child_done = false;
    child = create_thread(run, NULL, &stack);
    assert(child > -1);

    /* waste some time */
    nanosleep(&sleeptime, NULL);
    
    child_exit = true;
    /* we want deterministic printf ordering */
    while (!child_done)
        nanosleep(&sleeptime, NULL);
    delete_thread(child, stack);

#ifdef USE_DYNAMO
    dynamorio_app_stop();
    dynamorio_app_exit();
#endif
}
Exemplo n.º 3
0
int
main()
{
    int i;
    sleeptime.tv_sec = 0;
    sleeptime.tv_nsec = 10 * 1000 * 1000; /* 10ms */

    /* parent remains in own group. creates child who creates a thread group
     * and then exits them all
     */
    for (i = 0; i < NUM_THREADS; i++) {
        child_started[i] = false;
        child_exit[i] = false;
    }
    child[0] = create_thread(run, (void *)(long)0, &stack[0], false);
    assert(child[0] > -1);

    /* wait for child to start rest of threads */
    for (i = 0; i < NUM_THREADS; i++) {
        while (!child_started[i]) {
            /* waste some time: FIXME: should use futex */
            nanosleep(&sleeptime, NULL);
        }
    }

    child_exit[0] = true;
    while (!child_done[0])
        nanosleep(&sleeptime, NULL);
    delete_thread(child[0], stack[0]);
    for (i = 1; i < NUM_THREADS; i++)
        stack_free(stack[i], THREAD_STACK_SIZE);
}
Exemplo n.º 4
0
void thread2(void)
{
  int i = -100;
  for(;i<0;) {
    printf("%s: count = %d\n",__func__,i++);
    yield();
  }
  delete_thread();
  assert(0);
}
void thread1(void)
{
  int i = 1;
  for(;i<100;) {
    printf("%s: count = %d\n",__func__,i++);
    yield(); 
  }
  delete_thread();
  printf("%s\n","delete_thread2");
  assert(0);
}
Exemplo n.º 6
0
void
test_thread(bool share_sighand)
{
    /* First make a thread that shares signal handlers. */
    child_exit = false;
    child_done = false;
    child = create_thread(run, NULL, &stack, share_sighand);
    assert(child > -1);

    /* waste some time */
    nanosleep(&sleeptime, NULL);

    child_exit = true;
    /* we want deterministic printf ordering */
    while (!child_done)
        nanosleep(&sleeptime, NULL);
    delete_thread(child, stack);
}
Exemplo n.º 7
0
static ptid_t
fbsd_thread_wait (struct target_ops *ops,
		  ptid_t ptid, struct target_waitstatus *ourstatus, int options)
{
  struct target_ops *beneath = find_target_beneath (ops);
  ptid_t ret;
  long lwp;
  CORE_ADDR stop_pc;
  td_thrhandle_t th;
  td_thrinfo_t ti;

  ret = beneath->to_wait (beneath, ptid, ourstatus, options);
  if (GET_PID(ret) >= 0 && ourstatus->kind == TARGET_WAITKIND_STOPPED)
    {
      lwp = get_current_lwp (GET_PID(ret));
      ret = thread_from_lwp (BUILD_LWP(lwp, GET_PID(ret)),
         &th, &ti);
      if (!in_thread_list(ret)) {
        /*
         * We have to enable event reporting for initial thread
         * which was not mapped before.
	 */
        attach_thread(ret, &th, &ti, 1);
      }
      if (ourstatus->value.sig == TARGET_SIGNAL_TRAP)
        check_event(ret);
      /* this is a hack, if an event won't cause gdb to stop, for example,
         SIGALRM, gdb resumes the process immediatly without setting
         inferior_ptid to the new thread returned here, this is a bug
         because inferior_ptid may already not exist there, and passing
         a non-existing thread to fbsd_thread_resume causes error. However,
         if the exiting thread is the currently selected thread,
         then that is handled later in handle_inferior_event(), and we must
         not delete the currently selected thread. 
      */
      if (!fbsd_thread_alive (ops, inferior_ptid) && !ptid_equal(inferior_ptid, ret))
        {
          delete_thread (inferior_ptid);
          inferior_ptid = ret;
        }
    }

  return (ret);
}
Exemplo n.º 8
0
static void
haiku_remove_thread(team_debug_info *teamDebugInfo, thread_id threadID)
{
	thread_debug_info **info;
	for (info = &teamDebugInfo->threads; *info; info = &(*info)->next) {
		if ((*info)->thread == threadID) {
			thread_debug_info *foundInfo = *info;
			*info = foundInfo->next;
			if (foundInfo->last_event)
				xfree(foundInfo->last_event);
			xfree(foundInfo);

			// remove it from gdb's thread DB
			delete_thread(ptid_build(teamDebugInfo->team, 0, threadID));

			return;
		}
	}
}
Exemplo n.º 9
0
void simple_thread(void)
{
  int i, myID, sleep;
  
  myID = count;

  count ++; /* for the next thread 
	     * It is shared, do I need to lock??? A lock is not necessary. Yielding is collaborative. So cant be pre-empted at the middle
	     */

  for(i=myID; i<MAX;i++) {
    /* print something, sleep for a while and yield */
    printf("I'm %s %d: Count=%d\n",__func__,myID,i);
    for(sleep=-COUNT(myID); sleep < COUNT(myID); sleep++);
    yield();
  }

  delete_thread(); /* die */
  assert(0); /* I should not be running */
}
Exemplo n.º 10
0
int free_iothread(iothread_t* iothr)
{
   int err;

   if (iothr->thread) {
      requeststop_iothread(iothr);
      (void) join_thread(iothr->thread);

      cancelall_iolist(&iothr->iolist);

      err = delete_thread(&iothr->thread);
      (void) PROCESS_testerrortimer(&s_iothread_errtimer, &err);
      if (err) goto ONERR;
   }

   return 0;
ONERR:
   TRACEEXITFREE_ERRLOG(err);
   return err;
}
Exemplo n.º 11
0
int main()
{
    sleeptime.tv_sec = 0;
    sleeptime.tv_nsec = 10*1000*1000; /* 10ms */

    child_exit = false;
    child_done = false;
    child = create_thread(run, NULL, &stack);
    assert(child > -1);

    /* waste some time */
    nanosleep(&sleeptime, NULL);

    child_exit = true;
    /* we want deterministic printf ordering */
    while (!child_done)
        nanosleep(&sleeptime, NULL);
    delete_thread(child, stack);

    return 0;
}
Exemplo n.º 12
0
void main_loop()
{
    char input[STRING_SIZE];
    thread_t threads[MAX_THREADS_COUNT];
    struct thread_args_t all_args[MAX_THREADS_COUNT];
    int last_thread_id = -1;

    while (1) {
        fgets(input, STRING_SIZE, stdin);
        switch (input[0]) {
        case '+':
            add_thread(threads, all_args, ++last_thread_id);
            break;
        case '-':
            delete_thread(threads, all_args, &last_thread_id);
            break;
        case 'q':
            kill_all_threads(threads, all_args, last_thread_id);
            return;
        }
    }
}
Exemplo n.º 13
0
static ptid_t
fbsd_thread_wait (ptid_t ptid, struct target_waitstatus *ourstatus)
{
  ptid_t ret;
  long lwp;
  CORE_ADDR stop_pc;
  td_thrhandle_t th;
  td_thrinfo_t ti;

  ret = child_ops.to_wait (ptid, ourstatus);
  if (GET_PID(ret) >= 0 && ourstatus->kind == TARGET_WAITKIND_STOPPED)
    {
      lwp = get_current_lwp (GET_PID(ret));
      ret = thread_from_lwp (BUILD_LWP(lwp, GET_PID(ret)),
         &th, &ti);
      if (!in_thread_list(ret)) {
        /*
         * We have to enable event reporting for initial thread
         * which was not mapped before.
	 */
        attach_thread(ret, &th, &ti, 1);
      }
      if (ourstatus->value.sig == TARGET_SIGNAL_TRAP)
        check_event(ret);
      /* this is a hack, if an event won't cause gdb to stop, for example,
         SIGARLM, gdb resumes the process immediatly without setting
         inferior_ptid to the new thread returned here, this is a bug
         because inferior_ptid may already not exist there, and passing
         a none existing thread to fbsd_thread_resume causes error. */
      if (!fbsd_thread_alive (inferior_ptid))
        {
          delete_thread (inferior_ptid);
          inferior_ptid = ret;
        }
    }

  return (ret);
}
Exemplo n.º 14
0
/*----------------------------------------------------------------------------*/
status_t
kill_thread(thread_id thread)
{
	sys_thread_info_t *curr_th = NULL;
	sys_thread_info_t **prev_th = NULL;
	int status;
	
	if(acquire_sem(_thread_list_sem) == B_OK) {
		curr_th = _thread_list;
		prev_th = &_thread_list;
		
		while(curr_th) {
			if(curr_th->info.thread == thread) {
				/* thread found, remove it... */
				*prev_th = curr_th->next;
				status = pthread_cancel((pthread_t)thread);
				DBG(OUT("Killed thread %d (%s)\n", thread, curr_th->info.name));
				delete_thread(curr_th);
				release_sem(_thread_list_sem);
				if(status != 0) {
					switch(errno) {
						case EINVAL:
						case ESRCH: return B_BAD_THREAD_ID;
					}
				}
				return B_OK;
			}
			
			prev_th = &(curr_th->next);
			curr_th = curr_th->next;
		}
		
		release_sem(_thread_list_sem);
	}
	
	return B_BAD_THREAD_ID;
}
Exemplo n.º 15
0
static int test_update(void)
{
   logbuffer_t logbuf = logbuffer_FREE;
   thread_t *  thread = 0;
   pipe_t      pipe   = pipe_FREE;
   uint8_t     buffer[1024];
   uint8_t     readbuffer[1024+1];

   // prepare
   TEST(0 == init_pipe(&pipe));
   logbuf = (logbuffer_t) logbuffer_INIT(sizeof(buffer), buffer, pipe.write);

   // TEST truncate_logbuffer
   for (unsigned i = 0; i < 32; ++i) {
      logbuf.logsize = 32;
      logbuf.addr[i] = 'a';
      truncate_logbuffer(&logbuf, i);
      TEST(logbuf.addr == buffer);
      TEST(logbuf.size == sizeof(buffer));
      TEST(logbuf.io   == pipe.write);
      TEST(logbuf.addr[i] == 0);
      TEST(logbuf.logsize == i);
   }

   // TEST truncate_logbuffer: parameter with bigger or equal size are ignored
   for (unsigned i = 0; i < 32; ++i) {
      logbuf.logsize = i;
      logbuf.addr[i]   = 'a';
      logbuf.addr[i+1] = 'a';
      truncate_logbuffer(&logbuf, i+1);
      truncate_logbuffer(&logbuf, i);
      TEST(logbuf.addr == buffer);
      TEST(logbuf.size == sizeof(buffer));
      TEST(logbuf.io   == pipe.write);
      TEST(logbuf.addr[i]   == 'a');
      TEST(logbuf.addr[i+1] == 'a');
      TEST(logbuf.logsize   == i);
   }

   // TEST write_logbuffer
   memset(readbuffer, 0, sizeof(readbuffer));
   for (unsigned i = 0; i < sizeof(buffer); ++i) {
      buffer[i] = (uint8_t)i;
   }
   logbuf.logsize = logbuf.size;
   // test
   TEST( 0 == write_logbuffer(&logbuf));
   // check logbuf
   TEST( logbuf.addr == buffer);
   TEST( logbuf.size == sizeof(buffer));
   TEST( logbuf.logsize == sizeof(buffer));
   TEST( logbuf.io   == pipe.write);
   // check content of pipe
   static_assert(sizeof(readbuffer) > sizeof(buffer), "check that only sizeof(buffer) are written");
   TEST(sizeof(buffer) == read(pipe.read, readbuffer, sizeof(readbuffer)));
   for (unsigned i = 0; i < sizeof(buffer); ++i) {
      TEST(buffer[i] == readbuffer[i]);
   }

   // TEST printheader_logbuffer
   logbuf.logsize = 0;
   log_header_t header = log_header_INIT("test_update", "file", 123456);
   printheader_logbuffer(&logbuf, &header);
   TEST(0 == compare_header(logbuf.logsize, logbuf.addr, "test_update", "file", 123456));
   for (size_t len = logbuf.logsize, i = 1; i < 10; ++i) {
      printheader_logbuffer(&logbuf, &header);
      TEST((i+1)*len == logbuf.logsize);
      TEST(0 == compare_header(len, logbuf.addr + i*len, "test_update", "file", 123456));
   }

   // TEST printheader_logbuffer: other thread
   TEST(0 == newgeneric_thread(&thread, &thread_printheader, &logbuf));
   TEST(0 == join_thread(thread));
   TEST(0 == returncode_thread(thread));
   TEST(0 == delete_thread(&thread));

   // TEST printheader_logbuffer: adds " ..." at end in case of truncated message
   logbuf.logsize = logbuf.size - 10;
   logbuf.addr[logbuf.logsize] = 0;
   printheader_logbuffer(&logbuf, &header);
   TEST(logbuf.logsize == logbuf.size - 1)
   TEST(0 == memcmp(logbuf.addr + logbuf.size - 10, "[", 1));
   TEST(0 == memcmp(logbuf.addr + logbuf.size - 5, " ...", 5));

   // TEST vprintf_logbuffer: append on already stored content
   for (unsigned i = 0; i < sizeof(buffer)-100; ++i) {
      memset(buffer, 0, sizeof(buffer));
      memset(readbuffer, 0, sizeof(readbuffer));
      logbuf.logsize = i;
      printf_logbuffer(&logbuf, "%d : %s : %c;;", i, "OK!", '0');
      snprintf((char*)readbuffer + i, 100, "%d : %s : %c;;", i, "OK!", '0');
      TEST(0 == memcmp(buffer, readbuffer, sizeof(buffer)));
   }

   // TEST vprintf_logbuffer: different formats
   logbuf.logsize = 0;
   printf_logbuffer(&logbuf, "%%%s%%", "str" );
   printf_logbuffer(&logbuf, "%"PRIi8";", (int8_t)-1);
   printf_logbuffer(&logbuf, "%"PRIu8";", (uint8_t)1);
   printf_logbuffer(&logbuf, "%"PRIi16";", (int16_t)-256);
   printf_logbuffer(&logbuf, "%"PRIu16";", (uint16_t)256);
   printf_logbuffer(&logbuf, "%"PRIi32";", (int32_t)-65536);
   printf_logbuffer(&logbuf, "%"PRIu32";", (uint32_t)65536);
   printf_logbuffer(&logbuf, "%zd;", (ssize_t)-65536);
   printf_logbuffer(&logbuf, "%zu;", (size_t)65536);
   printf_logbuffer(&logbuf, "%g;", 2e100);
   printf_logbuffer(&logbuf, "%.0f;", (double)1234567);
   const char * result = "%str%-1;1;-256;256;-65536;65536;-65536;65536;2e+100;1234567;";
   TEST(strlen(result) == logbuf.logsize);
   TEST(0 == memcmp(logbuf.addr, result, logbuf.logsize));

   // TEST vprintf_logwriter: adds " ..." at end in case of truncated message
   char strtoobig[100];
   memset(strtoobig, '1', sizeof(strtoobig));
   logbuf.logsize = logbuf.size - sizeof(strtoobig);
   logbuf.addr[logbuf.logsize] = 0;
   printf_logbuffer(&logbuf, "%.100s", strtoobig);
   TEST(logbuf.logsize == logbuf.size - 1)
   TEST(0 == memcmp(logbuf.addr + logbuf.size - sizeof(strtoobig), strtoobig, sizeof(strtoobig)-5));
   TEST(0 == memcmp(logbuf.addr + logbuf.size - 5, " ...", 5));

   // TEST vprintf_logbuffer: format == 0
   logbuf.logsize = 0;
   printf_logbuffer(&logbuf, 0);
   // nothing printed
   TEST(0 == logbuf.logsize);

   // TEST vprintf_logbuffer: sizefree_logbuffer() == 0
   logbuf.logsize = logbuf.size;
   memset(logbuf.addr, 255, logbuf.size);
   printf_logbuffer(&logbuf, "%d", 12345);
   // check logbuf not changed
   TEST(buffer == logbuf.addr);
   TEST(sizeof(buffer) == logbuf.size);
   TEST(sizeof(buffer) == logbuf.logsize);
   TEST(pipe.write     == logbuf.io);
   // check content of logbuf not changed except for " ..."
   for (size_t i = 0; i < logbuf.logsize - 5; ++i) {
      TEST(255 == logbuf.addr[i]);
   }
   TEST(0 == memcmp(logbuf.addr + logbuf.logsize - 5, " ...", 4));
   TEST(255 == logbuf.addr[logbuf.logsize-1]);

   // TEST vprintf_logbuffer: logbuffer_t.size <= 5
   for (size_t s = 5; s <= 5; --s) {
      TEST(sizeof(buffer) == logbuf.size);
      logbuf.size = s;
      logbuf.logsize = 0;
      memset(logbuf.addr, 255, s);
      printf_logbuffer(&logbuf, "%d", 12345);
      // check logbuf
      TEST(buffer == logbuf.addr);
      TEST(s      == logbuf.size);
      TEST((s?s-1:0)  == logbuf.logsize);
      TEST(pipe.write == logbuf.io);
      // check content of logbuf
      if (s == 5) {
         // " ..."
         TEST(0 == memcmp(logbuf.addr, " ...", 5));
      } else {
         // truncated 12345
         for (size_t i = 0; i < logbuf.logsize; ++i) {
            TEST(i+'1' == logbuf.addr[i]);
         }
         TEST(0 == (logbuf.size ? logbuf.addr[logbuf.size-1] : 0));
      }
      // reset
      logbuf.size = sizeof(buffer);
   }

   // unprepare
   TEST(-1 == read(pipe.read, readbuffer, sizeof(readbuffer)));
   TEST(0 == free_pipe(&pipe));

   return 0;
ONERR:
   delete_thread(&thread);
   free_pipe(&pipe);
   return EINVAL;
}
Exemplo n.º 16
0
/*
 *	Check the min_spare_threads and max_spare_threads.
 *
 *	If there are too many or too few threads waiting, then we
 *	either create some more, or delete some.
 */
static void thread_pool_manage(time_t now)
{
	int spare;
	int i, total;
	THREAD_HANDLE *handle, *next;
	int active_threads;

	/*
	 *	We don't need a mutex lock here, as we're reading
	 *	active_threads, and not modifying it.  We want a close
	 *	approximation of the number of active threads, and this
	 *	is good enough.
	 */
	active_threads = thread_pool.active_threads;
	spare = thread_pool.total_threads - active_threads;
	if (debug_flag) {
		static int old_total = -1;
		static int old_active = -1;

		if ((old_total != thread_pool.total_threads) ||
				(old_active != active_threads)) {
			DEBUG2("Threads: total/active/spare threads = %d/%d/%d",
					thread_pool.total_threads, active_threads, spare);
			old_total = thread_pool.total_threads;
			old_active = active_threads;
		}
	}

	/*
	 *	If there are too few spare threads.  Go create some more.
	 */
	if ((thread_pool.total_threads < thread_pool.max_threads) &&
	    (spare < thread_pool.min_spare_threads)) {
		total = thread_pool.min_spare_threads - spare;

		if ((total + thread_pool.total_threads) > thread_pool.max_threads) {
			total = thread_pool.max_threads - thread_pool.total_threads;
		}

		DEBUG2("Threads: Spawning %d spares", total);

		/*
		 *	Create a number of spare threads.
		 */
		for (i = 0; i < total; i++) {
			handle = spawn_thread(now, 1);
			if (handle == NULL) {
				return;
			}
		}

		return;		/* there aren't too many spare threads */
	}

	/*
	 *	Only delete spare threads if we haven't already done
	 *	so this second.
	 */
	if (now == last_cleaned) {
		return;
	}
	last_cleaned = now;

	/*
	 *	Loop over the thread pool, deleting exited threads.
	 */
	for (handle = thread_pool.head; handle; handle = next) {
		next = handle->next;

		/*
		 *	Maybe we've asked the thread to exit, and it
		 *	has agreed.
		 */
		if (handle->status == THREAD_EXITED) {
			pthread_join(handle->pthread_id, NULL);
			delete_thread(handle);
		}
	}

	/*
	 *	Only delete the spare threads if sufficient time has
	 *	passed since we last created one.  This helps to minimize
	 *	the amount of create/delete cycles.
	 */
	if ((now - thread_pool.time_last_spawned) < thread_pool.cleanup_delay) {
		return;
	}

	/*
	 *	If there are too many spare threads, delete one.
	 *
	 *	Note that we only delete ONE at a time, instead of
	 *	wiping out many.  This allows the excess servers to
	 *	be slowly reaped, just in case the load spike comes again.
	 */
	if (spare > thread_pool.max_spare_threads) {

		spare -= thread_pool.max_spare_threads;

		DEBUG2("Threads: deleting 1 spare out of %d spares", spare);

		/*
		 *	Walk through the thread pool, deleting the
		 *	first idle thread we come across.
		 */
		for (handle = thread_pool.head; (handle != NULL) && (spare > 0) ; handle = next) {
			next = handle->next;

			/*
			 *	If the thread is not handling a
			 *	request, but still live, then tell it
			 *	to exit.
			 *
			 *	It will eventually wake up, and realize
			 *	it's been told to commit suicide.
			 */
			if ((handle->request == NULL) &&
			    (handle->status == THREAD_RUNNING)) {
				handle->status = THREAD_CANCELLED;
				/*
				 *	Post an extra semaphore, as a
				 *	signal to wake up, and exit.
				 */
				sem_post(&thread_pool.semaphore);
				spare--;
				break;
			}
		}
	}

	/*
	 *	If the thread has handled too many requests, then make it
	 *	exit.
	 */
	if (thread_pool.max_requests_per_thread > 0) {
		for (handle = thread_pool.head; handle; handle = next) {
			next = handle->next;

			/*
			 *	Not handling a request, but otherwise
			 *	live, we can kill it.
			 */
			if ((handle->request == NULL) &&
			    (handle->status == THREAD_RUNNING) &&
			    (handle->request_count > thread_pool.max_requests_per_thread)) {
				handle->status = THREAD_CANCELLED;
				sem_post(&thread_pool.semaphore);
			}
		}
	}

	/*
	 *	Otherwise everything's kosher.  There are not too few,
	 *	or too many spare threads.  Exit happily.
	 */
	return;
}
Exemplo n.º 17
0
static int test_logininfo(void)
{
   syslogin_info_t* info = 0;
   thread_t* thr = 0;
   uid_t   uid;
   gid_t   gid;
   int     fd[2] = { -1, -1 };
   uint8_t buffer[16];

   // prepare
   TEST(0 == pipe2(fd, O_CLOEXEC|O_NONBLOCK));

   // == lifetime ==

   for (unsigned entrypos = 0; true; ++entrypos) {
      setpwent();
      for (unsigned i = 0; i < entrypos; ++i) {
         TEST(0 != getpwent());
      }
      struct passwd* pwd = getpwent();
      if (pwd == 0) {
         TEST(entrypos > 2); // tested at least 2 entries
         break;
      }
      uid = pwd->pw_uid;
      gid = pwd->pw_gid;

      // TEST new_syslogininfo: read entries
      TEST(0 == new_syslogininfo(&info, uid));
      TEST(0 != info);
      TEST(0 <  info->size);
      TEST(uid == info->uid);
      TEST(1 <= info->nrgroups);
      TEST(info->nrgroups > info->gmain);
      TEST(gid == info->gid[info->gmain]);
      // check memory addr
      TEST(info->gname == (const char**)(&info[1]));
      TEST(info->gid   == (sys_groupid_t*)((uint8_t*)info->gname + info->nrgroups * sizeof(char*)));
      TEST(info->uname == (const char*)((uint8_t*)info->gid + info->nrgroups * sizeof(sys_groupid_t)));
      TEST(info->gname[0] == info->uname + strlen(info->uname) + 1);
      TEST((uint8_t*)info + info->size  == (const uint8_t*)info->gname[info->nrgroups-1] + strlen(info->gname[info->nrgroups-1]) + 1);
      for (size_t i = 1; i < info->nrgroups; ++i) {
         TEST(info->gname[i-1] + strlen(info->gname[i-1]) + 1 == info->gname[i]);
      }
      // DEBUG printf("user %s(%d) groups ", info->uname, info->uid);
      // DEBUG for (size_t i = 0; i < info->nrgroups; ++i) {
      // DEBUG    if (i == info->gmain) printf("*");
      // DEBUG    printf("%s(%d),", info->gname[i], info->gid[i]);
      // DEBUG }
      // DEBUG printf("\n");

      // TEST delete_syslogininfo
      TEST(0 != info);
      TEST(0 == delete_syslogininfo(&info));
      TEST(0 == info);
      TEST(0 == delete_syslogininfo(&info));
      TEST(0 == info);
   }

   // TEST new_syslogininfo: lock
   TEST(0 == new_thread(&thr, &thread_initinfo, (void*)(intptr_t)fd[1]));
   TEST(0 == lock_mutex(&s_syslogininfo_lock));
   struct pollfd pfd = { .fd = fd[0], .events = POLLIN };
   TEST(1 == poll(&pfd, 1, 10000));
   TEST(1 == read(fd[0], buffer, sizeof(buffer)));
   TEST(EBUSY == tryjoin_thread(thr));
   TEST(-1 == read(fd[0], buffer, sizeof(buffer)));
   TEST(EAGAIN == errno);
   TEST(0 == unlock_mutex(&s_syslogininfo_lock));
   TEST(0 == join_thread(thr));
   TEST(0 == returncode_thread(thr));
   TEST(0 == delete_thread(&thr));

   // TEST new_syslogininfo: ENOENT
   info = (void*)1;
   TEST(ENOENT == new_syslogininfo(&info, (uid_t)-2));
   TEST((void*)1 == info);
   info = 0;

   // == query ==

   // TEST username_syslogininfo
   syslogin_info_t info2;
   info2.uname = 0;
   TEST(0 == username_syslogininfo(&info2));
   for (uintptr_t i = 1; i; i <<= 1) {
      info2.uname = (const char*)i;
      TEST((const char*)i == username_syslogininfo(&info2));
   }

   // unprepare
   TEST(0 == close(fd[0]));
   TEST(0 == close(fd[1]));

   return 0;
ONERR:
   if (info != (void*)1) {
      delete_syslogininfo(&info);
   }
   close(fd[0]);
   close(fd[1]);
   return EINVAL;
}