1 /*
2    Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, see <http://www.gnu.org/licenses/>.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #if defined(_WIN32)
22 #include <winsock2.h>
23 #include "win32_compat.h"
24 #pragma comment(lib, "ws2_32.lib")
25 WSADATA wsaData;
26 #endif
27 
28 #ifdef HAVE_POLL_H
29 #include <poll.h>
30 #endif
31 
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <string.h>
40 #include "iscsi.h"
41 #include "scsi-lowlevel.h"
42 
43 #ifndef discard_const
44 #define discard_const(ptr) ((void *)((intptr_t)(ptr)))
45 #endif
46 
47 int showluns;
48 int useurls;
49 const char *initiator = "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-ls";
50 
51 struct client_state {
52        int finished;
53        int status;
54        int lun;
55        int type;
56        const char *username;
57        const char *password;
58 };
59 
60 
event_loop(struct iscsi_context * iscsi,struct client_state * state)61 void event_loop(struct iscsi_context *iscsi, struct client_state *state)
62 {
63 	struct pollfd pfd;
64 
65 	while (state->finished == 0) {
66 		pfd.fd = iscsi_get_fd(iscsi);
67 		pfd.events = iscsi_which_events(iscsi);
68 
69 		if (!pfd.events) {
70 			sleep(1);
71 			continue;
72 		}
73 
74 		if (poll(&pfd, 1, -1) < 0) {
75 			fprintf(stderr, "Poll failed");
76 			exit(10);
77 		}
78 		if (iscsi_service(iscsi, pfd.revents) < 0) {
79 			fprintf(stderr, "iscsi_service failed with : %s\n", iscsi_get_error(iscsi));
80 			exit(10);
81 		}
82 	}
83 }
84 
show_lun(struct iscsi_context * iscsi,int lun)85 void show_lun(struct iscsi_context *iscsi, int lun)
86 {
87 	struct scsi_task *task;
88 	struct scsi_inquiry_standard *inq;
89 	int type, no_media;
90 	long long size = 0;
91 	int size_pf = 0;
92 	static const char sf[] = {' ', 'k', 'M', 'G', 'T' };
93 
94 	/* check we can talk to the lun */
95 tur_try_again:
96 	if ((task = iscsi_testunitready_sync(iscsi, lun)) == NULL) {
97 		fprintf(stderr, "testunitready failed\n");
98 		exit(10);
99 	}
100 	if (task->status == SCSI_STATUS_CHECK_CONDITION) {
101 		if (task->sense.key == SCSI_SENSE_UNIT_ATTENTION && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET) {
102 			scsi_free_scsi_task(task);
103 			goto tur_try_again;
104 		}
105 	}
106 
107 	no_media = 0;
108 	if (task->status     == SCSI_STATUS_CHECK_CONDITION
109 	&&  task->sense.key  == SCSI_SENSE_NOT_READY
110 	&&  task->sense.ascq == SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT) {
111 	    	/* not an error, just a cdrom without a disk most likely */
112 		no_media = 1;
113 	} else if (task->status != SCSI_STATUS_GOOD) {
114 		fprintf(stderr, "TESTUNITREADY failed with %s\n", iscsi_get_error(iscsi));
115 		exit(10);
116 	}
117 	scsi_free_scsi_task(task);
118 
119 
120 
121 	/* check what type of lun we have */
122 	task = iscsi_inquiry_sync(iscsi, lun, 0, 0, 64);
123 	if (task == NULL || task->status != SCSI_STATUS_GOOD) {
124 		fprintf(stderr, "failed to send inquiry command : %s\n", iscsi_get_error(iscsi));
125 		exit(10);
126 	}
127 	inq = scsi_datain_unmarshall(task);
128 	if (inq == NULL) {
129 		fprintf(stderr, "failed to unmarshall inquiry datain blob\n");
130 		exit(10);
131 	}
132 	type = inq->device_type;
133 	scsi_free_scsi_task(task);
134 
135 
136 
137 	if (type == SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
138 		struct scsi_readcapacity10 *rc10;
139 
140 		task = iscsi_readcapacity10_sync(iscsi, lun, 0, 0);
141 		if (task == NULL || task->status != SCSI_STATUS_GOOD) {
142 			fprintf(stderr, "failed to send readcapacity command\n");
143 			exit(10);
144 		}
145 
146 		rc10 = scsi_datain_unmarshall(task);
147 		if (rc10 == NULL) {
148 			fprintf(stderr, "failed to unmarshall readcapacity10 data\n");
149 			exit(10);
150 		}
151 
152 		size  = rc10->block_size;
153 		size *= rc10->lba;
154 
155 		for (size_pf=0; size_pf<4 && size > 1024; size_pf++) {
156 			size /= 1024;
157 		}
158 
159 		scsi_free_scsi_task(task);
160 	}
161 
162 
163 	printf("Lun:%-4d Type:%s", lun, scsi_devtype_to_str(type));
164 	if (type == SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
165 		printf(" (Size:%lld%c)", size, sf[size_pf]);
166 	}
167 	if (no_media) {
168 		printf(" (No media loaded)");
169 	}
170 	printf("\n");
171 }
172 
list_luns(struct client_state * clnt,const char * target,const char * portal)173 void list_luns(struct client_state *clnt, const char *target, const char *portal)
174 {
175 	struct iscsi_context *iscsi;
176 	struct scsi_task *task;
177 	struct scsi_reportluns_list *list;
178 	int full_report_size;
179 	int i;
180 
181 	if (strncasecmp(portal, "[fe80:", 6) == 0) {
182 		fprintf(stderr, "skipping link-local address\n");
183 		return;
184 	}
185 
186 	iscsi = iscsi_create_context(initiator);
187 	if (iscsi == NULL) {
188 		printf("Failed to create context\n");
189 		exit(10);
190 	}
191 
192 	iscsi_set_initiator_username_pwd(iscsi, clnt->username, clnt->password);
193 	if (iscsi_set_targetname(iscsi, target)) {
194 		fprintf(stderr, "Failed to set target name\n");
195 		exit(10);
196 	}
197 	iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL);
198 	iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
199 
200 	if (iscsi_full_connect_sync(iscsi, portal, -1) != 0) {
201 		printf("list_luns: iscsi_connect failed. %s\n",
202                        iscsi_get_error(iscsi));
203 		exit(10);
204 	}
205 
206 	/* get initial reportluns data, all targets can report 16 bytes but some
207 	 * fail if we ask for too much.
208 	 */
209 	if ((task = iscsi_reportluns_sync(iscsi, 0, 16)) == NULL) {
210 		fprintf(stderr, "reportluns failed : %s\n", iscsi_get_error(iscsi));
211 		exit(10);
212 	}
213 	full_report_size = scsi_datain_getfullsize(task);
214 	if (full_report_size > task->datain.size) {
215 		scsi_free_scsi_task(task);
216 
217 		/* we need more data for the full list */
218 		if ((task = iscsi_reportluns_sync(iscsi, 0, full_report_size)) == NULL) {
219 			fprintf(stderr, "reportluns failed : %s\n", iscsi_get_error(iscsi));
220 			exit(10);
221 		}
222 	}
223 
224 	list = scsi_datain_unmarshall(task);
225 	if (list == NULL) {
226 		fprintf(stderr, "failed to unmarshall reportluns datain blob\n");
227 		exit(10);
228 	}
229 	for (i=0; i < (int)list->num; i++) {
230 		show_lun(iscsi, list->luns[i]);
231 	}
232 
233 	scsi_free_scsi_task(task);
234 	iscsi_destroy_context(iscsi);
235 }
236 
237 
238 
239 
discoverylogout_cb(struct iscsi_context * iscsi,int status,void * command_data _U_,void * private_data)240 void discoverylogout_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data)
241 {
242 	struct client_state *state = (struct client_state *)private_data;
243 
244 	if (status != 0) {
245 		fprintf(stderr, "Failed to logout from target. : %s\n", iscsi_get_error(iscsi));
246 		exit(10);
247 	}
248 
249 	if (iscsi_disconnect(iscsi) != 0) {
250 		fprintf(stderr, "Failed to disconnect old socket\n");
251 		exit(10);
252 	}
253 
254 	state->finished = 1;
255 }
256 
discovery_cb(struct iscsi_context * iscsi,int status,void * command_data,void * private_data)257 void discovery_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data)
258 {
259 	struct iscsi_discovery_address *addr;
260 
261 	if (status != 0) {
262 		fprintf(stderr, "Failed to do discovery on target. : %s\n", iscsi_get_error(iscsi));
263 		exit(10);
264 	}
265 
266 	for(addr=command_data; addr; addr=addr->next) {
267 		struct iscsi_target_portal *portal = addr->portals;
268 
269 		while (portal != NULL) {
270 			if (useurls == 1 && showluns == 0) {
271 				char *str = strrchr(portal->portal, ',');
272 				if (str != NULL) {
273 					str[0] = 0;
274 				}
275 				printf("iscsi://%s/%s/0\n", portal->portal, addr->target_name);
276 			} else {
277 				printf("Target:%s Portal:%s\n", addr->target_name, portal->portal);
278 			}
279 			if (showluns != 0) {
280 				list_luns(private_data, addr->target_name, portal->portal);
281 			}
282 			portal = portal->next;
283 		}
284 	}
285 
286 	if (iscsi_logout_async(iscsi, discoverylogout_cb, private_data) != 0) {
287 		fprintf(stderr, "iscsi_logout_async failed : %s\n", iscsi_get_error(iscsi));
288 		exit(10);
289 	}
290 }
291 
292 
discoverylogin_cb(struct iscsi_context * iscsi,int status,void * command_data _U_,void * private_data)293 void discoverylogin_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data)
294 {
295 	if (status != 0) {
296 		fprintf(stderr, "Login failed. %s\n", iscsi_get_error(iscsi));
297 		exit(10);
298 	}
299 
300 	if (iscsi_discovery_async(iscsi, discovery_cb, private_data) != 0) {
301 		fprintf(stderr, "failed to send discovery command : %s\n", iscsi_get_error(iscsi));
302 		exit(10);
303 	}
304 }
305 
discoveryconnect_cb(struct iscsi_context * iscsi,int status,void * command_data _U_,void * private_data)306 void discoveryconnect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, void *private_data)
307 {
308 	if (status != 0) {
309 		fprintf(stderr, "discoveryconnect_cb: connection failed : %s\n", iscsi_get_error(iscsi));
310 		exit(10);
311 	}
312 
313 	if (iscsi_login_async(iscsi, discoverylogin_cb, private_data) != 0) {
314 		fprintf(stderr, "iscsi_login_async failed : %s\n", iscsi_get_error(iscsi));
315 		exit(10);
316 	}
317 }
318 
print_usage(void)319 void print_usage(void)
320 {
321 	fprintf(stderr, "Usage: iscsi-ls [-?|--help] [-d|--debug] "
322                 "[--usage] [-i|--initiator-name=iqn-name]\n"
323                 "\t\t[-s|--show-luns] <iscsi-portal-url>\n");
324 }
325 
print_help(void)326 void print_help(void)
327 {
328 	fprintf(stderr, "Usage: iscsi-ls [OPTION...] <iscsi-url>\n");
329 	fprintf(stderr, "  -i, --initiator-name=iqn-name     Initiatorname to use\n");
330 	fprintf(stderr, "  -d, --debug                       Print debug information\n");
331 	fprintf(stderr, "  -s, --show-luns                   Show the luns for each target\n");
332 	fprintf(stderr, "      --url                         Output targets in URL format\n");
333 	fprintf(stderr, "                                    (does not work with -s)\n");
334 	fprintf(stderr, "\n");
335 	fprintf(stderr, "Help options:\n");
336 	fprintf(stderr, "  -?, --help                        Show this help message\n");
337 	fprintf(stderr, "      --usage                       Display brief usage message\n");
338 	fprintf(stderr, "\n");
339 	fprintf(stderr, "iSCSI Portal URL format : %s\n", ISCSI_PORTAL_URL_SYNTAX);
340 	fprintf(stderr, "\n");
341 	fprintf(stderr, "<host> is either of:\n");
342 	fprintf(stderr, "  \"hostname\"       iscsi.example\n");
343 	fprintf(stderr, "  \"ipv4-address\"   10.1.1.27\n");
344 	fprintf(stderr, "  \"ipv6-address\"   [fce0::1]\n");
345 }
346 
main(int argc,char * argv[])347 int main(int argc, char *argv[])
348 {
349 	struct iscsi_context *iscsi;
350 	struct iscsi_url *iscsi_url = NULL;
351 	struct client_state state;
352 	const char *url = NULL;
353 	int i;
354 	static int show_help = 0, show_usage = 0, debug = 0;
355 
356 #ifdef _WIN32
357 	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
358 		printf("Failed to start Winsock2\n");
359 		exit(10);
360 	}
361 #endif
362 
363         for (i = 1; i < argc; i++) {
364                 if (!strcmp(argv[i], "-?") ||
365                     !strcmp(argv[i], "-h") ||
366                     !strcmp(argv[i], "--help")) {
367                                 show_help = 1;
368                 } else if (!strcmp(argv[i], "-u") ||
369                            !strcmp(argv[i], "-usage")) {
370 			show_usage = 1;
371                 } else if (!strcmp(argv[i], "-d") ||
372                            !strcmp(argv[i], "--debug")) {
373 			debug = 1;
374                 } else if (!strcmp(argv[i], "-i") ||
375                            !strcmp(argv[i], "--initiator-name")) {
376 			initiator = argv[++i];
377                 } else if (!strcmp(argv[i], "-s") ||
378                            !strcmp(argv[i], "--show-luns")) {
379 			showluns = 1;
380                 } else if (!strcmp(argv[i], "-U") ||
381                            !strcmp(argv[i], "--url")) {
382 			useurls = 1;
383                 } else if (!strncmp("iscsi://", argv[i], 8)) {
384                         url = strdup(argv[i]);
385                 }
386         }
387 
388 	if (show_help != 0) {
389 		print_help();
390 		exit(0);
391 	}
392 
393 	if (show_usage != 0) {
394 		print_usage();
395 		exit(0);
396 	}
397 
398 	memset(&state, 0, sizeof(state));
399 
400 	if (url == NULL) {
401 		fprintf(stderr, "You must specify iscsi target portal.\n");
402 		print_usage();
403 		exit(10);
404 	}
405 
406 	iscsi = iscsi_create_context(initiator);
407 	if (iscsi == NULL) {
408 		printf("Failed to create context\n");
409 		exit(10);
410 	}
411 
412 	iscsi_set_session_type(iscsi, ISCSI_SESSION_DISCOVERY);
413         iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE);
414 
415 	if (debug > 0) {
416 		iscsi_set_log_level(iscsi, debug);
417 		iscsi_set_log_fn(iscsi, iscsi_log_to_stderr);
418 	}
419 
420 	iscsi_url = iscsi_parse_portal_url(iscsi, url);
421 
422 	if (url) {
423 		free(discard_const(url));
424 	}
425 
426 	if (iscsi_url == NULL) {
427 		fprintf(stderr, "Failed to parse URL: %s\n",
428 			iscsi_get_error(iscsi));
429 		exit(10);
430 	}
431 
432 	state.username = iscsi_url->user;
433 	state.password = iscsi_url->passwd;
434 
435 	if (iscsi_connect_async(iscsi, iscsi_url->portal, discoveryconnect_cb, &state) != 0) {
436 		fprintf(stderr, "connect_async: iscsi_connect failed. %s\n",
437                         iscsi_get_error(iscsi));
438 		exit(10);
439 	}
440 
441 	event_loop(iscsi, &state);
442 
443 	iscsi_destroy_url(iscsi_url);
444 	iscsi_destroy_context(iscsi);
445 	return 0;
446 }
447 
448