ccn_btree_store.c

Go to the documentation of this file.
00001 /**
00002  * File-based btree index storage
00003  */
00004 /* (Will be) Part of the CCNx C Library.
00005  *
00006  * Copyright (C) 2011 Palo Alto Research Center, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or modify it
00009  * under the terms of the GNU Lesser General Public License version 2.1
00010  * as published by the Free Software Foundation.
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00014  * Lesser General Public License for more details. You should have received
00015  * a copy of the GNU Lesser General Public License along with this library;
00016  * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
00017  * Fifth Floor, Boston, MA 02110-1301 USA.
00018  */
00019  
00020 #include <dirent.h>
00021 #include <errno.h>
00022 #include <fcntl.h>
00023 #include <sys/stat.h>
00024 #include <stdlib.h>
00025 #include <string.h>
00026 #include <signal.h>
00027 #include <unistd.h>
00028 
00029 #include <ccn/btree.h>
00030 #include <ccn/charbuf.h>
00031 
00032 static int bts_open(struct ccn_btree_io *, struct ccn_btree_node *);
00033 static int bts_read(struct ccn_btree_io *, struct ccn_btree_node *, unsigned);
00034 static int bts_write(struct ccn_btree_io *, struct ccn_btree_node *);
00035 static int bts_close(struct ccn_btree_io *, struct ccn_btree_node *);
00036 static int bts_destroy(struct ccn_btree_io **);
00037 
00038 struct bts_data {
00039     struct ccn_btree_io *io;
00040     struct ccn_charbuf *dirpath;
00041     int lfd;
00042 };
00043 
00044 /**
00045  * Create a btree storage layer from a directory.
00046  * 
00047  * In this implementation of the storage layer, each btree block is stored
00048  * as a separate file.
00049  * The files are named using the decimal representation of the nodeid.
00050  *
00051  * If msgs is not NULL, diagnostics may be recorded there.
00052  *
00053  * @param path is the name of the directory, which must exist.
00054  * @returns the new ccn_btree_io handle, or sets errno and returns NULL.
00055  */
00056 struct ccn_btree_io *
00057 ccn_btree_io_from_directory(const char *path, struct ccn_charbuf *msgs)
00058 {
00059     DIR *d = NULL;
00060     struct bts_data *md = NULL;
00061     struct ccn_btree_io *ans = NULL;
00062     struct ccn_btree_io *tans = NULL;
00063     struct ccn_charbuf *temp = NULL;
00064     char tbuf[21];
00065     int fd = -1;
00066     struct flock flk = {0};
00067     int pid, res;
00068     int maxnodeid = 0;
00069     
00070     /* Make sure we were handed a directory */
00071     d = opendir(path);
00072     if (d == NULL)
00073         goto Bail; /* errno per opendir */
00074     closedir(d);
00075     d = NULL;
00076     
00077     /* Allocate private data area */
00078     md = calloc(1, sizeof(*md));
00079     if (md == NULL)
00080         goto Bail; /* errno per calloc */
00081     md->lfd = -1;
00082     md->dirpath = ccn_charbuf_create();
00083     if (md->dirpath == NULL) goto Bail; /* errno per calloc */
00084     res = ccn_charbuf_putf(md->dirpath, "%s", path);
00085     if (res < 0) goto Bail; /* errno per calloc or snprintf */
00086     tans = calloc(1, sizeof(*tans));
00087     if (tans == NULL) goto Bail; /* errno per calloc */
00088     
00089     /* Try to create a lock file */
00090     temp = ccn_charbuf_create();
00091     if (temp == NULL) goto Bail; /* errno per calloc */    
00092     res = ccn_charbuf_append_charbuf(temp, md->dirpath);
00093     if (res < 0) goto Bail; /* errno per calloc */
00094     res = ccn_charbuf_putf(temp, "/.LCK");
00095     if (res < 0) goto Bail; /* errno per calloc or snprintf */
00096     flk.l_type = F_WRLCK;
00097     flk.l_whence = SEEK_SET;
00098     md->lfd = open(ccn_charbuf_as_string(temp),
00099                (O_RDWR | O_CREAT | O_EXCL),
00100                0600);
00101     if (md->lfd == -1) {
00102         if (errno == EEXIST) {
00103             // try to recover by checking if the pid the lock names exists
00104             md->lfd = open(ccn_charbuf_as_string(temp), O_RDWR);
00105             if (md->lfd == -1) {
00106                 if (msgs != NULL)
00107                     ccn_charbuf_append_string(msgs, "Unable to open pid file for update. ");
00108                 goto Bail;
00109             }
00110             memset(tbuf, 0, sizeof(tbuf));
00111             read(md->lfd, tbuf, sizeof(tbuf) - 1);
00112             pid = strtol(tbuf, NULL, 10);
00113             if (pid == (int)getpid())
00114                 goto Bail; /* locked by self; errno still EACCES */
00115             if (fcntl(md->lfd, F_SETLK, &flk) == -1) {
00116                 if (errno == EACCES || errno == EAGAIN) { // it's locked
00117                     fcntl(md->lfd, F_GETLK, &flk);
00118                     if (msgs != NULL)
00119                         ccn_charbuf_putf(msgs, "Locked by process id %d. ", flk.l_pid);
00120                     goto Bail;
00121                 }            
00122             }
00123             if (msgs != NULL)
00124                 ccn_charbuf_putf(msgs, "Breaking stale lock by pid %d. ", pid);
00125             lseek(md->lfd, 0, SEEK_SET);
00126             ftruncate(md->lfd, 0);
00127         }
00128         else {
00129             if (msgs != NULL)
00130                 ccn_charbuf_append_string(msgs, "Unable to open pid file. ");
00131             goto Bail; /* errno per open, probably EACCES */
00132         }
00133     }
00134     else if (fcntl(md->lfd, F_SETLK, &flk) == -1) {
00135         if (errno == EACCES || errno == EAGAIN) { // it's locked
00136             fcntl(md->lfd, F_GETLK, &flk);
00137             if (msgs != NULL)
00138                 ccn_charbuf_putf(msgs, "Locked by process id %d. ", flk.l_pid);
00139             goto Bail;
00140         }            
00141     }
00142     /* Locking succeeded - place our pid in the lockfile so humans can see it */
00143     temp->length = 0;
00144     ccn_charbuf_putf(temp, "%d", (int)getpid());
00145     if (write(md->lfd, temp->buf, temp->length) < 0) {
00146         if (msgs != NULL)
00147             ccn_charbuf_append_string(msgs, "Unable to write pid file.");
00148         goto Bail;
00149     }
00150     /* leave the lock file descriptor open, otherwise the lock is released */
00151     /* Read maxnodeid */
00152     temp->length = 0;
00153     ccn_charbuf_append_charbuf(temp, md->dirpath);
00154     ccn_charbuf_putf(temp, "/maxnodeid");
00155     fd = open(ccn_charbuf_as_string(temp), O_RDWR);
00156     if (fd != -1) {
00157         memset(tbuf, 0, sizeof(tbuf));
00158         read(fd, tbuf, sizeof(tbuf) - 1);
00159         errno = EINVAL;
00160         maxnodeid = strtoul(tbuf, NULL, 10);
00161         if (maxnodeid == 0)
00162             goto Bail;
00163     }
00164     /* Everything looks good. */
00165     ans = tans;
00166     tans = NULL;
00167     res = md->dirpath->length;
00168     if (res >= sizeof(ans->clue))
00169         res = sizeof(ans->clue) - 1;
00170     memcpy(ans->clue, md->dirpath->buf + md->dirpath->length - res, res);
00171     ans->btopen = &bts_open;
00172     ans->btread = &bts_read;
00173     ans->btwrite = &bts_write;
00174     ans->btclose = &bts_close;
00175     ans->btdestroy = &bts_destroy;
00176     ans->maxnodeid = maxnodeid;
00177     ans->openfds = 0;
00178     ans->data = md;
00179     md->io = ans;
00180     md = NULL;
00181 Bail:
00182     if (fd != -1) close(fd);
00183     if (tans != NULL) free(tans);
00184     if (md != NULL) {
00185         ccn_charbuf_destroy(&md->dirpath);
00186         free(md);
00187     }
00188     ccn_charbuf_destroy(&temp);
00189     return(ans);
00190 }
00191 
00192 struct bts_node_state {
00193     struct ccn_btree_node *node;
00194     int fd;
00195 };
00196 
00197 static int
00198 bts_open(struct ccn_btree_io *io, struct ccn_btree_node *node)
00199 {
00200     struct bts_node_state *nd = NULL;
00201     struct ccn_charbuf *temp = NULL;
00202     struct bts_data *md = io->data;
00203     int res;
00204     
00205     if (node->iodata != NULL || io != md->io) abort();
00206     nd = calloc(1, sizeof(*nd));
00207     if (nd == NULL)
00208         return(-1);
00209     temp = ccn_charbuf_create();
00210     if (temp == NULL)
00211         return(-1);
00212     res = ccn_charbuf_append_charbuf(temp, md->dirpath);
00213     res |= ccn_charbuf_putf(temp, "/%u", (unsigned)node->nodeid);
00214     if (res < 0) {
00215         ccn_charbuf_destroy(&temp);
00216         free(nd);
00217         return(-1);
00218     }
00219     nd->fd = open(ccn_charbuf_as_string(temp),
00220                (O_RDWR | O_CREAT),
00221                0640);
00222     if (nd->fd != -1 && node->nodeid > io->maxnodeid) {
00223         /* Record maxnodeid in a file */
00224         io->maxnodeid = node->nodeid;
00225         temp->length = 0;
00226         ccn_charbuf_append_charbuf(temp, md->dirpath);
00227         ccn_charbuf_putf(temp, "/maxnodeid");
00228         res = open(ccn_charbuf_as_string(temp),
00229                (O_RDWR | O_CREAT | O_TRUNC),
00230                0640);
00231         if (res >= 0) {
00232             temp->length = 0;
00233             ccn_charbuf_putf(temp, "%u", (unsigned)node->nodeid);
00234             write(res, temp->buf, temp->length);
00235             close(res);
00236         }
00237     }
00238     ccn_charbuf_destroy(&temp);
00239     if (nd->fd == -1) {
00240         free(nd);
00241         return(-1);
00242     }
00243     io->openfds++;
00244     nd->node = node;
00245     node->iodata = nd;
00246     return(nd->fd);
00247 }
00248 
00249 static int
00250 bts_read(struct ccn_btree_io *io, struct ccn_btree_node *node, unsigned limit)
00251 {
00252     struct bts_node_state *nd = node->iodata;
00253     ssize_t sres;
00254     off_t offset;
00255     off_t clean = 0;
00256     
00257     if (nd == NULL || nd->node != node) abort();
00258     offset = lseek(nd->fd, 0, SEEK_END);
00259     if (offset == (off_t)-1)
00260         return(-1);
00261     if (offset < limit)
00262         limit = offset;
00263     if (node->clean > 0 && node->clean <= node->buf->length)
00264         clean = node->clean;
00265     offset = lseek(nd->fd, clean, SEEK_SET);
00266     if (offset == (off_t)-1)
00267         return(-1);
00268     if (offset != clean)
00269         abort();
00270     node->buf->length = clean;  /* we know clean <= node->buf->length */
00271     sres = read(nd->fd, ccn_charbuf_reserve(node->buf, limit - clean), limit - clean);
00272     if (sres < 0)
00273         return(-1);
00274     if (sres != limit - clean) {
00275         abort(); // XXX - really should not happen unless someone else modified file
00276     }
00277     if (sres + node->buf->length > node->buf->limit) {
00278         abort(); // oooops!
00279     }
00280     node->buf->length += sres;
00281     return(0);
00282 }
00283 
00284 static int
00285 bts_write(struct ccn_btree_io *io, struct ccn_btree_node *node)
00286 {
00287     struct bts_node_state *nd = node->iodata;
00288     ssize_t sres;
00289     off_t offset;
00290     size_t clean = 0;
00291     
00292     if (nd == NULL || nd->node != node) abort();
00293     if (node->clean > 0 && node->clean <= node->buf->length)
00294         clean = node->clean;
00295     offset = lseek(nd->fd, clean, SEEK_SET);
00296     if (offset == (off_t)-1)
00297         return(-1);
00298     if (offset != clean)
00299         abort();
00300     sres = write(nd->fd, node->buf->buf + clean, node->buf->length - clean);
00301     if (sres == -1)
00302         return(-1);
00303     if (sres + clean != node->buf->length)
00304         abort();
00305     return(ftruncate(nd->fd, node->buf->length));
00306 }
00307 
00308 static int
00309 bts_close(struct ccn_btree_io *io, struct ccn_btree_node *node)
00310 {
00311     struct bts_node_state *nd = node->iodata;
00312     int res = -1;
00313     
00314     if (nd != NULL && nd->node == node) {
00315         res = close(nd->fd);
00316         if (res == -1 && errno == EINTR)
00317             return(res);
00318         io->openfds--;
00319         nd->node = NULL;
00320         node->iodata = NULL;
00321         free(nd);
00322     }
00323     return(res);
00324 }
00325 
00326 /**
00327  *  Remove the lock file, trusting that it is ours.
00328  *  @returns -1 if there were errors (but it cleans up what it can).
00329  */
00330 static int
00331 bts_remove_lockfile(struct ccn_btree_io *io)
00332 {
00333     size_t sav;
00334     int res;
00335     struct flock flk = {0};
00336     struct bts_data *md = NULL;
00337     
00338     md = io->data;
00339     sav = md->dirpath->length;
00340     ccn_charbuf_putf(md->dirpath, "/.LCK");
00341     res = unlink(ccn_charbuf_as_string(md->dirpath));
00342     md->dirpath->length = sav;
00343     if (md->lfd >= 0) {
00344         flk.l_type = F_UNLCK;
00345         flk.l_whence = SEEK_SET;
00346         fcntl(md->lfd, F_SETLK, &flk);
00347         md->lfd = -1;
00348     }
00349     return(res);
00350 }
00351 
00352 /**
00353  *  Remove the lock file and free up resources.
00354  *  @returns -1 if there were errors (but it cleans up what it can).
00355  */
00356 static int
00357 bts_destroy(struct ccn_btree_io **pio)
00358 {
00359     int res;
00360     struct bts_data *md = NULL;
00361 
00362     if (*pio == NULL)
00363         return(0);
00364     if ((*pio)->btdestroy != &bts_destroy)
00365         abort(); /* serious caller bug */
00366     res = bts_remove_lockfile(*pio);
00367     md = (*pio)->data;
00368     if (md->io != *pio) abort();
00369     ccn_charbuf_destroy(&md->dirpath);
00370     free(md);
00371     (*pio)->data = NULL;
00372     free(*pio);
00373     *pio = NULL;
00374     return(res);
00375 }

Generated on Thu Feb 16 00:44:00 2012 for Content-Centric Networking in C by  doxygen 1.5.6