conntable.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /** BEGIN COPYRIGHT BLOCK
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  35. * Copyright (C) 2005 Red Hat, Inc.
  36. * All rights reserved.
  37. * END COPYRIGHT BLOCK **/
  38. #ifdef HAVE_CONFIG_H
  39. # include <config.h>
  40. #endif
  41. /* Connection Table */
  42. #include "fe.h"
  43. Connection_Table *
  44. connection_table_new(int table_size)
  45. {
  46. Connection_Table *ct;
  47. int i = 0;
  48. ber_len_t maxbersize = config_get_maxbersize();
  49. ct= (Connection_Table*)slapi_ch_calloc( 1, sizeof(Connection_Table) );
  50. ct->size= table_size;
  51. ct->c = (Connection *)slapi_ch_calloc( 1, table_size * sizeof(Connection) );
  52. ct->fd = (struct POLL_STRUCT *)slapi_ch_calloc(1, table_size * sizeof(struct POLL_STRUCT));
  53. ct->table_mutex = PR_NewLock();
  54. /* We rely on the fact that we called calloc, which zeros the block, so we don't
  55. * init any structure element unless a zero value is troublesome later
  56. */
  57. for ( i = 0; i < table_size; i++ )
  58. {
  59. LBER_SOCKET invalid_socket;
  60. /* DBDB---move this out of here once everything works */
  61. ct->c[i].c_sb = ber_sockbuf_alloc();
  62. invalid_socket = SLAPD_INVALID_SOCKET;
  63. ct->c[i].c_sd = SLAPD_INVALID_SOCKET;
  64. #if defined(USE_OPENLDAP)
  65. ber_sockbuf_ctrl( ct->c[i].c_sb, LBER_SB_OPT_SET_FD, &invalid_socket );
  66. ber_sockbuf_ctrl( ct->c[i].c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &maxbersize );
  67. #else
  68. ber_sockbuf_set_option( ct->c[i].c_sb, LBER_SOCKBUF_OPT_DESC, &invalid_socket );
  69. /* openldap by default does not use readahead - the implementation is
  70. via a sockbuf_io layer */
  71. ber_sockbuf_set_option( ct->c[i].c_sb, LBER_SOCKBUF_OPT_NO_READ_AHEAD, LBER_OPT_ON );
  72. ber_sockbuf_set_option( ct->c[i].c_sb, LBER_SOCKBUF_OPT_MAX_INCOMING_SIZE, &maxbersize );
  73. #endif /* !USE_OPENLDAP */
  74. #ifndef _WIN32
  75. /* all connections start out invalid */
  76. ct->fd[i].fd = SLAPD_INVALID_SOCKET;
  77. #endif
  78. /* The connection table has a double linked list running through it.
  79. * This is used to find out which connections should be looked at
  80. * in the poll loop. Slot 0 in the table is always the head of
  81. * the linked list. Each slot has a c_next and c_prev which are
  82. * pointers back into the array of connection slots. */
  83. ct->c[i].c_next = NULL;
  84. ct->c[i].c_prev = NULL;
  85. ct->c[i].c_ci= i;
  86. ct->c[i].c_fdi= SLAPD_INVALID_SOCKET_INDEX;
  87. }
  88. return ct;
  89. }
  90. void connection_table_free(Connection_Table *ct)
  91. {
  92. int i;
  93. for (i = 0; i < ct->size; i++)
  94. {
  95. /* Free the contents of the connection structure */
  96. Connection *c= &(ct->c[i]);
  97. connection_done(c);
  98. }
  99. slapi_ch_free((void**)&ct->c);
  100. slapi_ch_free((void**)&ct->fd);
  101. PR_DestroyLock(ct->table_mutex);
  102. slapi_ch_free((void**)&ct);
  103. }
  104. #ifdef _WIN32
  105. /*
  106. * This function looks up connection by nspr fd. It is
  107. * slow because it iterrates through the entire connection
  108. * array. Currently, it is only used on NT in secure_read_function
  109. * to handle I/O timeout on SSL socket we should almost never
  110. * happen.
  111. */
  112. Connection*
  113. connection_table_get_connection_from_fd(Connection_Table *ct,PRFileDesc *prfd)
  114. {
  115. int i;
  116. for (i = 0; i < ct->size; i++)
  117. {
  118. if (ct->c[i].c_prfd == prfd)
  119. {
  120. return (&(ct->c[i]));
  121. }
  122. }
  123. return NULL;
  124. }
  125. #endif
  126. void
  127. connection_table_abandon_all_operations(Connection_Table *ct)
  128. {
  129. int i;
  130. for ( i = 0; i < ct->size; i++ )
  131. {
  132. if ( ct->c[i].c_mutex != NULL )
  133. {
  134. PR_Lock( ct->c[i].c_mutex );
  135. connection_abandon_operations( &ct->c[i] );
  136. PR_Unlock( ct->c[i].c_mutex );
  137. }
  138. }
  139. }
  140. /* Given a file descriptor for a socket, this function will return
  141. * a slot in the connection table to use.
  142. *
  143. * Note: this function is only called from the slapd_daemon (listener)
  144. * thread, which means it will never be called by two threads at
  145. * the same time.
  146. *
  147. * Returns a Connection on success
  148. * Returns NULL on failure
  149. */
  150. Connection *
  151. connection_table_get_connection(Connection_Table *ct, int sd)
  152. {
  153. Connection *c= NULL;
  154. int index, count;
  155. index = sd % ct->size;
  156. for( count = 0; count < ct->size; count++, index = (index + 1) % ct->size)
  157. {
  158. /* Do not use slot 0, slot 0 is head of the list of active connections */
  159. if ( index == 0 )
  160. {
  161. continue;
  162. }
  163. else if( ct->c[index].c_mutex == NULL )
  164. {
  165. break;
  166. }
  167. if( connection_is_free (&(ct->c[index])))
  168. {
  169. break;
  170. }
  171. }
  172. if ( count < ct->size )
  173. {
  174. /* Found an available Connection */
  175. c= &(ct->c[index]);
  176. PR_ASSERT(c->c_next==NULL);
  177. PR_ASSERT(c->c_prev==NULL);
  178. PR_ASSERT(c->c_extension==NULL);
  179. if ( c->c_mutex == NULL )
  180. {
  181. PR_Lock( ct->table_mutex );
  182. c->c_mutex = PR_NewLock();
  183. c->c_pdumutex = PR_NewLock();
  184. PR_Unlock( ct->table_mutex );
  185. if ( c->c_mutex == NULL || c->c_pdumutex == NULL )
  186. {
  187. c->c_mutex = NULL;
  188. c->c_pdumutex = NULL;
  189. LDAPDebug( LDAP_DEBUG_ANY,"PR_NewLock failed\n",0, 0, 0 );
  190. c= NULL;
  191. }
  192. }
  193. /* Let's make sure there's no cruft left on there from the last time this connection was used. */
  194. /* Note: no need to lock c->c_mutex because this function is only
  195. * called by one thread (the slapd_daemon thread), and if we got this
  196. * far then `c' is not being used by any operation threads, etc.
  197. */
  198. connection_cleanup(c);
  199. #ifdef ENABLE_NUNC_STANS
  200. /* NOTE - ok to do this here even if enable_nunc_stans is off */
  201. c->c_ct = ct; /* pointer to connection table that owns this connection */
  202. #endif
  203. }
  204. else
  205. {
  206. /* couldn't find a Connection */
  207. LDAPDebug( LDAP_DEBUG_CONNS, "max open connections reached\n", 0, 0, 0);
  208. }
  209. return c;
  210. }
  211. /* active connection iteration functions */
  212. Connection*
  213. connection_table_get_first_active_connection (Connection_Table *ct)
  214. {
  215. return ct->c[0].c_next;
  216. }
  217. Connection*
  218. connection_table_get_next_active_connection (Connection_Table *ct, Connection *c)
  219. {
  220. return c->c_next;
  221. }
  222. int connection_table_iterate_active_connections(Connection_Table *ct, void* arg, Connection_Table_Iterate_Function f)
  223. {
  224. /* Locking in this area seems rather undeveloped, I think because typically only one
  225. * thread accesses the connection table (daemon thread). But now we allow other threads
  226. * to iterate over the table. So we use the "new mutex mutex" to lock the table.
  227. */
  228. Connection *current_conn = NULL;
  229. int ret = 0;
  230. PR_Lock(ct->table_mutex);
  231. current_conn = connection_table_get_first_active_connection (ct);
  232. while (current_conn) {
  233. ret = f(current_conn, arg);
  234. if (ret) {
  235. break;
  236. }
  237. current_conn = connection_table_get_next_active_connection (ct, current_conn);
  238. }
  239. PR_Unlock(ct->table_mutex);
  240. return ret;
  241. }
  242. #ifdef FOR_DEBUGGING
  243. static void
  244. connection_table_dump_active_connection (Connection *c)
  245. {
  246. slapi_log_error(SLAPI_LOG_FATAL, "connection", "conn=%p fd=%d refcnt=%d c_flags=%d\n"
  247. "mutex=%p next=%p prev=%p\n\n", c, c->c_sd, c->c_refcnt, c->c_flags,
  248. c->c_mutex, c->c_next, c->c_prev);
  249. }
  250. static void
  251. connection_table_dump_active_connections (Connection_Table *ct)
  252. {
  253. Connection* c;
  254. PR_Lock(ct->table_mutex);
  255. slapi_log_error(SLAPI_LOG_FATAL, "connection", "********** BEGIN DUMP ************\n");
  256. c = connection_table_get_first_active_connection (ct);
  257. while (c)
  258. {
  259. connection_table_dump_active_connection (c);
  260. c = connection_table_get_next_active_connection (ct, c);
  261. }
  262. slapi_log_error(SLAPI_LOG_FATAL, "connection", "********** END DUMP ************\n");
  263. PR_Unlock(ct->table_mutex);
  264. }
  265. #endif
  266. /*
  267. * There's a double linked list of active connections running through the array
  268. * of connections. This function removes a connection (by index) from that
  269. * list. This list is used to find the connections that should be used in the
  270. * poll call.
  271. */
  272. int
  273. connection_table_move_connection_out_of_active_list(Connection_Table *ct,Connection *c)
  274. {
  275. int c_sd; /* for logging */
  276. /* we always have previous element because list contains a dummy header */;
  277. PR_ASSERT (c->c_prev);
  278. #ifdef FOR_DEBUGGING
  279. slapi_log_error(SLAPI_LOG_FATAL, "connection", "Moving connection out of active list\n");
  280. connection_table_dump_active_connection (c);
  281. #endif
  282. c_sd = c->c_sd;
  283. /*
  284. * Note: the connection will NOT be moved off the active list if any other threads still hold
  285. * a reference to the connection (that is, its reference count must be 1 or less).
  286. */
  287. if(c->c_refcnt > 1) {
  288. LDAPDebug2Args(LDAP_DEBUG_CONNS,
  289. "not moving conn %d out of active list because refcnt is %d\n",
  290. c_sd, c->c_refcnt);
  291. return 1; /* failed */
  292. }
  293. /* We need to lock here because we're modifying the linked list */
  294. PR_Lock(ct->table_mutex);
  295. c->c_prev->c_next = c->c_next;
  296. if (c->c_next)
  297. {
  298. c->c_next->c_prev = c->c_prev;
  299. }
  300. connection_release_nolock (c);
  301. connection_cleanup (c);
  302. PR_Unlock(ct->table_mutex);
  303. LDAPDebug1Arg(LDAP_DEBUG_CONNS, "moved conn %d out of active list and freed\n", c_sd);
  304. #ifdef FOR_DEBUGGING
  305. connection_table_dump_active_connections (ct);
  306. #endif
  307. return 0; /* success */
  308. }
  309. /*
  310. * There's a double linked list of active connections running through the array
  311. * of connections. This function adds a connection (by index) to the head of
  312. * that list. This list is used to find the connections that should be used in the
  313. * poll call.
  314. */
  315. void
  316. connection_table_move_connection_on_to_active_list(Connection_Table *ct,Connection *c)
  317. {
  318. PR_ASSERT(c->c_next==NULL);
  319. PR_ASSERT(c->c_prev==NULL);
  320. PR_Lock(ct->table_mutex);
  321. PR_ASSERT(0 == connection_acquire_nolock (c));
  322. #ifdef FOR_DEBUGGING
  323. slapi_log_error(SLAPI_LOG_FATAL, "connection", "Moving connection into active list\n");
  324. connection_table_dump_active_connection (c);
  325. #endif
  326. c->c_next = ct->c[0].c_next;
  327. if ( c->c_next != NULL )
  328. {
  329. c->c_next->c_prev = c;
  330. }
  331. c->c_prev = &(ct->c[0]);
  332. ct->c[0].c_next = c;
  333. PR_Unlock(ct->table_mutex);
  334. #ifdef FOR_DEBUGGING
  335. connection_table_dump_active_connections (ct);
  336. #endif
  337. }
  338. /*
  339. * Replace the following attributes within the entry 'e' with
  340. * information about the connection table:
  341. * connection // multivalued; one value for each active connection
  342. * currentconnections // single valued; an integer count
  343. * totalconnections // single valued; an integer count
  344. * dtablesize // single valued; an integer size
  345. * readwaiters // single valued; an integer count
  346. */
  347. void
  348. connection_table_as_entry(Connection_Table *ct, Slapi_Entry *e)
  349. {
  350. char buf[BUFSIZ];
  351. char maxthreadbuf[BUFSIZ];
  352. struct berval val;
  353. struct berval *vals[2];
  354. int i, nconns, nreadwaiters;
  355. struct tm utm;
  356. vals[0] = &val;
  357. vals[1] = NULL;
  358. attrlist_delete( &e->e_attrs, "connection");
  359. nconns = 0;
  360. nreadwaiters = 0;
  361. for ( i = 0; i < (ct!=NULL?ct->size:0); i++ )
  362. {
  363. PR_Lock( ct->table_mutex );
  364. if ( (ct->c[i].c_mutex == NULL) || (ct->c[i].c_mutex == (PRLock*)-1) )
  365. {
  366. PR_Unlock( ct->table_mutex );
  367. continue;
  368. }
  369. /* Can't take c_mutex if holding table_mutex; temporarily unlock */
  370. PR_Unlock( ct->table_mutex );
  371. PR_Lock( ct->c[i].c_mutex );
  372. if ( ct->c[i].c_sd != SLAPD_INVALID_SOCKET )
  373. {
  374. char buf2[20];
  375. int lendn = ct->c[i].c_dn ? strlen(ct->c[i].c_dn) : 6; /* "NULLDN" */
  376. char *bufptr = &buf[0];
  377. char *newbuf = NULL;
  378. int maxthreadstate = 0;
  379. if(ct->c[i].c_flags & CONN_FLAG_MAX_THREADS){
  380. maxthreadstate = 1;
  381. }
  382. nconns++;
  383. if ( ct->c[i].c_gettingber )
  384. {
  385. nreadwaiters++;
  386. }
  387. #ifdef _WIN32
  388. {
  389. struct tm *pt;
  390. pt = gmtime( &ct->c[i].c_starttime );
  391. memcpy(&utm, pt, sizeof(struct tm) );
  392. }
  393. #else
  394. gmtime_r( &ct->c[i].c_starttime, &utm );
  395. #endif
  396. strftime( buf2, sizeof(buf2), "%Y%m%d%H%M%SZ", &utm );
  397. /*
  398. * Max threads per connection stats
  399. *
  400. * Appended output "1:2:3"
  401. *
  402. * 1 = Connection max threads state: 1 is in max threads, 0 is not
  403. * 2 = The number of times this thread has hit max threads
  404. * 3 = The number of operations attempted that were blocked
  405. * by max threads.
  406. */
  407. PR_snprintf(maxthreadbuf, sizeof(maxthreadbuf), "%d:%"NSPRIu64":%"NSPRIu64"",
  408. maxthreadstate, ct->c[i].c_maxthreadscount,
  409. ct->c[i].c_maxthreadsblocked);
  410. if ((lendn + strlen(maxthreadbuf)) > (BUFSIZ - 46)) {
  411. /*
  412. * 46 = 4 for the "i" couter + 20 for buf2 +
  413. * 10 for c_opsinitiated + 10 for c_opscompleted +
  414. * 1 for c_gettingber + 1
  415. */
  416. newbuf = (char *) slapi_ch_malloc(lendn + strlen(maxthreadbuf) + 46);
  417. bufptr = newbuf;
  418. }
  419. sprintf( bufptr, "%d:%s:%d:%d:%s%s:%s:%s", i,
  420. buf2,
  421. ct->c[i].c_opsinitiated,
  422. ct->c[i].c_opscompleted,
  423. ct->c[i].c_gettingber ? "r" : "-",
  424. "",
  425. ct->c[i].c_dn ? ct->c[i].c_dn : "NULLDN",
  426. maxthreadbuf
  427. );
  428. val.bv_val = bufptr;
  429. val.bv_len = strlen( bufptr );
  430. attrlist_merge( &e->e_attrs, "connection", vals );
  431. slapi_ch_free_string(&newbuf);
  432. }
  433. PR_Unlock( ct->c[i].c_mutex );
  434. }
  435. PR_snprintf( buf, sizeof(buf), "%d", nconns );
  436. val.bv_val = buf;
  437. val.bv_len = strlen( buf );
  438. attrlist_replace( &e->e_attrs, "currentconnections", vals );
  439. PR_snprintf( buf, sizeof(buf), "%" NSPRIu64, slapi_counter_get_value(num_conns));
  440. val.bv_val = buf;
  441. val.bv_len = strlen( buf );
  442. attrlist_replace( &e->e_attrs, "totalconnections", vals );
  443. PR_snprintf( buf, sizeof(buf), "%" NSPRIu64, slapi_counter_get_value(conns_in_maxthreads));
  444. val.bv_val = buf;
  445. val.bv_len = strlen( buf );
  446. attrlist_replace( &e->e_attrs, "currentconnectionsatmaxthreads", vals );
  447. PR_snprintf( buf, sizeof(buf), "%" NSPRIu64, slapi_counter_get_value(max_threads_count));
  448. val.bv_val = buf;
  449. val.bv_len = strlen( buf );
  450. attrlist_replace( &e->e_attrs, "maxthreadsperconnhits", vals );
  451. PR_snprintf( buf, sizeof(buf), "%d", (ct!=NULL?ct->size:0) );
  452. val.bv_val = buf;
  453. val.bv_len = strlen( buf );
  454. attrlist_replace( &e->e_attrs, "dtablesize", vals );
  455. PR_snprintf( buf, sizeof(buf), "%d", nreadwaiters );
  456. val.bv_val = buf;
  457. val.bv_len = strlen( buf );
  458. attrlist_replace( &e->e_attrs, "readwaiters", vals );
  459. }
  460. void
  461. connection_table_dump_activity_to_errors_log(Connection_Table *ct)
  462. {
  463. int i;
  464. for ( i = 0; i < ct->size; i++ )
  465. {
  466. Connection *c= &(ct->c[i]);
  467. if ( c->c_mutex != NULL )
  468. {
  469. /* Find the connection we are referring to */
  470. int j= c->c_fdi;
  471. PR_Lock( c->c_mutex );
  472. if ( (c->c_sd != SLAPD_INVALID_SOCKET) &&
  473. (j >= 0) && (c->c_prfd == ct->fd[j].fd) )
  474. {
  475. int r = ct->fd[j].out_flags & SLAPD_POLL_FLAGS;
  476. if ( r )
  477. {
  478. LDAPDebug( LDAP_DEBUG_CONNS,"activity on %d%s\n", i, r ? "r" : "",0 );
  479. }
  480. }
  481. PR_Unlock( c->c_mutex );
  482. }
  483. }
  484. }
  485. #if 0
  486. void dump_op_list(FILE *file, Operation *op);
  487. void
  488. connection_table_dump(Connection_Table *ct)
  489. {
  490. FILE *file;
  491. file = fopen("/tmp/slapd.conn", "a+");
  492. if (file != NULL)
  493. {
  494. int i;
  495. fprintf(file, "=============pid=%d==================\n", getpid());
  496. for ( i = 0; i < ct->size; i++ )
  497. {
  498. if ( (ct->c[i].c_sd == SLAPD_INVALID_SOCKET) && (ct->c[i].c_connid == 0) )
  499. {
  500. continue;
  501. }
  502. fprintf(file, "c[%d].c_dn=0x%x\n", i, ct->c[i].c_dn);
  503. dump_op_list(file, ct->c[i].c_ops);
  504. fprintf(file, "c[%d].c_sb.sb_sd=%d\n", i, ct->c[i].c_sd);
  505. fprintf(file, "c[%d].c_connid=%d\n", i, ct->c[i].c_connid);
  506. fprintf(file, "c[%d].c_opsinitiated=%d\n", i, ct->c[i].c_opsinitiated);
  507. fprintf(file, "c[%d].c_opscompleted=%d\n", i, ct->c[i].c_opscompleted);
  508. }
  509. fclose(file);
  510. }
  511. }
  512. static const char *
  513. op_status2str( int o_status )
  514. {
  515. const char *s = "unknown";
  516. switch( o_status ) {
  517. case SLAPI_OP_STATUS_PROCESSING:
  518. s = "processing";
  519. break;
  520. case SLAPI_OP_STATUS_ABANDONED:
  521. s = "abandoned";
  522. break;
  523. case SLAPI_OP_STATUS_WILL_COMPLETE:
  524. s = "will_complete";
  525. break;
  526. case SLAPI_OP_STATUS_RESULT_SENT:
  527. s = "result_sent";
  528. break;
  529. }
  530. return s;
  531. }
  532. void
  533. dump_op_list(FILE *file, Operation *op)
  534. {
  535. Operation *tmp;
  536. for ( tmp = op; tmp != NULL; tmp = tmp->o_next )
  537. {
  538. fprintf(file,
  539. "(o_msgid=%d, o_tag=%d, o_sdn=0x%x, o_opid=%d, o_connid=%d, o_status=%s)\n",
  540. tmp->o_msgid, tmp->o_tag, slapi_sdn_get_dn(&tmp->o_sdn), tmp->o_connid,
  541. *tmp->o_tid, op_status2str( tmp->o_status ));
  542. }
  543. }
  544. #endif /* 0 */