summaryrefslogtreecommitdiffstats
path: root/src/server/job.c
blob: b9e17f624558a90b37eb65694961f307672bf32f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
#include "job.h"
#include "saveload.h"
#include "helper.h"
#include "memlog.h"
#include "rpc.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <pthread.h>
#include "sockhelper.h"

#include <glib/gslist.h>

#include <libxml/parser.h>
#include <libxml/xpath.h>
#include "xmlutil.h"
#include "../config.h"

#define DEV_STRLEN 12 // INCLUDING NULLCHAR (increase to 13 if you need more than 100 (0-99) devices)
#define MAX_NUM_DEVICES_TO_CHECK 100

#ifndef	FALSE
#define	FALSE	(0)
#endif

#ifndef	TRUE
#define	TRUE	(!FALSE)
#endif

typedef struct
{
	char available;
	char name[DEV_STRLEN];
} device_t;

// "/dev/dnbdXX" == 11 bytes per device + nullchar = 12
static device_t *devices = NULL;
static int num_devices = 0;
static char keep_running = TRUE;

// Private functions
static char *get_free_device();
static void return_free_device(char *name);
static void connect_proxy_images();
static void query_servers();
static char *create_cache_filename(char *name, int rid, char *buffer, int maxlen);
static void add_alt_server(dnbd3_image_t *image, dnbd3_host_t *host);
static void remove_alt_server(dnbd3_trusted_server_t *server);
static void dnbd3_update_atimes(time_t now);

//

void *dnbd3_job_thread(void *data)
{
	int i, j;
	// Determine number of available dnbd3 devices, which are needed for proxy mode
	char dev[DEV_STRLEN];
	for (i = 0; i < MAX_NUM_DEVICES_TO_CHECK; ++i)
	{
		snprintf(dev, DEV_STRLEN, "/dev/dnbd%d", i);
		if (access(dev, W_OK | R_OK)) // Need RW access to device to read and do ioctl
			continue;
		++num_devices;
	}
	if (num_devices > 0)
	{
		devices = calloc(num_devices, sizeof(*devices));
		for (i = 0, j = 0; i < MAX_NUM_DEVICES_TO_CHECK; ++i)
		{
			memset(dev, 0, DEV_STRLEN);
			snprintf(dev, DEV_STRLEN, "/dev/dnbd%d", i);
			if (access(dev, W_OK | R_OK))
				continue;
			if (j >= num_devices) // More available devices during second iteration? :-(
				break;
			memcpy(devices[j].name, dev, DEV_STRLEN);
			devices[j].available = TRUE;
			++j;
		}
		num_devices = j;
	}
	memlogf("[INFO] %d available dnbd3 devices for proxy mode", num_devices);
	//
	time_t next_delete_invocation = 0;
	//
	// Job/Watchdog mainloop
	while (keep_running)
	{
		const time_t starttime = time(NULL);
		//
		// Update image atime
		dnbd3_update_atimes(starttime);
		// Call image deletion function if last call is more than 5 minutes ago
		if (starttime > next_delete_invocation)
		{
			next_delete_invocation = starttime + 300;
			dnbd3_exec_delete(TRUE);
		}
		// Check for proxied images that have not been set up yet
		connect_proxy_images();
		// TODO: Replicate proxied images (limited bandwidth)
		// Query other servers for new images/status/...
		query_servers();
		// TODO: Switch server of dnbd device based on more sophisticated inputs than just rtt
		// Calc sleep timeout for next iteration
		sleep(30 - (time(NULL) - starttime)); // Sleep 30 seconds, but account for the time it took to execute the loop
	}
	//
	free(devices);
	devices = NULL;
	pthread_exit(NULL);
	return NULL;
}

void dnbd3_job_shutdown()
{
	keep_running = FALSE;
}

