| /* |
| * Simple utility to migrate a group of specified files. |
| * The unsorted input is from migfind, and is of the form: |
| * filehandle length filehandle file size |
| * |
| * The data for each file will be stored in another file located |
| * in a different directory. This 'staging directory' should be on |
| * another filesystem. The staging file will be named the same as |
| * the file handle. This simple-minded scheme suffices, since we're |
| * not interested in showing any media management in this example. |
| * |
| * ASSUMPTIONS: |
| * Persistent managed regions are supported |
| * Persistent DM attributes are supported |
| * |
| * |
| * This code was written by Peter Lawthers, and placed in the public |
| * domain for use by DMAPI implementors and app writers. |
| * |
| * Standard disclaimer: |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <fcntl.h> |
| |
| #include <lib/hsm.h> |
| |
| extern char *optarg; |
| extern int optind, optopt, opterr; |
| char *Progname; |
| int Verbose; |
| |
| int mig_files(dm_sessid_t, char *); |
| int mk_nonrez(dm_sessid_t, void *, size_t, dm_token_t, dm_off_t); |
| int set_mrgns(dm_sessid_t, void *, size_t, dm_token_t, dm_off_t, |
| dm_off_t *); |
| void clear_mrgns(dm_sessid_t, void *, dm_size_t, dm_token_t); |
| int lock_file(dm_sessid_t, void *, size_t, dm_right_t, dm_token_t *); |
| void unlock_file(dm_sessid_t, dm_token_t); |
| int get_dmchange(dm_sessid_t, void *, size_t, dm_token_t, u_int *); |
| int create_stgfile(char *, void *, size_t, char *, int *); |
| int setup_dmapi(dm_sessid_t *); |
| int save_filedata(dm_sessid_t, void *, size_t, int, dm_size_t); |
| int extract_fields(char *, char *, size_t *, dm_size_t *); |
| int save_dataloc(dm_sessid_t, void *, size_t, dm_token_t, char *); |
| |
| void usage(char *); |
| |
| void |
| usage( |
| char *prog) |
| { |
| fprintf(stderr, "Usage: %s ", prog); |
| fprintf(stderr, " <-v verbose> "); |
| fprintf(stderr, "<staging directory>\n"); |
| } |
| |
| |
| int |
| main( |
| int argc, |
| char *argv[]) |
| { |
| |
| int c; |
| int error; |
| char *stage_dir; |
| dm_sessid_t sid; |
| |
| |
| error = 0; |
| Progname = argv[0]; |
| stage_dir = NULL; |
| |
| while ((c = getopt(argc, argv, "v")) != EOF) { |
| switch (c) { |
| case 'v': |
| Verbose++; |
| break; |
| |
| case '?': |
| default: |
| usage(Progname); |
| exit(1); |
| } |
| } |
| if (optind >= argc) { |
| usage(Progname); |
| exit(1); |
| } |
| stage_dir = argv[optind]; |
| if (stage_dir == NULL) { |
| usage(Progname); |
| exit(1); |
| } |
| |
| /* |
| * Init the dmapi, and get a session. |
| */ |
| error = setup_dmapi(&sid); |
| if (error) |
| exit(1); |
| |
| /* |
| * Migrate all the files given to us via stdin |
| */ |
| error = mig_files(sid, stage_dir); |
| |
| |
| if (dm_destroy_session(sid)) |
| errno_msg("Can't shut down session, line=%d, errno=%d", __LINE__, errno); |
| |
| return(error); |
| } |
| |
| /* |
| * Migrate all the files given in stdin |
| */ |
| |
| int |
| mig_files( |
| dm_sessid_t sid, |
| char *stage_dir) |
| { |
| void *hanp; |
| size_t hlen; |
| dm_size_t fsize; |
| int error; |
| u_int change_start, change_end; |
| int stg_fd; /* staging file descriptor */ |
| dm_off_t off; /* starting offset */ |
| dm_token_t token; /* file token */ |
| char ibuf[IBUFSIZE]; |
| char handle_buf[HANDLE_LEN]; |
| char stgpath[MAXPATHLEN]; |
| |
| /* |
| * Read all the lines in std input and migrate each file. |
| * This simple-minded migout does no batching, sorting, or |
| * anything else that a real HSM might do. |
| */ |
| while (fgets(ibuf, IBUFSIZE, stdin) != NULL) { |
| error = extract_fields(ibuf, handle_buf, &hlen, &fsize); |
| if (error) { |
| err_msg("%s/%d: mangled input line, '%s' ", __FILE__, __LINE__, ibuf); |
| continue; |
| } |
| hanp = (void *)handle_buf; |
| if (Verbose) { |
| print_handle(hanp, hlen); |
| } |
| |
| /* |
| * Create and open the file in the staging directory. |
| */ |
| error = create_stgfile(stage_dir, hanp, hlen, stgpath, &stg_fd); |
| if (error) |
| continue; |
| |
| /* |
| * Get the file's DMAPI change indicator so that we |
| * can tell if it changed (either via a data mod, or |
| * a DM attribute update) while we are staging it out |
| */ |
| error = get_dmchange(sid, hanp, hlen, DM_NO_TOKEN, |
| &change_start); |
| if (error) { |
| close(stg_fd); |
| continue; |
| } |
| |
| /* |
| * Write all the file's data to our file in the |
| * staging directory. In a real HSM, the data would |
| * be copied off to tertiary storage somewhere. |
| * |
| * The staging file descriptor will be closed for us |
| * in all cases. |
| */ |
| error = save_filedata(sid, hanp, hlen, stg_fd, fsize); |
| if (error) |
| continue; |
| |
| |
| /* |
| * Get exclusive access to the file so we can blow |
| * away its data |
| */ |
| error = lock_file(sid, hanp, hlen, DM_RIGHT_EXCL, &token); |
| if (error) { |
| err_msg("Can't get exclusive access to file, ignoring"); |
| continue; |
| } |
| |
| /* |
| * Make sure the file did not change |
| */ |
| error = get_dmchange(sid, hanp, hlen, token, &change_end); |
| if (error) { |
| unlock_file(sid, token); |
| continue; |
| } |
| if (change_start != change_end) { |
| unlock_file(sid, token); |
| err_msg("File changed during stageout, ignoring"); |
| continue; |
| } |
| |
| /* |
| * Save the location of the data (the staging file) |
| * in a private DM attribute so that we can get the |
| * file back in the future |
| */ |
| error = save_dataloc(sid, hanp, hlen, token, stgpath); |
| if (error) { |
| err_msg("Can't save location of file data"); |
| unlock_file(sid, token); |
| continue; |
| } |
| |
| |
| /* |
| * Set up the managed regions on the file so that |
| * a foray past the fencepost will cause an event to |
| * be generated. |
| */ |
| error = set_mrgns(sid, hanp, hlen, token, fsize, &off); |
| if (error) { |
| unlock_file(sid, token); |
| err_msg("Can't set managed regions"); |
| continue; |
| } |
| |
| /* |
| * Now we can safely blow away the data. |
| */ |
| error = mk_nonrez(sid, hanp, hlen, token, off); |
| if (error) { |
| clear_mrgns(sid, hanp, hlen, token); |
| } |
| |
| /* |
| * Unlock the file, which releases the token |
| */ |
| unlock_file(sid, token); |
| |
| } |
| |
| return(0); |
| } |
| |
| |
| /* |
| * Remove the data for a file |
| */ |
| int |
| mk_nonrez( |
| dm_sessid_t sid, |
| void *hanp, |
| size_t hlen, |
| dm_token_t token, |
| dm_off_t off) |
| { |
| int error; |
| |
| error = dm_punch_hole(sid, hanp, hlen, token, off, 0); |
| if (error == -1) { |
| errno_msg("Can't punch hole in file, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| return(0); |
| } |
| |
| |
| /* |
| * Set up the managed regions on a file. We try to leave some of the |
| * file resident; the actual amount left on-disk is dependent |
| * on the rounding enforced by the DMAPI implementation. |
| */ |
| int |
| set_mrgns( |
| dm_sessid_t sid, |
| void *hanp, |
| size_t hlen, |
| dm_token_t token, |
| dm_off_t fsize, |
| dm_off_t *start_off) |
| { |
| dm_off_t rroff; |
| dm_size_t rlenp; |
| dm_region_t rgn; |
| u_int exact_flag; |
| int error; |
| |
| if (fsize > FENCEPOST_SIZE) { |
| error = dm_probe_hole(sid, hanp, hlen, token, FENCEPOST_SIZE, 0, |
| &rroff, &rlenp); |
| if (error == -1) { |
| errno_msg("Can't probe hole in file, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| } else { |
| rroff = 0; |
| } |
| *start_off = rroff; |
| |
| /* |
| * Now we know what the DMAPI and filesystem will support with |
| * respect to rounding of holes. We try to set our managed region |
| * to begin at this offset and continue to the end of the file. |
| * We set the event mask so that we'll trap on all events that |
| * occur in the managed region. |
| * |
| * Note that some implementations may not be able to support |
| * a managed region that starts someplace other than the beginning |
| * of the file. If we really cared, we could check the exact_flag. |
| */ |
| rgn.rg_offset = rroff; |
| rgn.rg_size = 0; |
| rgn.rg_flags = DM_REGION_READ | DM_REGION_WRITE | DM_REGION_TRUNCATE; |
| error = dm_set_region(sid, hanp, hlen, token, 1, &rgn, &exact_flag); |
| if (error == -1) { |
| errno_msg("Can't set managed region, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| return(0); |
| } |
| |
| |
| /* |
| * Clear a managed region on a file |
| */ |
| void |
| clear_mrgns( |
| dm_sessid_t sid, |
| void *hanp, |
| dm_size_t hlen, |
| dm_token_t token) |
| { |
| dm_region_t rgn; |
| u_int exact_flag; |
| int error; |
| |
| rgn.rg_offset = 0; |
| rgn.rg_size = 0; |
| rgn.rg_flags = DM_REGION_NOEVENT; |
| error = dm_set_region(sid, hanp, hlen, token, 1, &rgn, &exact_flag); |
| if (error) |
| errno_msg("Can't clear managed regions from file, line=%d, errno=%d", __LINE__, errno); |
| |
| return; |
| } |
| |
| |
| /* |
| * File rights are accessed via a token. The token must be associated |
| * with a synchronous event message. So, to obtain either shared or |
| * exclusive rights to a file, we first associate a token with a message, |
| * and then request our desired right |
| */ |
| int |
| lock_file( |
| dm_sessid_t sid, |
| void *hanp, |
| size_t hlen, |
| dm_right_t right, |
| dm_token_t *token) |
| { |
| int error; |
| |
| error = dm_create_userevent(sid, (size_t)0, (void *)0, token); |
| if (error == -1) { |
| errno_msg("Can't create user event for token context, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| error = dm_request_right(sid, hanp, hlen, *token, DM_RR_WAIT, right); |
| if (error == -1) { |
| errno_msg("Can't get requested right for token, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| return(0); |
| } |
| |
| |
| /* |
| * Release the lock on a file |
| */ |
| void |
| unlock_file( |
| dm_sessid_t sid, |
| dm_token_t token) |
| { |
| int error; |
| |
| error = dm_respond_event(sid, token, DM_RESP_CONTINUE, 0, 0, 0); |
| if (error == -1) |
| errno_msg("Can't respond to event and release token, line=%d, errno=%d", __LINE__, errno); |
| |
| return; |
| } |
| |
| |
| int |
| create_stgfile( |
| char *stage_dir, |
| void *hanp, |
| size_t hlen, |
| char *path, |
| int *stg_fd) |
| { |
| char handle_str[HANDLE_STR]; |
| |
| if (hlen > HANDLE_LEN) { |
| err_msg("Handle length (%d) too long for file", hlen); |
| return(1); |
| } |
| |
| strcpy(path, stage_dir); |
| strcat(path, "/"); |
| hantoa(hanp, hlen, handle_str); |
| |
| /* |
| * Concat the ascii representation of the file handle |
| * (which is two times longer than the binary version) |
| * onto the staging path name |
| */ |
| strncat(path, (char *)handle_str, hlen*2); |
| |
| if ( (*stg_fd = open(path, O_RDWR | O_CREAT, 0644)) < 0) { |
| errno_msg("Can't open file %s, line=%d, errno=%d\n", path, __LINE__, errno); |
| return(1); |
| } |
| |
| return(0); |
| } |
| |
| |
| /* |
| * Extract the three fields from the input line. THe input is of |
| * the form |
| * filehandle length filehandle file size |
| * |
| * The length of the file handle is expected to be less than 64 bytes. |
| */ |
| int |
| extract_fields( |
| char *ibuf, |
| char *handle_buf, |
| size_t *hlen, |
| dm_size_t *fsize) |
| { |
| char *cp, *start; |
| size_t len; |
| char *hanp; |
| char *hanpp=NULL; |
| |
| /* |
| * Skip any leading white space, and check the length |
| * of the file handle |
| */ |
| cp = ibuf; |
| while (!isalnum(*cp)) |
| cp++; |
| |
| start = cp; |
| while (isalnum(*cp)) |
| cp++; |
| *cp = '\0'; |
| |
| len = strtol(start, 0, 0); |
| if (len > HANDLE_LEN) { |
| err_msg("%s/%d: Handle length %d too long in input line", __FILE__, __LINE__, len); |
| return(1); |
| } |
| |
| *hlen = len; |
| |
| /* |
| * Skip over white space, and extract the file handle |
| */ |
| while (!isalnum(*cp)) |
| cp++; |
| hanp = cp; |
| |
| /* |
| * Skip over the ascii length of the file handle, and |
| * then extract the file's length |
| */ |
| cp += len*2; |
| *cp = '\0'; |
| |
| atohan( hanp, (void**)&hanpp, &len ); |
| memcpy( handle_buf, hanpp, len); |
| free( hanpp ); |
| |
| /* skip over white space */ |
| while (!isalnum(*cp)) |
| cp++; |
| |
| /* read file length */ |
| start = cp; |
| while (isalnum(*cp)) |
| cp++; |
| *cp = '\0'; |
| |
| *fsize = strtol(start, 0, 0); |
| |
| return(0); |
| |
| } |
| |
| |
| /* |
| * Save the location of the file's data so that we can find |
| * it again when we're staging the file back in. Rather than store |
| * the full pathname of the staging file, we just store the handle. |
| * This means the staging dir must be on a filesystem that supports |
| * the DMAPI. |
| */ |
| int |
| save_dataloc( |
| dm_sessid_t sid, |
| void *hanp, |
| size_t hlen, |
| dm_token_t token, |
| char *stgpath) |
| { |
| void *stg_hanp; |
| size_t stg_hlen; |
| int error; |
| dm_attrname_t hanp_attrname; |
| dm_attrname_t hlen_attrname; |
| |
| if (dm_path_to_handle(stgpath, &stg_hanp, &stg_hlen) == -1) { |
| errno_msg("Can't get handle for path %s, line=%d, errno=%d", stgpath, __LINE__, errno); |
| return(1); |
| } |
| |
| /* |
| * Since handles are in two parts, they become two attributes. |
| * This can be useful, since we can extract the length |
| * separately when we stage the file back in |
| */ |
| memcpy((void *)&hanp_attrname.an_chars[0], DLOC_HAN, DM_ATTR_NAME_SIZE); |
| error = dm_set_dmattr(sid, hanp, hlen, token, &hanp_attrname, |
| 0, stg_hlen, stg_hanp); |
| if (error == -1) { |
| errno_msg("Can't set DM attr of staging filehandle, line=%d, errno=%d",__LINE__, errno); |
| return(1); |
| } |
| |
| memcpy((void *)&hlen_attrname.an_chars[0], DLOC_HANLEN, DM_ATTR_NAME_SIZE); |
| error = dm_set_dmattr(sid, hanp, hlen, token, &hlen_attrname, |
| 0, sizeof(stg_hlen), (void *)&stg_hlen); |
| if (error == -1) { |
| errno_msg("Can't set DM attr of staging filehandle length, line=%d, errno=%d", __LINE__, errno); |
| return(1); |
| } |
| return(0); |
| } |