oops...checked in some temporary changes from testing on ia64 box.
[xfstests-dev.git] / dmapi / src / sample_hsm / migout.c
1 /*
2  * Simple utility to migrate a group of specified files.
3  * The unsorted input is from migfind, and is of the form:
4  *      filehandle length       filehandle      file size
5  *
6  * The data for each file will be stored in another file located
7  * in a different directory. This 'staging directory' should be on
8  * another filesystem. The staging file will be named the same as
9  * the file handle. This simple-minded scheme suffices, since we're
10  * not interested in showing any media management in this example.
11  *
12  * ASSUMPTIONS:
13  *      Persistent managed regions are supported
14  *      Persistent DM attributes are supported
15  *
16  *
17  * This code was written by Peter Lawthers, and placed in the public
18  * domain for use by DMAPI implementors and app writers.
19  *
20  * Standard disclaimer:
21  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/stat.h>
37
38 #include <unistd.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include <fcntl.h>
42
43 #include <lib/hsm.h>
44
45 extern char     *optarg;
46 extern int       optind, optopt, opterr;
47 char            *Progname;
48 int              Verbose;
49
50 int     mig_files(dm_sessid_t, char *);
51 int     mk_nonrez(dm_sessid_t, void *, size_t, dm_token_t, dm_off_t);
52 int     set_mrgns(dm_sessid_t, void *, size_t, dm_token_t, dm_off_t,
53                   dm_off_t *);
54 void    clear_mrgns(dm_sessid_t, void *, dm_size_t, dm_token_t);
55 int     lock_file(dm_sessid_t, void *, size_t, dm_right_t, dm_token_t *);
56 void    unlock_file(dm_sessid_t, dm_token_t);
57 int     get_dmchange(dm_sessid_t, void *, size_t, dm_token_t, u_int *);
58 int     create_stgfile(char *, void *, size_t, char *, int *);
59 int     setup_dmapi(dm_sessid_t *);
60 int     save_filedata(dm_sessid_t, void *, size_t, int, dm_size_t);
61 int     extract_fields(char *, char *, size_t *, dm_size_t *);
62 int     save_dataloc(dm_sessid_t, void *, size_t, dm_token_t, char *);
63
64 void    usage(char *);
65
66 void
67 usage(
68         char *prog)
69 {
70         fprintf(stderr, "Usage: %s ", prog);
71         fprintf(stderr, " <-v verbose> ");
72         fprintf(stderr, "<staging directory>\n");
73 }
74
75
76 int
77 main(
78         int     argc,
79         char    *argv[])
80 {
81
82         int              c;
83         int              error;
84         char            *stage_dir;
85         dm_sessid_t      sid;
86
87
88         error     = 0;
89         Progname  = argv[0];
90         stage_dir = NULL;
91
92         while ((c = getopt(argc, argv, "v")) != EOF) {
93                 switch (c) {
94                 case 'v':
95                         Verbose++;
96                         break;
97
98                 case '?':
99                 default:
100                         usage(Progname);
101                         exit(1);
102                 }
103         }
104         if (optind >= argc) {
105                 usage(Progname);
106                 exit(1);
107         }
108         stage_dir = argv[optind];
109         if (stage_dir == NULL) {
110                 usage(Progname);
111                 exit(1);
112         }
113
114         /*
115          * Init the dmapi, and get a session.
116          */
117         error = setup_dmapi(&sid);
118         if (error)
119                 exit(1);
120
121         /*
122          * Migrate all the files given to us via stdin
123          */
124         error = mig_files(sid, stage_dir);
125
126
127         if (dm_destroy_session(sid))
128                 errno_msg("Can't shut down session, line=%d, errno=%d", __LINE__, errno);
129
130         return(error);
131 }
132
133 /*
134  * Migrate all the files given in stdin
135  */
136
137 int
138 mig_files(
139         dm_sessid_t      sid,
140         char            *stage_dir)
141 {
142         void            *hanp;
143         size_t           hlen;
144         dm_size_t        fsize;
145         int              error;
146         u_int            change_start, change_end;
147         int              stg_fd;                /* staging file descriptor */
148         dm_off_t         off;                   /* starting offset */
149         dm_token_t       token;                 /* file token */
150         char             ibuf[IBUFSIZE];
151         char             handle_buf[HANDLE_LEN];
152         char             stgpath[MAXPATHLEN];
153
154         /*
155          * Read all the lines in std input and migrate each file.
156          * This simple-minded migout does no batching, sorting, or
157          * anything else that a real HSM might do.
158          */
159         while (fgets(ibuf, IBUFSIZE, stdin) != NULL) {
160                 error = extract_fields(ibuf, handle_buf, &hlen, &fsize);
161                 if (error) {
162                         err_msg("%s/%d: mangled input line, '%s' ", __FILE__, __LINE__, ibuf);
163                         continue;
164                 }
165                 hanp = (void *)handle_buf;
166                 if (Verbose) {
167                         print_handle(hanp, hlen);
168                 }
169
170                 /*
171                  * Create and open the file in the staging directory.
172                  */
173                 error = create_stgfile(stage_dir, hanp, hlen, stgpath, &stg_fd);
174                 if (error)
175                         continue;
176
177                 /*
178                  * Get the file's DMAPI change indicator so that we
179                  * can tell if it changed (either via a data mod, or
180                  * a DM attribute update) while we are staging it out
181                  */
182                 error = get_dmchange(sid, hanp, hlen, DM_NO_TOKEN,
183                                         &change_start);
184                 if (error) {
185                         close(stg_fd);
186                         continue;
187                 }
188
189                 /*
190                  * Write all the file's data to our file in the
191                  * staging directory. In a real HSM, the data would
192                  * be copied off to tertiary storage somewhere.
193                  *
194                  * The staging file descriptor will be closed for us
195                  * in all cases.
196                  */
197                 error = save_filedata(sid, hanp, hlen, stg_fd, fsize);
198                 if (error)
199                         continue;
200
201
202                 /*
203                  * Get exclusive access to the file so we can blow
204                  * away its data
205                  */
206                 error = lock_file(sid, hanp, hlen, DM_RIGHT_EXCL, &token);
207                 if (error) {
208                         err_msg("Can't get exclusive access to file, ignoring");
209                         continue;
210                 }
211
212                 /*
213                  * Make sure the file did not change
214                  */
215                 error = get_dmchange(sid, hanp, hlen, token, &change_end);
216                 if (error) {
217                         unlock_file(sid, token);
218                         continue;
219                 }
220                 if (change_start != change_end) {
221                         unlock_file(sid, token);
222                         err_msg("File changed during stageout, ignoring");
223                         continue;
224                 }
225
226                 /*
227                  * Save the location of the data (the staging file)
228                  * in a private DM attribute so that we can get the
229                  * file back in the future
230                  */
231                 error = save_dataloc(sid, hanp, hlen, token, stgpath);
232                 if (error) {
233                         err_msg("Can't save location of file data");
234                         unlock_file(sid, token);
235                         continue;
236                 }
237
238
239                 /*
240                  * Set up the managed regions on the file so that
241                  * a foray past the fencepost will cause an event to
242                  * be generated.
243                  */
244                 error = set_mrgns(sid, hanp, hlen, token, fsize, &off);
245                 if (error) {
246                         unlock_file(sid, token);
247                         err_msg("Can't set managed regions");
248                         continue;
249                 }
250
251                 /*
252                  * Now we can safely blow away the data.
253                  */
254                 error = mk_nonrez(sid, hanp, hlen, token, off);
255                 if (error)  {
256                         clear_mrgns(sid, hanp, hlen, token);
257                 }
258
259                 /*
260                  * Unlock the file, which releases the token
261                  */
262                 unlock_file(sid, token);
263
264         }
265
266         return(0);
267 }
268
269
270 /*
271  * Remove the data for a file
272  */
273 int
274 mk_nonrez(
275         dm_sessid_t      sid,
276         void            *hanp,
277         size_t           hlen,
278         dm_token_t       token,
279         dm_off_t         off)
280 {
281         int     error;
282
283         error = dm_punch_hole(sid, hanp, hlen, token, off, 0);
284         if (error == -1) {
285                 errno_msg("Can't punch hole in file, line=%d, errno=%d", __LINE__, errno);
286                 return(1);
287         }
288         return(0);
289 }
290
291
292 /*
293  * Set up the managed regions on a file. We try to leave some of the
294  * file resident; the actual amount left on-disk is dependent
295  * on the rounding enforced by the DMAPI implementation.
296  */
297 int
298 set_mrgns(
299         dm_sessid_t      sid,
300         void            *hanp,
301         size_t           hlen,
302         dm_token_t       token,
303         dm_off_t         fsize,
304         dm_off_t        *start_off)
305 {
306         dm_off_t        rroff;
307         dm_size_t       rlenp;
308         dm_region_t     rgn;
309         u_int           exact_flag;
310         int             error;
311
312         if (fsize > FENCEPOST_SIZE) {
313                 error = dm_probe_hole(sid, hanp, hlen, token, FENCEPOST_SIZE, 0,
314                                         &rroff, &rlenp);
315                 if (error == -1) {
316                         errno_msg("Can't probe hole in file, line=%d, errno=%d", __LINE__, errno);
317                         return(1);
318                 }
319         } else {
320                 rroff = 0;
321         }
322         *start_off = rroff;
323
324         /*
325          * Now we know what the DMAPI and filesystem will support with
326          * respect to rounding of holes. We try to set our managed region
327          * to begin at this offset and continue to the end of the file.
328          * We set the event mask so that we'll trap on all events that
329          * occur in the managed region.
330          *
331          * Note that some implementations may not be able to support
332          * a managed region that starts someplace other than the beginning
333          * of the file. If we really cared, we could check the exact_flag.
334          */
335         rgn.rg_offset = rroff;
336         rgn.rg_size   = 0;
337         rgn.rg_flags  = DM_REGION_READ | DM_REGION_WRITE | DM_REGION_TRUNCATE;
338         error = dm_set_region(sid, hanp, hlen, token, 1, &rgn, &exact_flag);
339         if (error == -1) {
340                 errno_msg("Can't set managed region, line=%d, errno=%d", __LINE__, errno);
341                 return(1);
342         }
343         return(0);
344 }
345
346
347 /*
348  * Clear a managed region on a file
349  */
350 void
351 clear_mrgns(
352         dm_sessid_t     sid,
353         void            *hanp,
354         dm_size_t       hlen,
355         dm_token_t      token)
356 {
357         dm_region_t     rgn;
358         u_int           exact_flag;
359         int             error;
360
361         rgn.rg_offset = 0;
362         rgn.rg_size = 0;
363         rgn.rg_flags = DM_REGION_NOEVENT;
364         error = dm_set_region(sid, hanp, hlen, token, 1, &rgn, &exact_flag);
365         if (error)
366                 errno_msg("Can't clear managed regions from file, line=%d, errno=%d", __LINE__, errno);
367
368         return;
369 }
370
371
372 /*
373  * File rights are accessed via a token. The token must be associated
374  * with a synchronous event message. So, to obtain either shared or
375  * exclusive rights to a file, we first associate a token with a message,
376  * and then request our desired right
377  */
378 int
379 lock_file(
380         dm_sessid_t      sid,
381         void            *hanp,
382         size_t           hlen,
383         dm_right_t       right,
384         dm_token_t      *token)
385 {
386         int     error;
387
388         error = dm_create_userevent(sid,  (size_t)0, (void *)0, token);
389         if (error == -1) {
390                 errno_msg("Can't create user event for token context, line=%d, errno=%d", __LINE__, errno);
391                 return(1);
392         }
393         error = dm_request_right(sid, hanp, hlen, *token, DM_RR_WAIT, right);
394         if (error == -1) {
395                 errno_msg("Can't get requested right for token, line=%d, errno=%d", __LINE__, errno);
396                 return(1);
397         }
398         return(0);
399 }
400
401
402 /*
403  * Release the lock on a file
404  */
405 void
406 unlock_file(
407         dm_sessid_t      sid,
408         dm_token_t       token)
409 {
410         int     error;
411
412         error = dm_respond_event(sid, token, DM_RESP_CONTINUE, 0, 0, 0);
413         if (error == -1)
414                 errno_msg("Can't respond to event and release token, line=%d, errno=%d", __LINE__, errno);
415
416         return;
417 }
418
419
420 int
421 create_stgfile(
422         char    *stage_dir,
423         void    *hanp,
424         size_t   hlen,
425         char    *path,
426         int     *stg_fd)
427 {
428         char    handle_str[HANDLE_STR];
429
430         if (hlen > HANDLE_LEN) {
431                 err_msg("Handle length (%d) too long for file", hlen);
432                 return(1);
433         }
434
435         strcpy(path, stage_dir);
436         strcat(path, "/");
437         hantoa(hanp, hlen, handle_str);
438
439         /*
440          * Concat the ascii representation of the file handle
441          * (which is two times longer than the binary version)
442          * onto the staging path name
443          */
444         strncat(path, (char *)handle_str, hlen*2);
445
446         if ( (*stg_fd = open(path, O_RDWR | O_CREAT, 0644)) < 0) {
447                 errno_msg("Can't open file %s, line=%d, errno=%d\n", path, __LINE__, errno);
448                 return(1);
449         }
450
451         return(0);
452 }
453
454
455 /*
456  * Extract the three fields from the input line. THe input is of
457  * the form
458  *      filehandle length       filehandle      file size
459  *
460  * The length of the file handle is expected to be less than 64 bytes.
461  */
462 int
463 extract_fields(
464         char            *ibuf,
465         char            *handle_buf,
466         size_t          *hlen,
467         dm_size_t       *fsize)
468 {
469         char    *cp, *start;
470         size_t   len;
471         char *hanp;
472
473         /*
474          * Skip any leading white space, and check the length
475          * of the file handle
476          */
477         cp = ibuf;
478         while (!isalnum(*cp))
479                 cp++;
480
481         start = cp;
482         while (isalnum(*cp))
483                 cp++;
484         *cp = '\0';
485
486         len = strtol(start, 0, 0);
487         if (len > HANDLE_LEN) {
488                 err_msg("%s/%d: Handle length %d too long in input line", __FILE__, __LINE__, len);
489                 return(1);
490         }
491
492         *hlen = len;
493
494         /*
495          * Skip over white space, and extract the file handle
496          */
497         while (!isalnum(*cp))
498                 cp++;
499         hanp = cp;
500
501         /*
502          * Skip over the ascii length of the file handle, and
503          * then extract the file's length
504          */
505         cp += len*2;
506         *cp = '\0';
507
508         atohan( hanp, (void**)&handle_buf, &len );
509
510
511         /* skip over white space */
512         while (!isalnum(*cp))
513                 cp++;
514
515         /* read file length */
516         start = cp;
517         while (isalnum(*cp))
518                 cp++;
519         *cp = '\0';
520
521         *fsize = strtol(start, 0, 0);
522
523         return(0);
524
525 }
526
527
528 /*
529  * Save the location of the file's data so that we can find
530  * it again when we're staging the file back in. Rather than store
531  * the full pathname of the staging file, we just store the handle.
532  * This means the staging dir must be on a filesystem that supports
533  * the DMAPI.
534  */
535 int
536 save_dataloc(
537         dm_sessid_t      sid,
538         void            *hanp,
539         size_t           hlen,
540         dm_token_t       token,
541         char            *stgpath)
542 {
543         void            *stg_hanp;
544         size_t           stg_hlen;
545         int              error;
546         dm_attrname_t    hanp_attrname;
547         dm_attrname_t    hlen_attrname;
548
549         if (dm_path_to_handle(stgpath, &stg_hanp, &stg_hlen) == -1) {
550                 errno_msg("Can't get handle for path %s, line=%d, errno=%d", stgpath, __LINE__, errno);
551                 return(1);
552         }
553
554         /*
555          * Since handles are in two parts, they become two attributes.
556          * This can be useful, since we can extract the length
557          * separately when we stage the file back in
558          */
559         memcpy((void *)&hanp_attrname.an_chars[0], DLOC_HAN, DM_ATTR_NAME_SIZE);
560         error = dm_set_dmattr(sid, hanp, hlen, token, &hanp_attrname,
561                                 0, stg_hlen, stg_hanp);
562         if (error == -1) {
563                 errno_msg("Can't set DM attr of staging filehandle, line=%d, errno=%d",__LINE__, errno);
564                 return(1);
565         }
566
567         memcpy((void *)&hlen_attrname.an_chars[0], DLOC_HANLEN, DM_ATTR_NAME_SIZE);
568         error = dm_set_dmattr(sid, hanp, hlen, token, &hlen_attrname,
569                                 0, sizeof(stg_hlen), (void *)&stg_hlen);
570         if (error == -1) {
571                 errno_msg("Can't set DM attr of staging filehandle length, line=%d, errno=%d", __LINE__, errno);
572                 return(1);
573         }
574         return(0);
575 }