pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
st_output.c
Go to the documentation of this file.
1/*
2 * Copyright 2019-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10#include <crm_internal.h>
11#include <stdarg.h>
12#include <stdint.h>
13
14#include <crm/stonith-ng.h>
15#include <crm/common/iso8601.h>
16#include <crm/common/util.h>
17#include <crm/common/xml.h>
18#include <crm/common/output.h>
23
37static char *
38timespec_string(time_t sec, long nsec, bool show_usec) {
39 const struct timespec ts = {
40 .tv_sec = sec,
41 .tv_nsec = nsec,
42 };
43
44 return pcmk__timespec2str(&ts,
48 |(show_usec? crm_time_usecs : 0));
49}
50
59static const char *
60history_action_text(const stonith_history_t *history)
61{
62 if (pcmk__str_eq(history->action, PCMK_ACTION_ON, pcmk__str_none)) {
63 return "unfencing";
64 }
65 if (pcmk__str_eq(history->action, PCMK_ACTION_OFF, pcmk__str_none)) {
66 return "turning off";
67 }
68 return pcmk__s(history->action, "fencing");
69}
70
81static const char *
82state_str(const stonith_history_t *history)
83{
84 switch (history->state) {
85 case st_failed: return "failed";
86 case st_done: return "successful";
87 default: return "pending";
88 }
89}
90
108gchar *
110 bool full_history, const char *later_succeeded,
111 uint32_t show_opts)
112{
113 GString *str = g_string_sized_new(256); // Generous starting size
114 char *completed_time_s = NULL;
115
116 if ((history->state == st_failed) || (history->state == st_done)) {
117 completed_time_s = timespec_string(history->completed,
118 history->completed_nsec, true);
119 }
120
121 pcmk__g_strcat(str, history_action_text(history), " of ", history->target,
122 NULL);
123
124 if (!pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
125 // More human-friendly
126 if (((history->state == st_failed) || (history->state == st_done))
127 && (history->delegate != NULL)) {
128
129 pcmk__g_strcat(str, " by ", history->delegate, NULL);
130 }
131 pcmk__g_strcat(str, " for ", history->client, "@", history->origin,
132 NULL);
133 if (!full_history) {
134 g_string_append(str, " last"); // For example, "last failed at ..."
135 }
136 }
137
138 pcmk__add_word(&str, 0, state_str(history));
139
140 // For failed actions, add exit reason if available
141 if ((history->state == st_failed) && (history->exit_reason != NULL)) {
142 pcmk__g_strcat(str, " (", history->exit_reason, ")", NULL);
143 }
144
145 if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
146 // More technical
147 g_string_append(str, ": ");
148
149 // For completed actions, add delegate if available
150 if (((history->state == st_failed) || (history->state == st_done))
151 && (history->delegate != NULL)) {
152
153 pcmk__g_strcat(str, PCMK_XA_DELEGATE "=", history->delegate, ", ",
154 NULL);
155 }
156
157 // Add information about originator
158 pcmk__g_strcat(str,
159 PCMK_XA_CLIENT "=", history->client, ", "
160 PCMK_XA_ORIGIN "=", history->origin, NULL);
161
162 // For completed actions, add completion time
163 if (completed_time_s != NULL) {
164 if (full_history) {
165 g_string_append(str, ", completed");
166 } else if (history->state == st_failed) {
167 g_string_append(str, ", last-failed");
168 } else {
169 g_string_append(str, ", last-successful");
170 }
171 pcmk__g_strcat(str, "='", completed_time_s, "'", NULL);
172 }
173 } else if (completed_time_s != NULL) {
174 // More human-friendly
175 pcmk__g_strcat(str, " at ", completed_time_s, NULL);
176 }
177
178 if ((history->state == st_failed) && (later_succeeded != NULL)) {
179 pcmk__g_strcat(str,
180 " (a later attempt from ", later_succeeded,
181 " succeeded)", NULL);
182 }
183
184 free(completed_time_s);
185 return g_string_free(str, FALSE);
186}
187
188PCMK__OUTPUT_ARGS("failed-fencing-list", "stonith_history_t *", "GList *",
189 "uint32_t", "uint32_t", "bool")
190static int
191failed_history(pcmk__output_t *out, va_list args)
192{
193 stonith_history_t *history = va_arg(args, stonith_history_t *);
194 GList *only_node = va_arg(args, GList *);
195 uint32_t section_opts = va_arg(args, uint32_t);
196 uint32_t show_opts = va_arg(args, uint32_t);
197 bool print_spacer = va_arg(args, int);
198
199 int rc = pcmk_rc_no_output;
200
201 for (stonith_history_t *hp = history; hp; hp = hp->next) {
202 if (hp->state != st_failed) {
203 continue;
204 }
205
206 if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
207 continue;
208 }
209
210 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Fencing Actions");
211 out->message(out, "stonith-event", hp,
212 pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
213 false, stonith__later_succeeded(hp, history), show_opts);
214 out->increment_list(out);
215 }
216
218 return rc;
219}
220
221PCMK__OUTPUT_ARGS("fencing-list", "stonith_history_t *", "GList *", "uint32_t",
222 "uint32_t", "bool")
223static int
224stonith_history(pcmk__output_t *out, va_list args)
225{
226 stonith_history_t *history = va_arg(args, stonith_history_t *);
227 GList *only_node = va_arg(args, GList *);
228 uint32_t section_opts = va_arg(args, uint32_t);
229 uint32_t show_opts = va_arg(args, uint32_t);
230 bool print_spacer = va_arg(args, int);
231
232 int rc = pcmk_rc_no_output;
233
234 for (stonith_history_t *hp = history; hp; hp = hp->next) {
235 if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
236 continue;
237 }
238
239 if (hp->state != st_failed) {
240 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History");
241 out->message(out, "stonith-event", hp,
242 pcmk_all_flags_set(section_opts,
244 false, stonith__later_succeeded(hp, history), show_opts);
245 out->increment_list(out);
246 }
247 }
248
250 return rc;
251}
252
253PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *",
254 "GList *", "uint32_t", "uint32_t", "bool")
255static int
256full_history(pcmk__output_t *out, va_list args)
257{
258 crm_exit_t history_rc G_GNUC_UNUSED = va_arg(args, crm_exit_t);
259 stonith_history_t *history = va_arg(args, stonith_history_t *);
260 GList *only_node = va_arg(args, GList *);
261 uint32_t section_opts = va_arg(args, uint32_t);
262 uint32_t show_opts = va_arg(args, uint32_t);
263 bool print_spacer = va_arg(args, int);
264
265 int rc = pcmk_rc_no_output;
266
267 for (stonith_history_t *hp = history; hp; hp = hp->next) {
268 if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
269 continue;
270 }
271
272 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History");
273 out->message(out, "stonith-event", hp,
274 pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
275 false, stonith__later_succeeded(hp, history), show_opts);
276 out->increment_list(out);
277 }
278
280 return rc;
281}
282
283PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *",
284 "GList *", "uint32_t", "uint32_t", "bool")
285static int
286full_history_xml(pcmk__output_t *out, va_list args)
287{
288 crm_exit_t history_rc = va_arg(args, crm_exit_t);
289 stonith_history_t *history = va_arg(args, stonith_history_t *);
290 GList *only_node = va_arg(args, GList *);
291 uint32_t section_opts = va_arg(args, uint32_t);
292 uint32_t show_opts = va_arg(args, uint32_t);
293 bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
294
295 int rc = pcmk_rc_no_output;
296
297 if (history_rc == 0) {
298 for (stonith_history_t *hp = history; hp; hp = hp->next) {
299 if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
300 continue;
301 }
302
303 PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Fencing History");
304 out->message(out, "stonith-event", hp,
305 pcmk_all_flags_set(section_opts,
307 false, stonith__later_succeeded(hp, history), show_opts);
308 out->increment_list(out);
309 }
310
312 } else {
313 char *rc_s = pcmk__itoa(history_rc);
314
316 PCMK_XA_STATUS, rc_s,
317 NULL);
318 free(rc_s);
319
320 rc = pcmk_rc_ok;
321 }
322
323 return rc;
324}
325
326PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
327static int
328last_fenced_html(pcmk__output_t *out, va_list args) {
329 const char *target = va_arg(args, const char *);
330 time_t when = va_arg(args, time_t);
331
332 if (when) {
333 char *buf = crm_strdup_printf("Node %s last fenced at: %s", target, ctime(&when));
334 pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, buf);
335 free(buf);
336 return pcmk_rc_ok;
337 } else {
338 return pcmk_rc_no_output;
339 }
340}
341
342PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
343static int
344last_fenced_text(pcmk__output_t *out, va_list args) {
345 const char *target = va_arg(args, const char *);
346 time_t when = va_arg(args, time_t);
347
348 if (when) {
349 pcmk__indented_printf(out, "Node %s last fenced at: %s", target, ctime(&when));
350 } else {
351 pcmk__indented_printf(out, "Node %s has never been fenced\n", target);
352 }
353
354 return pcmk_rc_ok;
355}
356
357PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t")
358static int
359last_fenced_xml(pcmk__output_t *out, va_list args) {
360 const char *target = va_arg(args, const char *);
361 time_t when = va_arg(args, time_t);
362
363 if (when) {
364 char *buf = timespec_string(when, 0, false);
365
368 PCMK_XA_WHEN, buf,
369 NULL);
370
371 free(buf);
372 return pcmk_rc_ok;
373 } else {
374 return pcmk_rc_no_output;
375 }
376}
377
378PCMK__OUTPUT_ARGS("pending-fencing-list", "stonith_history_t *", "GList *",
379 "uint32_t", "uint32_t", "bool")
380static int
381pending_actions(pcmk__output_t *out, va_list args)
382{
383 stonith_history_t *history = va_arg(args, stonith_history_t *);
384 GList *only_node = va_arg(args, GList *);
385 uint32_t section_opts = va_arg(args, uint32_t);
386 uint32_t show_opts = va_arg(args, uint32_t);
387 bool print_spacer = va_arg(args, int);
388
389 int rc = pcmk_rc_no_output;
390
391 for (stonith_history_t *hp = history; hp; hp = hp->next) {
392 if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
393 continue;
394 }
395
396 /* Skip the rest of the history after we see a failed/done action */
397 if ((hp->state == st_failed) || (hp->state == st_done)) {
398 break;
399 }
400
401 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Pending Fencing Actions");
402 out->message(out, "stonith-event", hp,
403 pcmk_all_flags_set(section_opts, pcmk_section_fencing_all),
404 false, stonith__later_succeeded(hp, history), show_opts);
405 out->increment_list(out);
406 }
407
409 return rc;
410}
411
412PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
413 "const char *", "uint32_t")
414static int
415stonith_event_html(pcmk__output_t *out, va_list args)
416{
417 stonith_history_t *event = va_arg(args, stonith_history_t *);
418 bool full_history = va_arg(args, int);
419 bool completed_only G_GNUC_UNUSED = va_arg(args, int);
420 const char *succeeded = va_arg(args, const char *);
421 uint32_t show_opts = va_arg(args, uint32_t);
422
423 gchar *desc = stonith__history_description(event, full_history, succeeded,
424 show_opts);
425
426 switch(event->state) {
427 case st_done:
428 out->list_item(out, "successful-stonith-event", "%s", desc);
429 break;
430
431 case st_failed:
432 out->list_item(out, "failed-stonith-event", "%s", desc);
433 break;
434
435 default:
436 out->list_item(out, "pending-stonith-event", "%s", desc);
437 break;
438 }
439 g_free(desc);
440 return pcmk_rc_ok;
441}
442
443PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
444 "const char *", "uint32_t")
445static int
446stonith_event_text(pcmk__output_t *out, va_list args)
447{
448 stonith_history_t *event = va_arg(args, stonith_history_t *);
449 bool full_history = va_arg(args, int);
450 bool completed_only = va_arg(args, int);
451 const char *succeeded = va_arg(args, const char *);
452 uint32_t show_opts = va_arg(args, uint32_t);
453
454 if (completed_only) {
455 pcmk__formatted_printf(out, "%lld\n", (long long) event->completed);
456 } else {
457 gchar *desc = stonith__history_description(event, full_history, succeeded,
458 show_opts);
459
460 pcmk__indented_printf(out, "%s\n", desc);
461 g_free(desc);
462 }
463
464 return pcmk_rc_ok;
465}
466
467PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool",
468 "const char *", "uint32_t")
469static int
470stonith_event_xml(pcmk__output_t *out, va_list args)
471{
472 stonith_history_t *event = va_arg(args, stonith_history_t *);
473 bool full_history G_GNUC_UNUSED = va_arg(args, int);
474 bool completed_only G_GNUC_UNUSED = va_arg(args, int);
475 const char *succeeded G_GNUC_UNUSED = va_arg(args, const char *);
476 uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
477
478 xmlNodePtr node = NULL;
479
481 PCMK_XA_ACTION, event->action,
482 PCMK_XA_TARGET, event->target,
483 PCMK_XA_CLIENT, event->client,
484 PCMK_XA_ORIGIN, event->origin,
485 NULL);
486
487 switch (event->state) {
488 case st_failed:
491 PCMK_XA_EXIT_REASON, event->exit_reason,
492 NULL);
493 break;
494
495 case st_done:
497 break;
498
499 default: {
500 char *state = pcmk__itoa(event->state);
504 NULL);
505 free(state);
506 break;
507 }
508 }
509
510 if (event->delegate != NULL) {
511 crm_xml_add(node, PCMK_XA_DELEGATE, event->delegate);
512 }
513
514 if ((event->state == st_failed) || (event->state == st_done)) {
515 char *time_s = timespec_string(event->completed, event->completed_nsec,
516 true);
517
518 crm_xml_add(node, PCMK_XA_COMPLETED, time_s);
519 free(time_s);
520 }
521
522 return pcmk_rc_ok;
523}
524
525PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
526 "const char *", "int")
527static int
528validate_agent_html(pcmk__output_t *out, va_list args) {
529 const char *agent = va_arg(args, const char *);
530 const char *device = va_arg(args, const char *);
531 const char *output = va_arg(args, const char *);
532 const char *error_output = va_arg(args, const char *);
533 int rc = va_arg(args, int);
534
535 if (device) {
536 char *buf = crm_strdup_printf("Validation of %s on %s %s", agent, device,
537 rc ? "failed" : "succeeded");
538 pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, buf);
539 free(buf);
540 } else {
541 char *buf = crm_strdup_printf("Validation of %s %s", agent,
542 rc ? "failed" : "succeeded");
543 pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, buf);
544 free(buf);
545 }
546
547 out->subprocess_output(out, rc, output, error_output);
548 return rc;
549}
550
551PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
552 "const char *", "int")
553static int
554validate_agent_text(pcmk__output_t *out, va_list args) {
555 const char *agent = va_arg(args, const char *);
556 const char *device = va_arg(args, const char *);
557 const char *output = va_arg(args, const char *);
558 const char *error_output = va_arg(args, const char *);
559 int rc = va_arg(args, int);
560
561 if (device) {
562 pcmk__indented_printf(out, "Validation of %s on %s %s\n", agent, device,
563 rc ? "failed" : "succeeded");
564 } else {
565 pcmk__indented_printf(out, "Validation of %s %s\n", agent,
566 rc ? "failed" : "succeeded");
567 }
568
569 out->subprocess_output(out, rc, output, error_output);
570 return rc;
571}
572
573PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *",
574 "const char *", "int")
575static int
576validate_agent_xml(pcmk__output_t *out, va_list args) {
577 const char *agent = va_arg(args, const char *);
578 const char *device = va_arg(args, const char *);
579 const char *output = va_arg(args, const char *);
580 const char *error_output = va_arg(args, const char *);
581 int rc = va_arg(args, int);
582
583 const char *valid = pcmk__btoa(rc == pcmk_ok);
584 xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_VALIDATE,
585 PCMK_XA_AGENT, agent,
586 PCMK_XA_VALID, valid,
587 NULL);
588
589 if (device != NULL) {
590 crm_xml_add(node, PCMK_XA_DEVICE, device);
591 }
592
594 out->subprocess_output(out, rc, output, error_output);
596
597 return rc;
598}
599
600static pcmk__message_entry_t fmt_functions[] = {
601 { "failed-fencing-list", "default", failed_history },
602 { "fencing-list", "default", stonith_history },
603 { "full-fencing-list", "default", full_history },
604 { "full-fencing-list", "xml", full_history_xml },
605 { "last-fenced", "html", last_fenced_html },
606 { "last-fenced", "log", last_fenced_text },
607 { "last-fenced", "text", last_fenced_text },
608 { "last-fenced", "xml", last_fenced_xml },
609 { "pending-fencing-list", "default", pending_actions },
610 { "stonith-event", "html", stonith_event_html },
611 { "stonith-event", "log", stonith_event_text },
612 { "stonith-event", "text", stonith_event_text },
613 { "stonith-event", "xml", stonith_event_xml },
614 { "validate", "html", validate_agent_html },
615 { "validate", "log", validate_agent_text },
616 { "validate", "text", validate_agent_text },
617 { "validate", "xml", validate_agent_xml },
618
619 { NULL, NULL, NULL }
620};
621
622void
#define PCMK_ACTION_ON
Definition actions.h:55
#define PCMK_ACTION_OFF
Definition actions.h:54
Utility functions.
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition util.h:80
const char * stonith__later_succeeded(const stonith_history_t *event, const stonith_history_t *top_history)
Definition st_client.c:2347
ISO_8601 Date handling.
#define crm_time_log_timeofday
Definition iso8601.h:68
#define crm_time_usecs
Definition iso8601.h:76
#define crm_time_log_with_timezone
Definition iso8601.h:69
#define crm_time_log_date
Definition iso8601.h:67
char * pcmk__timespec2str(const struct timespec *ts, uint32_t flags)
Definition iso8601.c:2183
#define PCMK_VALUE_SUCCESS
Definition options.h:217
#define PCMK_VALUE_PENDING
Definition options.h:193
#define PCMK_VALUE_FAILED
Definition options.h:153
Control output from tools.
@ pcmk_show_failed_detail
Definition output.h:67
#define pcmk_section_fencing_all
Definition output.h:46
Formatted output for pacemaker tools.
void pcmk__output_xml_pop_parent(pcmk__output_t *out)
Definition output_xml.c:566
void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent)
Definition output_xml.c:552
void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
Definition output.c:206
#define PCMK__OUTPUT_LIST_HEADER(out_obj, cond, retcode, title...)
xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text)
void pcmk__indented_printf(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name,...) G_GNUC_NULL_TERMINATED
Definition output_xml.c:519
void void void pcmk__formatted_printf(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define PCMK__OUTPUT_LIST_FOOTER(out_obj, retcode)
const char * target
Definition pcmk_fence.c:31
@ pcmk_rc_no_output
Definition results.h:128
@ pcmk_rc_ok
Definition results.h:159
#define pcmk_ok
Definition results.h:65
enum crm_exit_e crm_exit_t
Exit status codes for tools and daemons.
gchar * stonith__history_description(const stonith_history_t *history, bool full_history, const char *later_succeeded, uint32_t show_opts)
Definition st_output.c:109
void stonith__register_messages(pcmk__output_t *out)
Definition st_output.c:623
Fencing aka. STONITH.
@ st_failed
Definition stonith-ng.h:130
@ st_done
Definition stonith-ng.h:128
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
gboolean pcmk__str_in_list(const gchar *s, const GList *lst, uint32_t flags)
Definition strings.c:984
@ pcmk__str_none
@ pcmk__str_star_matches
@ pcmk__str_casei
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1299
This structure contains everything that makes up a single output formatter.
Fencing history entry.
Definition stonith-ng.h:173
struct stonith_history_s * next
Definition stonith-ng.h:181
Wrappers for and extensions to libxml2.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
void pcmk__xe_set_props(xmlNodePtr node,...) G_GNUC_NULL_TERMINATED
#define PCMK_XA_VALID
Definition xml_names.h:440
#define PCMK_XA_AGENT
Definition xml_names.h:234
#define PCMK_XA_TARGET
Definition xml_names.h:418
#define PCMK_XA_CLIENT
Definition xml_names.h:247
#define PCMK_XA_WHEN
Definition xml_names.h:450
#define PCMK_XA_STATUS
Definition xml_names.h:410
#define PCMK_XA_ORIGIN
Definition xml_names.h:351
#define PCMK_XA_EXTENDED_STATUS
Definition xml_names.h:281
#define PCMK_XE_FENCE_EVENT
Definition xml_names.h:114
#define PCMK_XA_DELEGATE
Definition xml_names.h:260
#define PCMK_XA_EXIT_REASON
Definition xml_names.h:274
#define PCMK_XE_LAST_FENCED
Definition xml_names.h:126
#define PCMK_XE_FENCE_HISTORY
Definition xml_names.h:115
#define PCMK_XA_ACTION
Definition xml_names.h:229
#define PCMK_XA_COMPLETED
Definition xml_names.h:250
#define PCMK_XE_VALIDATE
Definition xml_names.h:219
#define PCMK_XA_DEVICE
Definition xml_names.h:263
#define PCMK__XE_DIV