static void connect_proxy_images()
{
	int s, n;
	dnbd3_server_entry_t servers[NUMBER_SERVERS];
	char imagename[1000];
	int rid;
	dnbd3_ioctl_t msg;
	memset(&msg, 0, sizeof(dnbd3_ioctl_t));
	msg.len = (uint16_t)sizeof(dnbd3_ioctl_t);
	msg.read_ahead_kb = DEFAULT_READ_AHEAD_KB;
	msg.is_server = TRUE;
	for (n = 0 ;; ++n) // Iterate over all images
	{
		pthread_spin_lock(&_spinlock);
		dnbd3_image_t *image = g_slist_nth_data(_dnbd3_images, n);
		if (image == NULL)
		{	// End of list reached
			pthread_spin_unlock(&_spinlock);
			break;
		}
		if (image->working && image->cache_map && image->file)
		{	// Image is relayed and already connected, check if cache is complete
			int complete = TRUE, j;
			const int map_len_bytes = IMGSIZE_TO_MAPBYTES(image->filesize);
			for (j = 0; j < map_len_bytes - 1; ++j)
			{
				if (image->cache_map[j] != 0xFF)
				{
					complete = FALSE;
					break;
				}
			}
			if (complete) // Every block except the last one is complete
			{ // Last one might need extra treatment if it's not a full byte
				const int blocks_in_last_byte = (image->filesize >> 12) & 7;
				uint8_t last_byte = 0;
				if (blocks_in_last_byte == 0)
					last_byte = 0xFF;
				else
					for (j = 0; j < blocks_in_last_byte; ++j)
						last_byte |= (1 << j);
				complete = ((image->cache_map[map_len_bytes - 1] & last_byte) == last_byte);
			}
			if (!complete) // Image is not complete, finished handling it
			{
				pthread_spin_unlock(&_spinlock);
				continue;
			}
			// Image is 100% cached, disconnect dnbd3 device
			memlogf("[INFO] Disconnecting %s because local copy of %s is complete.", image->file, image->config_group);
			int dh = open(image->file, O_RDONLY);
			if (dh < 0)
				memlogf("[ERROR] Could not open() device '%s'", image->file);
			else
			{
				if (ioctl(dh, IOCTL_CLOSE, (void*)0) != 0)
					memlogf("[ERROR] Could not IOCTL_CLOSE device '%s'", image->file);
				else
					return_free_device(image->file);
				close(dh);
			}
			free(image->file);
			image->file = NULL;
			pthread_spin_unlock(&_spinlock);
			continue;
		}
		if (image->working || image->file || image->low_name == NULL)
		{	// Image is a local one, nothing to do
			pthread_spin_unlock(&_spinlock);
			continue;
		}
		// Image is relayed and not connected yet
		char *devname = get_free_device();
		if (devname == NULL)
		{	// All devices busy, can't connect
			pthread_spin_unlock(&_spinlock);
			continue;
		}
		// Remember image information as the image pointer isn't
		// guaranteed to stay valid after unlocking
		snprintf(imagename, 1000, "%s", image->low_name);
		rid = image->rid;
		memcpy(servers, image->servers, sizeof(servers[0]) * NUMBER_SERVERS);
		pthread_spin_unlock(&_spinlock);
		int dh = open(devname, O_RDWR);
		if (dh < 0) // Open device so we can issue ioctls to it
		{
			pthread_spin_lock(&_spinlock);
			return_free_device(devname); // Failed :-(
			pthread_spin_unlock(&_spinlock);
			continue;
		}
		for (s = 0; s < NUMBER_SERVERS; ++s)
		{	// Try to connect to any of the alt servers known for that image
			if (servers[s].host.type == 0)
				continue;
			// connect device
			printf("[DEBUG] Connecting device....\n");
			msg.host = servers[s].host;
			msg.imgname = imagename;
			msg.imgnamelen = strlen(imagename);
			msg.rid = rid;
			if (ioctl(dh, IOCTL_OPEN, &msg) < 0)
				continue;
			printf("[DEBUG] Connected! Adding alt servers...\n");
			// connected. we manually add all known alt servers to this
			// device, so even if the initial server doesn't consider some
			// of those as trusted servers, they are still used for failover/load balancing
			for (++s; s < NUMBER_SERVERS; ++s)
			{
				if (servers[s].host.type == 0)
					continue;
				msg.host = servers[s].host;
				if (ioctl(dh, IOCTL_ADD_SRV, &msg) < 0)
					memlogf("[WARNING] Could not add alt server to proxy device");
				else
					printf("[DEBUG] Added an alt server\n");
			}
			printf("[DEBUG] Done, handling file size...\n");
			// LOCK + UPDATE
			int isworking = FALSE, alloc_cache = FALSE;
			pthread_spin_lock(&_spinlock);
			if (g_slist_find(_dnbd3_images, image) == NULL)
			{	// Image not in list anymore, was deleted in meantime...
				if (ioctl(dh, IOCTL_CLOSE, &msg) < 0)
					memlogf("[WARNING] Could not close device after use - lost %s", devname);
				else
					return_free_device(devname);
				pthread_spin_unlock(&_spinlock);
				break;
			}
			// Image still exists
			image->file = strdup(devname);
			long long oct = 0;
			int t, ret;
			for (t = 0; t < 10 && dh >= 0; ++t)
			{	// For some reason the getsize-ioctl might return 0 right after connecting
				// No idea why this happen. Maybe the IOCTL_OPEN call returns before the
				// connection is fully established, but I have no idea why.
				// So let's retry a couple of times if it fails.
				ret = ioctl(dh, BLKGETSIZE64, &oct);
				if (ret == 0 && oct > 0)
					break;
				close(dh);
				usleep(100 * 1000);
				dh = open(devname, O_RDONLY);
			}
			if (dh < 0 || ret != 0)
				memlogf("[ERROR] SIZE fail on %s (ret=%d, oct=%lld)", devname, ret, oct);
			else if (oct == 0)
				memlogf("[ERROR] Reported disk size is 0.");
			else if (image->filesize != 0 && image->filesize != oct)
				memlogf("[ERROR] Remote and local size of image do not match: %llu != %llu for %s", (unsigned long long)oct, (unsigned long long)image->filesize, image->low_name);
			else
				isworking = TRUE;
			image->filesize = (uint64_t)oct;
			if (image->cache_file != NULL && isworking && image->cache_map == NULL)
			{
				printf("[DEBUG] Image has cache file %s\n", image->cache_file);
				const int mapsize = IMGSIZE_TO_MAPBYTES(image->filesize);
				image->cache_map = calloc(mapsize, 1);
				off_t cachelen = -1;
				int ch = open(image->cache_file, O_RDONLY);
				if (ch >= 0)
				{
					cachelen = lseek(ch, 0, SEEK_END);
					close(ch);
				}
				if (ch < 0 || cachelen != image->filesize)
					alloc_cache = TRUE;
				if (cachelen == image->filesize)
				{
					char mapfile[strlen(image->cache_file) + 5];
					sprintf(mapfile, "%s.map", image->cache_file);
					int cmh = open(mapfile, O_RDONLY);
					if (cmh >= 0)
					{
						if (lseek(cmh, 0, SEEK_END) != mapsize)
							memlogf("[WARNING] Existing cache map has wrong size.");
						else
						{
							lseek(cmh, 0, SEEK_SET);
							read(cmh, image->cache_map, mapsize);
							printf("[DEBUG] Found existing cache file and map for %s\n", image->low_name);
						}
						close(cmh);
					}
				}
			}
			char cfname[1000] = {0};
			off_t fs = image->filesize;
			if (isworking && !(alloc_cache && image->cache_file))
			{
				image->working = TRUE;
				if (!image->cache_file) // This should be removed. proxy with no cache is completely pointless.
					memlogf("[WARNING] Proxy-Mode enabled without cache directory. This will most likely hurt performance.");
				goto continue_with_next_image;
			}
			snprintf(cfname, 1000, "%s", image->cache_file);
			pthread_spin_unlock(&_spinlock);
			if (isworking && *cfname)
			{
				int ch = open(cfname, O_WRONLY | O_CREAT, 0600);
				if (ch >= 0)
				{
					// Pre-allocate disk space
					// TODO: Check if this has a performance impact on the rest of the server (ie. client lag)
					// If so, do this gracefully by incrementing size and sleeping in between.
					printf("[DEBUG] Pre-allocating disk space...\n");
					lseek(ch, fs - 1, SEEK_SET);
					write(ch, &ch, 1);
					close(ch);
					printf("[DEBUG] Allocation complete.\n");
					pthread_spin_lock(&_spinlock);
					if (g_slist_find(_dnbd3_images, image) != NULL)
					{
						image->working = TRUE;
						memlogf("[INFO] Enabled relayed image %s (%lld)", image->low_name, (long long)fs);
						goto continue_with_next_image;
					}
					unlink(cfname);
					memlogf("[WARNING] Image has gone away");
					pthread_spin_unlock(&_spinlock);
				}
				else
					memlogf("[WARNING] Could not pre-allocate %s", cfname);
			}
			break;
		} // <-- end of loop over servers
		// If this point is reached, setting up replication was not successful,
		// so lock and free the filename allocated earlier, and return the device.
		pthread_spin_lock(&_spinlock);
		if (g_slist_find(_dnbd3_images, image) != NULL)
		{
			free(image->file);
			image->file = NULL;
		}
		return_free_device(devname);
continue_with_next_image:
		pthread_spin_unlock(&_spinlock);
		close(dh);
	}
}

