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