added an optional recall delay
[tridge/hacksm.git] / hacksmd.c
1 /*
2   a test implementation of a HSM daemon
3
4   Andrew Tridgell August 2008
5
6  */
7
8 #include "hacksm.h"
9
10 static struct {
11         bool blocking_wait;
12         unsigned debug;
13         bool use_fork;
14         unsigned recall_delay;
15 } options = {
16         .blocking_wait = true,
17         .debug = 2,
18         .use_fork = false,
19         .recall_delay = 0,
20 };
21
22 static struct {
23         dm_sessid_t sid;
24 } dmapi = {
25         .sid = DM_NO_SESSION
26 };
27
28 #define SESSION_NAME "hacksmd"
29
30 /* no special handling on terminate in hacksmd, as we want existing
31    events to stay around so we can continue them on restart */
32 static void hsm_term_handler(int signal)
33 {
34         printf("Got signal %d - exiting\n", signal);
35         exit(1);
36 }
37
38
39 /*
40   initialise DMAPI, possibly recovering an existing session. The
41   hacksmd session is never destroyed, to allow for recovery of
42   partially completed events
43  */
44 static void hsm_init(void)
45 {
46         char *dmapi_version = NULL;
47         dm_eventset_t eventSet;
48         int ret;
49         int errcode = 0;
50
51         while ((ret = dm_init_service(&dmapi_version)) == -1) {
52                 if (errno != errcode) {
53                         errcode = errno;
54                         printf("Waiting for DMAPI to initialise (%d: %s)\n", 
55                                errno, strerror(errno));
56                 }
57                 sleep(1);
58         }
59
60         printf("Initialised DMAPI version '%s'\n", dmapi_version);      
61
62         hsm_recover_session(SESSION_NAME, &dmapi.sid);
63
64         /* we want mount events only initially */
65         DMEV_ZERO(eventSet);
66         DMEV_SET(DM_EVENT_MOUNT, eventSet);
67         ret = dm_set_disp(dmapi.sid, DM_GLOBAL_HANP, DM_GLOBAL_HLEN, DM_NO_TOKEN,
68                           &eventSet, DM_EVENT_MAX);
69         if (ret != 0) {
70                 printf("Failed to setup events\n");
71                 exit(1);
72         }
73 }
74
75
76 /*
77   called on a DM_EVENT_MOUNT event . This just needs to acknowledge
78   the mount. We don't have any sort of 'setup' step before running
79   hacksmd on a filesystem, so it just accepts mount events from any
80   filesystem that supports DMAPI
81  */
82 static void hsm_handle_mount(dm_eventmsg_t *msg)
83 {
84         dm_mount_event_t *mount;
85         void *hand1;
86         size_t hand1len;
87         dm_eventset_t eventSet;
88         int ret;
89         
90         mount = DM_GET_VALUE(msg, ev_data, dm_mount_event_t*);
91         hand1 = DM_GET_VALUE(mount , me_handle1, void *);
92         hand1len = DM_GET_LEN(mount, me_handle1);
93         
94         DMEV_ZERO(eventSet);
95         DMEV_SET(DM_EVENT_READ, eventSet);
96         DMEV_SET(DM_EVENT_WRITE, eventSet);
97         DMEV_SET(DM_EVENT_TRUNCATE, eventSet);
98         DMEV_SET(DM_EVENT_DESTROY, eventSet);
99         ret = dm_set_eventlist(dmapi.sid, hand1, hand1len,
100                                DM_NO_TOKEN, &eventSet, DM_EVENT_MAX);
101         if (ret != 0) {
102                 printf("Failed to setup all event handler\n");
103                 exit(1);
104         }
105         
106         ret = dm_set_disp(dmapi.sid, hand1, hand1len, DM_NO_TOKEN,
107                           &eventSet, DM_EVENT_MAX);
108         if (ret != 0) {
109                 printf("Failed to setup disposition for all events\n");
110                 exit(1);
111         }
112         
113         ret = dm_respond_event(dmapi.sid, msg->ev_token, 
114                                DM_RESP_CONTINUE, 0, 0, NULL);
115         if (ret != 0) {
116                 printf("Failed to respond to mount event\n");
117                 exit(1);
118         }
119 }
120
121 /*
122   called on a data event from DMAPI. Check the files attribute, and if
123   it is migrated then do a recall
124  */
125 static void hsm_handle_recall(dm_eventmsg_t *msg)
126 {
127         dm_data_event_t *ev;
128         void *hanp;
129         size_t hlen, rlen;
130         int ret;
131         dm_attrname_t attrname;
132         dm_token_t token = msg->ev_token;
133         struct hsm_attr h;
134         dm_boolean_t exactFlag;
135         int fd;
136         char buf[0x10000];
137         off_t ofs;
138         dm_right_t right;
139         dm_response_t response = DM_RESP_CONTINUE;
140         int retcode = 0;
141
142         ev = DM_GET_VALUE(msg, ev_data, dm_data_event_t *);
143         hanp = DM_GET_VALUE(ev, de_handle, void *);
144         hlen = DM_GET_LEN(ev, de_handle);
145
146         memset(attrname.an_chars, 0, DM_ATTR_NAME_SIZE);
147         strncpy((char*)attrname.an_chars, HSM_ATTRNAME, DM_ATTR_NAME_SIZE);
148
149         /* make sure we have an exclusive right on the file */
150         ret = dm_query_right(dmapi.sid, hanp, hlen, token, &right);
151         if (ret != 0 && errno != ENOENT) {
152                 printf("dm_query_right failed - %s\n", strerror(errno));
153                 retcode = EIO;
154                 response = DM_RESP_ABORT;
155                 goto done;
156         }
157         
158         if (right != DM_RIGHT_EXCL || errno == ENOENT) {
159                 ret = dm_request_right(dmapi.sid, hanp, hlen, token, DM_RR_WAIT, DM_RIGHT_EXCL);
160                 if (ret != 0) {
161                         printf("dm_request_right failed - %s\n", strerror(errno));
162                         retcode = EIO;
163                         response = DM_RESP_ABORT;
164                         goto done;
165                 }
166         }
167
168         /* get the attribute from the file, and make sure it is
169            valid */
170         ret = dm_get_dmattr(dmapi.sid, hanp, hlen, token, &attrname, 
171                             sizeof(h), &h, &rlen);
172         if (ret != 0) {
173                 if (errno == ENOENT) {
174                         if (options.debug > 2) {
175                                 printf("File already recalled (no attribute)\n");
176                         }
177                         goto done;
178                 }
179                 printf("dm_get_dmattr failed - %s\n", strerror(errno));
180                 retcode = EIO;
181                 response = DM_RESP_ABORT;
182                 goto done;
183         }
184
185         if (rlen != sizeof(h)) {
186                 printf("hsm_handle_read - bad attribute size %d\n", (int)rlen);
187                 retcode = EIO;
188                 response = DM_RESP_ABORT;
189                 goto done;
190         }
191
192         if (strncmp(h.magic, HSM_MAGIC, sizeof(h.magic)) != 0) {
193                 printf("Bad magic '%*.*s'\n", (int)sizeof(h.magic), (int)sizeof(h.magic),
194                        h.magic);
195                 retcode = EIO;
196                 response = DM_RESP_ABORT;
197                 goto done;
198         }
199
200         /* mark the file as being recalled. This ensures that if
201            hacksmd dies part way through the recall that another
202            migrate won't happen until the recall is completed by a
203            restarted hacksmd */
204         h.state = HSM_STATE_RECALL;
205         ret = dm_set_dmattr(dmapi.sid, hanp, hlen, token, &attrname, 0, sizeof(h), (void*)&h);
206         if (ret != 0) {
207                 printf("dm_set_dmattr failed - %s\n", strerror(errno));
208                 retcode = EIO;
209                 response = DM_RESP_ABORT;
210                 goto done;
211         }
212
213         /* get the migrated data from the store, and put it in the
214            file with invisible writes */
215         fd = hsm_store_open(h.device, h.inode, O_RDONLY);
216         if (fd == -1) {
217                 printf("Failed to open store file for file 0x%llx:0x%llx\n",
218                        (unsigned long long)h.device, (unsigned long long)h.inode);
219                 retcode = EIO;
220                 response = DM_RESP_ABORT;
221                 goto done;
222         }
223
224         if (options.debug > 1) {
225                 printf("%s %s: Recalling file %llx:%llx of size %d\n", 
226                        timestring(),
227                        dmapi_event_string(msg->ev_type),
228                        (unsigned long long)h.device, (unsigned long long)h.inode,
229                        (int)h.size);
230         }
231
232         if (options.recall_delay) {
233                 sleep(random() % options.recall_delay);
234         }
235
236         ofs = 0;
237         while ((ret = read(fd, buf, sizeof(buf))) > 0) {
238                 int ret2 = dm_write_invis(dmapi.sid, hanp, hlen, token, DM_WRITE_SYNC, ofs, ret, buf);
239                 if (ret2 != ret) {
240                         printf("dm_write_invis failed - %s\n", strerror(errno));
241                         retcode = EIO;
242                         response = DM_RESP_ABORT;
243                         goto done;
244                 }
245                 ofs += ret;
246         }
247         close(fd);
248
249         /* remove the attribute from the file - it is now fully recalled */
250         ret = dm_remove_dmattr(dmapi.sid, hanp, hlen, token, 0, &attrname);
251         if (ret != 0) {
252                 printf("dm_remove_dmattr failed - %s\n", strerror(errno));
253                 retcode = EIO;
254                 response = DM_RESP_ABORT;
255                 goto done;
256         }
257
258         /* remove the store file */
259         ret = hsm_store_unlink(h.device, h.inode);
260         if (ret != 0) {
261                 printf("WARNING: Failed to unlink store file\n");
262         }
263
264         /* remove the managed region from the file */
265         ret = dm_set_region(dmapi.sid, hanp, hlen, token, 0, NULL, &exactFlag);
266         if (ret == -1) {
267                 printf("failed dm_set_region - %s\n", strerror(errno));
268                 retcode = EIO;
269                 response = DM_RESP_ABORT;
270                 goto done;
271         }
272
273 done:
274         /* tell the kernel that the event has been handled */
275         ret = dm_respond_event(dmapi.sid, msg->ev_token, 
276                                response, retcode, 0, NULL);
277         if (ret != 0) {
278                 printf("Failed to respond to read event\n");
279                 exit(1);
280         }
281 }
282
283
284 /*
285   called on a DM_EVENT_DESTROY event, when a file is being deleted
286  */
287 static void hsm_handle_destroy(dm_eventmsg_t *msg)
288 {
289         dm_destroy_event_t *ev;
290         void *hanp;
291         size_t hlen, rlen;
292         int ret;
293         dm_attrname_t attrname;
294         dm_token_t token = msg->ev_token;
295         struct hsm_attr h;
296         dm_right_t right;
297         dm_response_t response = DM_RESP_CONTINUE;
298         int retcode = 0;
299         dm_boolean_t exactFlag;
300
301         ev = DM_GET_VALUE(msg, ev_data, dm_destroy_event_t *);
302         hanp = DM_GET_VALUE(ev, ds_handle, void *);
303         hlen = DM_GET_LEN(ev, ds_handle);
304
305         if (DM_TOKEN_EQ(token, DM_INVALID_TOKEN)) {
306                 goto done;
307         }
308
309         /* make sure we have an exclusive lock on the file */
310         ret = dm_query_right(dmapi.sid, hanp, hlen, token, &right);
311         if (ret != 0 && errno != ENOENT) {
312                 printf("dm_query_right failed - %s\n", strerror(errno));
313                 retcode = EIO;
314                 response = DM_RESP_ABORT;
315                 goto done;
316         }
317
318         if (right != DM_RIGHT_EXCL || errno == ENOENT) {
319                 ret = dm_request_right(dmapi.sid, hanp, hlen, token, DM_RR_WAIT, DM_RIGHT_EXCL);
320                 if (ret != 0) {
321                         printf("dm_request_right failed - %s\n", strerror(errno));
322                         retcode = EIO;
323                         response = DM_RESP_ABORT;
324                         goto done;
325                 }
326         }
327
328         memset(attrname.an_chars, 0, DM_ATTR_NAME_SIZE);
329         strncpy((char*)attrname.an_chars, HSM_ATTRNAME, DM_ATTR_NAME_SIZE);
330
331         /* get the attribute and check it is valid. This is just
332            paranoia really, as the file is going away */
333         ret = dm_get_dmattr(dmapi.sid, hanp, hlen, token, &attrname, 
334                             sizeof(h), &h, &rlen);
335         if (ret != 0) {
336                 printf("WARNING: dm_get_dmattr failed - %s\n", strerror(errno));
337                 goto done;
338         }
339
340         if (rlen != sizeof(h)) {
341                 printf("hsm_handle_read - bad attribute size %d\n", (int)rlen);
342                 retcode = EIO;
343                 response = DM_RESP_ABORT;
344                 goto done;
345         }
346
347         if (strncmp(h.magic, HSM_MAGIC, sizeof(h.magic)) != 0) {
348                 printf("Bad magic '%*.*s'\n", (int)sizeof(h.magic), (int)sizeof(h.magic), h.magic);
349                 retcode = EIO;
350                 response = DM_RESP_ABORT;
351                 goto done;
352         }
353
354         if (options.debug > 1) {
355                 printf("%s: Destroying file %llx:%llx of size %d\n", 
356                        dmapi_event_string(msg->ev_type),
357                        (unsigned long long)h.device, (unsigned long long)h.inode,
358                        (int)h.size);
359         }
360
361         /* remove the store file */
362         ret = hsm_store_unlink(h.device, h.inode);
363         if (ret == -1) {
364                 printf("WARNING: Failed to unlink store file for file 0x%llx:0x%llx\n",
365                        (unsigned long long)h.device, (unsigned long long)h.inode);
366         }
367
368         /* remove the attribute */
369         ret = dm_remove_dmattr(dmapi.sid, hanp, hlen, token, 0, &attrname);
370         if (ret != 0) {
371                 printf("dm_remove_dmattr failed - %s\n", strerror(errno));
372                 retcode = EIO;
373                 response = DM_RESP_ABORT;
374                 goto done;
375         }
376
377         /* and clear the managed region */
378         ret = dm_set_region(dmapi.sid, hanp, hlen, token, 0, NULL, &exactFlag);
379         if (ret == -1) {
380                 printf("WARNING: failed dm_set_region - %s\n", strerror(errno));
381         }
382
383 done:
384         /* only respond if the token is real */
385         if (!DM_TOKEN_EQ(msg->ev_token,DM_NO_TOKEN) &&
386             !DM_TOKEN_EQ(msg->ev_token, DM_INVALID_TOKEN)) {
387                 ret = dm_respond_event(dmapi.sid, msg->ev_token, 
388                                        response, retcode, 0, NULL);
389                 if (ret != 0) {
390                         printf("Failed to respond to destroy event\n");
391                         exit(1);
392                 }
393         }
394 }
395
396 /*
397   main switch for DMAPI messages
398  */
399 static void hsm_handle_message(dm_eventmsg_t *msg)
400 {
401         switch (msg->ev_type) {
402         case DM_EVENT_MOUNT:
403                 hsm_handle_mount(msg);
404                 break;
405         case DM_EVENT_READ:
406         case DM_EVENT_WRITE:
407                 hsm_handle_recall(msg);
408                 break;
409         case DM_EVENT_DESTROY:
410                 hsm_handle_destroy(msg);
411                 break;
412         default:
413                 if (!DM_TOKEN_EQ(msg->ev_token,DM_NO_TOKEN) &&
414                     !DM_TOKEN_EQ(msg->ev_token, DM_INVALID_TOKEN)) {
415                         printf("Giving default response\n");
416                         int ret = dm_respond_event(dmapi.sid, msg->ev_token, 
417                                                DM_RESP_CONTINUE, 0, 0, NULL);
418                         if (ret != 0) {
419                                 printf("Failed to respond to mount event\n");
420                                 exit(1);
421                         }
422                 }
423                 break;
424         }
425 }
426
427 /*
428   wait for DMAPI events to come in and dispatch them
429  */
430 static void hsm_wait_events(void)
431 {
432         int ret;
433         char buf[0x10000];
434         size_t rlen;
435
436         printf("Waiting for events\n");
437         
438         while (1) {
439                 dm_eventmsg_t *msg;
440                 if (options.blocking_wait) {
441                         ret = dm_get_events(dmapi.sid, 0, DM_EV_WAIT, sizeof(buf), buf, &rlen);
442                 } else {
443                         /* optionally don't use DM_RR_WAIT to ensure
444                            that the daemon can be killed. This is only
445                            needed because GPFS uses an uninterruptible
446                            sleep for dm_get_events with DM_EV_WAIT. It
447                            should be an interruptible sleep */
448                         msleep(10);
449                         ret = dm_get_events(dmapi.sid, 0, 0, sizeof(buf), buf, &rlen);
450                 }
451                 if (ret < 0) {
452                         if (errno == EAGAIN) continue;
453                         if (errno == ESTALE) {
454                                 printf("DMAPI service has shutdown - restarting\n");
455                                 hsm_init();
456                                 continue;
457                         }
458                         printf("Failed to get event (%s)\n", strerror(errno));
459                         exit(1);
460                 }
461
462                 /* loop over all the messages we received */
463                 for (msg=(dm_eventmsg_t *)buf; 
464                      msg; 
465                      msg = DM_STEP_TO_NEXT(msg, dm_eventmsg_t *)) {
466                         /* optionally fork on each message, thus
467                            giving parallelism and allowing us to delay
468                            recalls, simulating slow tape speeds */
469                         if (options.use_fork) {
470                                 if (fork() != 0) continue;
471                                 srandom(getpid() ^ time(NULL));
472                                 hsm_handle_message(msg);
473                                 _exit(0);
474                         } else {
475                                 hsm_handle_message(msg);
476                         }
477                 }
478         }
479 }
480
481 /*
482   on startup we look for partially completed events from an earlier
483   instance of hacksmd, and continue them if we can
484  */
485 static void hsm_cleanup_events(void)
486 {
487         char buf[0x1000];
488         size_t rlen;
489         dm_token_t *tok = NULL;
490         u_int n = 0;
491         int ret, i;
492
493         while (1) {
494                 u_int n2;
495                 ret = dm_getall_tokens(dmapi.sid, n, tok, &n2);
496                 if (ret == -1 && errno == E2BIG) {
497                         n = n2;
498                         tok = realloc(tok, sizeof(dm_token_t)*n);
499                         continue;
500                 }
501                 if (ret == -1) {
502                         printf("dm_getall_tokens - %s\n", strerror(errno));
503                         return;
504                 }
505                 if (ret == 0 && n2 == 0) {
506                         break;
507                 }
508                 printf("Cleaning up %u tokens\n", n2);
509                 for (i=0;i<n2;i++) {
510                         dm_eventmsg_t *msg;
511                         /* get the message associated with this token
512                            back from the kernel */
513                         ret = dm_find_eventmsg(dmapi.sid, tok[i], sizeof(buf), buf, &rlen);
514                         if (ret == -1) {
515                                 printf("Unable to find message for token in cleanup\n");
516                                 continue;
517                         }
518                         msg = (dm_eventmsg_t *)buf;
519                         /* there seems to be a bug where GPFS
520                            sometimes gives us a garbage token here */
521                         if (!DM_TOKEN_EQ(tok[i], msg->ev_token)) {
522                                 printf("Message token mismatch in cleanup\n");
523                                 dm_respond_event(dmapi.sid, tok[i], 
524                                                  DM_RESP_ABORT, EINTR, 0, NULL);
525                         } else {
526                                 unsigned saved_delay = options.recall_delay;
527                                 hsm_handle_message(msg);
528                                 options.recall_delay = saved_delay;
529                         }
530                 }
531         }
532         if (tok) free(tok);
533 }
534
535 /*
536   show program usage
537  */
538 static void usage(void)
539 {
540         printf("Usage: hacksmd <options>\n");
541         printf("\n\tOptions:\n");
542         printf("\t\t -c                 cleanup lost tokens\n");
543         printf("\t\t -N                 use a non-blocking event wait\n");
544         printf("\t\t -d level           choose debug level\n");
545         printf("\t\t -F                 fork to handle each event\n");
546         printf("\t\t -R delay           set a random delay on recall up to 'delay' seconds\n");
547         exit(0);
548 }
549
550 /* main code */
551 int main(int argc, char * const argv[])
552 {
553         int opt;
554         bool cleanup = false;
555
556         /* parse command-line options */
557         while ((opt = getopt(argc, argv, "chNd:FR:")) != -1) {
558                 switch (opt) {
559                 case 'c':
560                         cleanup = true;
561                         break;
562                 case 'd':
563                         options.debug = strtoul(optarg, NULL, 0);
564                         break;
565                 case 'R':
566                         options.recall_delay = strtoul(optarg, NULL, 0);
567                         break;
568                 case 'N':
569                         options.blocking_wait = false;
570                         break;
571                 case 'F':
572                         options.use_fork = true;
573                         break;
574                 case 'h':
575                 default:
576                         usage();
577                         break;
578                 }
579         }
580
581         setlinebuf(stdout);     
582
583         argv += optind;
584         argc -= optind;
585
586         signal(SIGCHLD, SIG_IGN);
587
588         signal(SIGTERM, hsm_term_handler);
589         signal(SIGINT, hsm_term_handler);
590
591         hsm_init();
592
593         if (cleanup) {
594                 hsm_cleanup_tokens(dmapi.sid, DM_RESP_ABORT, EINTR);
595                 return 0;
596         }
597
598         hsm_cleanup_events();
599
600         hsm_wait_events();
601
602         return 0;
603 }