static void dnbd3_update_atimes(time_t now)
{
	GSList *iterator;
	pthread_spin_lock(&_spinlock);
	for (iterator = _dnbd3_clients; iterator; iterator = iterator->next)
	{
		dnbd3_client_t *client = iterator->data;
		if (client && client->image && !client->is_server)
			client->image->atime = now;
	}
	pthread_spin_unlock(&_spinlock);
}

static void query_servers()
{
	if (_trusted_servers == NULL)
		return;
	int client_sock, num;
	dnbd3_trusted_server_t *server;
	dnbd3_host_t host;
	char xmlbuffer[MAX_RPC_PAYLOAD];
	for (num = 0;; ++num)
	{
		// "Iterate" this way to prevent holding the lock for a long time,
		// although there is a very small chance to skip a server this way...
		pthread_spin_lock(&_spinlock);
		server = g_slist_nth_data(_trusted_servers, num);
		if (server == NULL)
		{
			pthread_spin_unlock(&_spinlock);
			break; // Done
		}
		host = server->host; // Copy host, in case server gets deleted by another thread
		pthread_spin_unlock(&_spinlock);
		// Connect
		host.port = htons(ntohs(host.port) + 1); // RPC port is client port + 1
		client_sock = sock_connect(&host, 800, 600);
		if (client_sock == -1)
			continue;
		//
		// Send and receive info from server
		// Send message
		dnbd3_rpc_t header;
		header.cmd = htonl(RPC_IMG_LIST);
		header.size = 0;
		send(client_sock, (char *)&header, sizeof(header), 0);
		if (!recv_data(client_sock, &header, sizeof(header)))
		{
			printf("[DEBUG] Could not get status from other server...\n");
			goto communication_error;
		}
		header.cmd = ntohl(header.cmd);
		header.size = ntohl(header.size);
		if (header.cmd != RPC_IMG_LIST)
		{
			printf("[DEBUG] Error. Reply from other server was cmd:%d, error:%d\n", (int)header.cmd, (int)-1);
			goto communication_error;
		}
		if (header.size > MAX_RPC_PAYLOAD)
		{
			memlogf("[WARNING] XML payload from other server exceeds MAX_RPC_PAYLOAD (%d > %d)", (int)header.size, (int)MAX_RPC_PAYLOAD);
			goto communication_error;
		}
		if (!recv_data(client_sock, xmlbuffer, header.size))
		{
			printf("[DEBUG] Error reading XML payload from other server.\n");
			goto communication_error;
		}
		close(client_sock);
		//
		// Process data, update server info, add/remove this server as alt server for images, replicate images, etc.
		xmlDocPtr doc = xmlReadMemory(xmlbuffer, header.size, "noname.xml", NULL, 0);

		if (doc == NULL)
		{
			memlogf("[WARNING] Could not parse XML data received by other server.");
			goto communication_error;
		}
		// Data seems ok

		xmlNodePtr cur;
		FOR_EACH_NODE(doc, "/data/images/image", cur)
		{
			if (cur->type != XML_ELEMENT_NODE)
				continue;
			NEW_POINTERLIST;
			char *image = XML_GETPROP(cur, "name");
			char *ridstr = XML_GETPROP(cur, "rid");
			char *sizestr = XML_GETPROP(cur, "size");
			if (!image || !ridstr || !sizestr)
				goto free_current_image;
			const int rid = atoi(ridstr);
			const long long size = atoll(sizestr);
			if (rid <= 0)
			{
				printf("[DEBUG] Ignoring remote image with rid %d\n", rid);
				goto free_current_image;
			}
			char *slash = strrchr(image, '/');
			if (slash == NULL)
			{
				printf("[DEBUG] Ignoring remote image with no '/' in name...\n");
				goto free_current_image;
			}
			else
			{
				*slash++ = '\0';
				if (!is_valid_namespace(image))
				{
					printf("[DEBUG] Ignoring remote image with invalid namespace '%s'\n", image);
					goto free_current_image;
				}
				if (!is_valid_imagename(slash))
				{
					printf("[DEBUG] Ignoring remote image with invalid name '%s'\n", slash);
					goto free_current_image;
				}
				snprintf(xmlbuffer, MAX_RPC_PAYLOAD, "%s/%s", image, slash);
			}
			// Image seems legit, check if there's a local copy
			dnbd3_namespace_t *trust;
			pthread_spin_lock(&_spinlock);
			trust = dnbd3_get_trust_level(&host, image);
			if (trust == NULL)
			{	// Namespace of image is not trusted
				pthread_spin_unlock(&_spinlock);
				printf("[DEBUG] No NS match: '%s'\n", xmlbuffer);
				goto free_current_image;
			}
			char *sdel = XML_GETPROP(cur, "softdelete");
			char *hdel = XML_GETPROP(cur, "harddelete");
			const time_t softdelete = sdel ? atol(sdel) : 0;
			const time_t harddelete = hdel ? atol(hdel) : 0;
			dnbd3_image_t *local_image = dnbd3_get_image(xmlbuffer, rid, FALSE);
			if (local_image == NULL && trust->auto_replicate)
			{
				pthread_spin_unlock(&_spinlock);
				const time_t deadline = time(NULL) + 60;
				if ((softdelete != 0 && softdelete < deadline)
					|| (harddelete != 0 && harddelete < deadline))
				{	// Image is already about to be deleted, ignore it
					printf("[DEBUG] Not replicating old image: '%s'\n", xmlbuffer);
					goto free_current_image;
				}
				// Image is NEW, add it!
				dnbd3_image_t newimage;
				char cachefile[90];
				memset(&newimage, 0, sizeof(newimage));
				newimage.config_group = xmlbuffer;
				newimage.rid = rid;
				newimage.filesize = size;
				newimage.delete_hard = harddelete;
				newimage.delete_soft = softdelete;
				if (_cache_dir)
				{
					newimage.cache_file = create_cache_filename(xmlbuffer, rid, cachefile, 90);
					printf("[DEBUG] Cache file is %s\n", newimage.cache_file);
				}
				dnbd3_add_image(&newimage);
				pthread_spin_lock(&_spinlock);
				local_image = dnbd3_get_image(xmlbuffer, rid, FALSE);
				if (local_image)
					add_alt_server(local_image, &host);
				pthread_spin_unlock(&_spinlock);
			}
			else if (local_image != NULL)
			{
				// Image is already KNOWN, add alt server if appropriate
				const time_t deadline = time(NULL) + 60;
				if (softdelete == 0 || softdelete > deadline)
				{
					if (local_image->filesize == 0) // Size is unknown, just assume the trusted server got it right
						local_image->filesize = size;
					if (size != local_image->filesize)
						printf("[DEBUG] Ignoring remote image '%s' because it has a different size from the local version! (remote: %llu, local: %llu)\n", local_image->config_group, size, (unsigned long long)local_image->filesize);
					else
						add_alt_server(local_image, &host);
				}
				if (local_image->cache_file && trust->auto_replicate) {
					local_image->delete_hard = harddelete;
					local_image->delete_soft = softdelete;
				}
				pthread_spin_unlock(&_spinlock);
			}
			else
			{
				printf("[DEBUG] No NS match: '%s'\n", xmlbuffer);
				pthread_spin_unlock(&_spinlock);
			}
			// Cleanup
free_current_image:
			FREE_POINTERLIST;
		} END_FOR_EACH;


		// ...
		xmlFreeDoc(doc);
		//
		continue;
communication_error:
		close(client_sock);
		pthread_spin_lock(&_spinlock);
		if (g_slist_find(_trusted_servers, server))
		{
			if (server->unreachable < 10 && ++server->unreachable == 5)
				remove_alt_server(server);
		}
		pthread_spin_unlock(&_spinlock);
	}
}

