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             if (read(md->lfd, tbuf, sizeof(tbuf) - 1) <= 0) {
00112                 if (msgs != NULL)
00113                     ccn_charbuf_append_string(msgs, "Unable to read pid from pid file. ");
00114                 goto Bail;
00115             }
00116             pid = strtol(tbuf, NULL, 10);
00117             if (pid == (int)getpid())
00118                 goto Bail; /* locked by self; errno still EACCES */
00119             if (fcntl(md->lfd, F_SETLK, &flk) == -1) {
00120                 if (errno == EACCES || errno == EAGAIN) { // it's locked
00121                     fcntl(md->lfd, F_GETLK, &flk);
00122                     if (msgs != NULL)
00123                         ccn_charbuf_putf(msgs, "Locked by process id %d. ", flk.l_pid);
00124                     goto Bail;
00125                 }            
00126             }
00127             if (msgs != NULL)
00128                 ccn_charbuf_putf(msgs, "Breaking stale lock by pid %d. ", pid);
00129             lseek(md->lfd, 0, SEEK_SET);
00130             if (ftruncate(md->lfd, 0) < 0) {
00131                 if (msgs != NULL)
00132                     ccn_charbuf_append_string(msgs, "Unable to truncate pid file. ");
00133                 goto Bail; /* errno per ftruncate */
00134             };
00135         }
00136         else {
00137             if (msgs != NULL)
00138                 ccn_charbuf_append_string(msgs, "Unable to open pid file. ");
00139             goto Bail; /* errno per open, probably EACCES */
00140         }
00141     }
00142     else if (fcntl(md->lfd, F_SETLK, &flk) == -1) {
00143         if (errno == EACCES || errno == EAGAIN) { // it's locked
00144             fcntl(md->lfd, F_GETLK, &flk);
00145             if (msgs != NULL)
00146                 ccn_charbuf_putf(msgs, "Locked by process id %d. ", flk.l_pid);
00147             goto Bail;
00148         }            
00149     }
00150     /* Locking succeeded - place our pid in the lockfile so humans can see it */
00151     temp->length = 0;
00152     ccn_charbuf_putf(temp, "%d", (int)getpid());
00153     if (write(md->lfd, temp->buf, temp->length) <= 0) {
00154         if (msgs != NULL)
00155             ccn_charbuf_append_string(msgs, "Unable to write pid file.");
00156         goto Bail;
00157     }
00158     /* leave the lock file descriptor open, otherwise the lock is released */
00159     /* Read maxnodeid */
00160     temp->length = 0;
00161     ccn_charbuf_append_charbuf(temp, md->dirpath);
00162     ccn_charbuf_putf(temp, "/maxnodeid");
00163     fd = open(ccn_charbuf_as_string(temp), O_RDWR);
00164     if (fd != -1) {
00165         memset(tbuf, 0, sizeof(tbuf));
00166         res = read(fd, tbuf, sizeof(tbuf) - 1);
00167         errno = EINVAL;
00168         maxnodeid = strtoul(tbuf, NULL, 10);
00169         if (maxnodeid == 0)
00170             goto Bail;
00171     }
00172     /* Everything looks good. */
00173     ans = tans;
00174     tans = NULL;
00175     res = md->dirpath->length;
00176     if (res >= sizeof(ans->clue))
00177         res = sizeof(ans->clue) - 1;
00178     memcpy(ans->clue, md->dirpath->buf + md->dirpath->length - res, res);
00179     ans->btopen = &bts_open;
00180     ans->btread = &bts_read;
00181     ans->btwrite = &bts_write;
00182     ans->btclose = &bts_close;
00183     ans->btdestroy = &bts_destroy;
00184     ans->maxnodeid = maxnodeid;
00185     ans->openfds = 0;
00186     ans->data = md;
00187     md->io = ans;
00188     md = NULL;
00189 Bail:
00190     if (fd != -1) close(fd);
00191     if (tans != NULL) free(tans);
00192     if (md != NULL) {
00193         ccn_charbuf_destroy(&md->dirpath);
00194         free(md);
00195     }
00196     ccn_charbuf_destroy(&temp);
00197     return(ans);
00198 }
00199 
00200 struct bts_node_state {
00201     struct ccn_btree_node *node;
00202     int fd;
00203 };
00204 
00205 static int
00206 bts_open(struct ccn_btree_io *io, struct ccn_btree_node *node)
00207 {
00208     struct bts_node_state *nd = NULL;
00209     struct ccn_charbuf *temp = NULL;
00210     struct bts_data *md = io->data;
00211     int res, l;
00212     
00213     if (node->iodata != NULL || io != md->io) abort();
00214     nd = calloc(1, sizeof(*nd));
00215     if (nd == NULL)
00216         return(-1);
00217     temp = ccn_charbuf_create();
00218     if (temp == NULL)
00219         goto Bail;
00220     res = ccn_charbuf_append_charbuf(temp, md->dirpath);
00221     res |= ccn_charbuf_putf(temp, "/%u", (unsigned)node->nodeid);
00222     if (res < 0)
00223         goto Bail;
00224     nd->fd = open(ccn_charbuf_as_string(temp),
00225                (O_RDWR | O_CREAT),
00226                0640);
00227     if (nd->fd < 0)
00228         goto Bail;
00229     if (node->nodeid > io->maxnodeid) {
00230         /* Record maxnodeid in a file */
00231         io->maxnodeid = node->nodeid;
00232         temp->length = 0;
00233         ccn_charbuf_append_charbuf(temp, md->dirpath);
00234         ccn_charbuf_putf(temp, "/maxnodeid");
00235         res = open(ccn_charbuf_as_string(temp),
00236                    (O_RDWR | O_CREAT | O_TRUNC),
00237                    0640);
00238         if (res < 0)
00239             goto Bail;
00240         temp->length = 0;
00241         ccn_charbuf_putf(temp, "%u", (unsigned)node->nodeid);
00242         l = write(res, temp->buf, temp->length);
00243         close(res);
00244         if (l != temp->length)
00245             goto Bail;
00246     }
00247     ccn_charbuf_destroy(&temp);
00248     io->openfds++;
00249     nd->node = node;
00250     node->iodata = nd;
00251     return(nd->fd);
00252     
00253 Bail:
00254     ccn_charbuf_destroy(&temp);
00255     if (nd->fd >= 0)
00256         close(nd->fd);
00257     free(nd);
00258     return(-1);
00259 }
00260 
00261 static int
00262 bts_read(struct ccn_btree_io *io, struct ccn_btree_node *node, unsigned limit)
00263 {
00264     struct bts_node_state *nd = node->iodata;
00265     ssize_t sres;
00266     off_t offset;
00267     off_t clean = 0;
00268     
00269     if (nd == NULL || nd->node != node) abort();
00270     offset = lseek(nd->fd, 0, SEEK_END);
00271     if (offset == (off_t)-1)
00272         return(-1);
00273     if (offset < limit)
00274         limit = offset;
00275     if (node->clean > 0 && node->clean <= node->buf->length)
00276         clean = node->clean;
00277     offset = lseek(nd->fd, clean, SEEK_SET);
00278     if (offset == (off_t)-1)
00279         return(-1);
00280     if (offset != clean)
00281         abort();
00282     node->buf->length = clean;  /* we know clean <= node->buf->length */
00283     sres = read(nd->fd, ccn_charbuf_reserve(node->buf, limit - clean), limit - clean);
00284     if (sres < 0)
00285         return(-1);
00286     if (sres != limit - clean) {
00287         abort(); // XXX - really should not happen unless someone else modified file
00288     }
00289     if (sres + node->buf->length > node->buf->limit) {
00290         abort(); // oooops!
00291     }
00292     node->buf->length += sres;
00293     return(0);
00294 }
00295 
00296 static int
00297 bts_write(struct ccn_btree_io *io, struct ccn_btree_node *node)
00298 {
00299     struct bts_node_state *nd = node->iodata;
00300     ssize_t sres;
00301     off_t offset;
00302     size_t clean = 0;
00303     
00304     if (nd == NULL || nd->node != node) abort();
00305     if (node->clean > 0 && node->clean <= node->buf->length)
00306         clean = node->clean;
00307     offset = lseek(nd->fd, clean, SEEK_SET);
00308     if (offset == (off_t)-1)
00309         return(-1);
00310     if (offset != clean)
00311         abort();
00312     sres = write(nd->fd, node->buf->buf + clean, node->buf->length - clean);
00313     if (sres == -1)
00314         return(-1);
00315     if (sres + clean != node->buf->length)
00316         abort();
00317     return(ftruncate(nd->fd, node->buf->length));
00318 }
00319 
00320 static int
00321 bts_close(struct ccn_btree_io *io, struct ccn_btree_node *node)
00322 {
00323     struct bts_node_state *nd = node->iodata;
00324     int res = -1;
00325     
00326     if (nd != NULL && nd->node == node) {
00327         res = close(nd->fd);
00328         if (res == -1 && errno == EINTR)
00329             return(res);
00330         io->openfds--;
00331         nd->node = NULL;
00332         node->iodata = NULL;
00333         free(nd);
00334     }
00335     return(res);
00336 }
00337 
00338 /**
00339  *  Remove the lock file, trusting that it is ours.
00340  *  @returns -1 if there were errors (but it cleans up what it can).
00341  */
00342 static int
00343 bts_remove_lockfile(struct ccn_btree_io *io)
00344 {
00345     size_t sav;
00346     int res;
00347     struct flock flk = {0};
00348     struct bts_data *md = NULL;
00349     
00350     md = io->data;
00351     sav = md->dirpath->length;
00352     ccn_charbuf_putf(md->dirpath, "/.LCK");
00353     res = unlink(ccn_charbuf_as_string(md->dirpath));
00354     md->dirpath->length = sav;
00355     if (md->lfd >= 0) {
00356         flk.l_type = F_UNLCK;
00357         flk.l_whence = SEEK_SET;
00358         fcntl(md->lfd, F_SETLK, &flk);
00359         md->lfd = -1;
00360     }
00361     return(res);
00362 }
00363 
00364 /**
00365  *  Remove the lock file and free up resources.
00366  *  @returns -1 if there were errors (but it cleans up what it can).
00367  */
00368 static int
00369 bts_destroy(struct ccn_btree_io **pio)
00370 {
00371     int res;
00372     struct bts_data *md = NULL;
00373 
00374     if (*pio == NULL)
00375         return(0);
00376     if ((*pio)->btdestroy != &bts_destroy)
00377         abort(); /* serious caller bug */
00378     res = bts_remove_lockfile(*pio);
00379     md = (*pio)->data;
00380     if (md->io != *pio) abort();
00381     ccn_charbuf_destroy(&md->dirpath);
00382     free(md);
00383     (*pio)->data = NULL;
00384     free(*pio);
00385     *pio = NULL;
00386     return(res);
00387 }
Generated on Tue Aug 21 14:54:17 2012 for Content-Centric Networking in C by  doxygen 1.6.3