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