static char *create_cache_filename(char *name, int rid, char *buffer, int maxlen)
{
	if (_cache_dir == NULL)
		return NULL;
	size_t cdl = strlen(_cache_dir);
	if (maxlen < 15 + cdl)
		return NULL;
	if (strlen(name) + 16 + cdl < maxlen)
		snprintf(buffer, maxlen, "%s/%s_rid_%d.cache", _cache_dir, name, rid);
	else
	{
		char *slash = strrchr(name, '/');
		if (slash == NULL)
		{
			snprintf(buffer, maxlen - 17, "%s/%s", _cache_dir, name);
			snprintf(buffer + maxlen - 17, 17, "_rid_%d.cache", rid);
		}
		else
		{
			snprintf(buffer, maxlen, "%s/%s", _cache_dir, name);
			snprintf(buffer + cdl, maxlen - cdl, "%s_rid_%d.cache", slash, rid);
		}
	}
	char *ptr = buffer + cdl + 1;
	while (*ptr)
	{
		if (*ptr == '/' || *ptr < 32 || *ptr == ' ' || *ptr == '\\' || *ptr == '*' || *ptr == '?')
			*ptr = '_';
		++ptr;
	}
	FILE *fh;
	while ((fh = fopen(buffer, "rb")))
	{	// Alter file name as long as a file by that name already exists (el cheapo edition)
		fclose(fh);
		char *c = buffer + rand() % strlen(buffer);
		*c = rand() % 26 + 'A';
	}
	return buffer;
}

/**
 * !! Call this while holding the lock !!
 */
static void add_alt_server(dnbd3_image_t *image, dnbd3_host_t *host)
{
	int i;
	for (i = 0; i < NUMBER_SERVERS; ++i)
	{
		if (is_same_server(host, &image->servers[i].host))
		{	// Alt server already known for this image
			if (image->servers[i].failures)
			{	// It was disabled, re-enable and send info to clients
				image->servers[i].failures = 0;
				break;
			}
			else	// Alt-Server already known and active, do nothing
				return;
		}
	}
	// Add to list if it wasn't in there
	if (i >= NUMBER_SERVERS)
		for (i = 0; i < NUMBER_SERVERS; ++i)
		{
			if (image->servers[i].host.type == 0)
			{
				image->servers[i].host = *host;
				image->servers[i].failures = 0;
				break;
			}
		}
	if (i >= NUMBER_SERVERS) // To many known alt servers already
		return;
	// Broadcast to connected clients. Note that 'i' now points to the new server
	printf("[DEBUG] Adding alt server to %s\n", image->low_name);
	GSList *itc;
	dnbd3_reply_t header;
	header.cmd = CMD_GET_SERVERS;
	header.magic = dnbd3_packet_magic;
	header.size = sizeof(dnbd3_server_entry_t);
	fixup_reply(header);
	for (itc = _dnbd3_clients; itc; itc = itc->next)
	{
		dnbd3_client_t *const client = itc->data;
		if (client->image == image)
		{
			// Don't send message directly as the lock is being held; instead, enqueue it
			NEW_BINSTRING(message, sizeof(header) + sizeof(image->servers[i]));
			memcpy(message->data, &header, sizeof(header));
			memcpy(message->data + sizeof(header), &image->servers[i], sizeof(image->servers[i]));
			client->sendqueue = g_slist_append(client->sendqueue, message);
		}
	}
}

/**
 * !! Call this while holding the lock !!
 */
static void remove_alt_server(dnbd3_trusted_server_t *server)
{
	GSList *iti, *itc;
	int i;
	dnbd3_reply_t header;
	header.cmd = CMD_GET_SERVERS;
	header.magic = dnbd3_packet_magic;
	header.size = sizeof(dnbd3_server_entry_t);
	fixup_reply(header);
	// Iterate over all images
	for (iti = _dnbd3_images; iti; iti = iti->next)
	{
		dnbd3_image_t *const image = iti->data;
		// Check if any alt_server for that image is the server to be removed
		for (i = 0; i < NUMBER_SERVERS; ++i)
		{
			if (is_same_server(&server->host, &image->servers[i].host))
			{
				// Remove server from that image and tell every connected client about it
				image->servers[i].failures = 1;
				for (itc = _dnbd3_clients; itc; itc = itc->next)
				{
					dnbd3_client_t *const client = itc->data;
					if (client->image == image)
					{
						// Don't send message directly as the lock is being held; instead, enqueue it
						NEW_BINSTRING(message, sizeof(header) + sizeof(image->servers[i]));
						memcpy(message->data, &header, sizeof(header));
						memcpy(message->data + sizeof(header), &image->servers[i], sizeof(image->servers[i]));
						client->sendqueue = g_slist_append(client->sendqueue, message);
					}
				}
				image->servers[i].host.type = 0;
			}
		}
	}
}

/**
 * Get full name of an available dnbd3 device, eg. /dev/dnbd4
 * Returned buffer is owned by this module, do not modify or free!
 * Returns NULL if all devices are in use
 */
static char *get_free_device()
{
	if (devices == NULL)
		return NULL;
	int i, c;
	char buffer[100];
	for (i = 0; i < num_devices; ++i)
	{
		if (!devices[i].available)
			continue;
		devices[i].available = FALSE;
		// Check sysfs if device is maybe already connected
		snprintf(buffer, 100, "/sys/devices/virtual/block/%s/net/cur_server_addr", devices[i].name + 5);
		FILE *f = fopen(buffer, "r");
		if (f == NULL)
		{
			printf("[DEBUG] Could not open %s - device marked as used.\n", buffer);
			continue;
		}
		c = fgetc(f);
		fclose(f);
		if (c > 0)
		{
			// Could read something, so the device is connected
			printf("[DEBUG] Free device %s is actually in use - marked as such.\n", devices[i].name);
			continue;
		}
		return devices[i].name;
	}
	memlogf("[WARNING] No more free dnbd3 devices - proxy mode probably affected.");
	return NULL;
}

static void return_free_device(char *name)
{
	if (devices == NULL)
		return;
	int i;
	for (i = 0; i < num_devices; ++i)
	{
		if (devices[i].available || strcmp(devices[i].name, name) != 0)
			continue;
		devices[i].available = TRUE;
		break;
	}
}