1 /* -*- c++ -*- */
2 /*
3  * Copyright 2003,2011,2013 Free Software Foundation, Inc.
4  *
5  * This file is part of GNU Radio
6  *
7  * GNU Radio is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3, or (at your option)
10  * any later version.
11  *
12  * GNU Radio is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with GNU Radio; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include "vmcircbuf_mmap_tmpfile.h"
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <stdexcept>
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
34 #endif
35 #ifdef HAVE_SYS_MMAN_H
36 #include <sys/mman.h>
37 #endif
38 #include "pagesize.h"
39 #include <gnuradio/sys_paths.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <stdio.h>
43 #include <string.h>
44 
45 namespace gr {
46 
vmcircbuf_mmap_tmpfile(int size)47 vmcircbuf_mmap_tmpfile::vmcircbuf_mmap_tmpfile(int size) : gr::vmcircbuf(size)
48 {
49 #if !defined(HAVE_MMAP)
50     fprintf(stderr, "gr::vmcircbuf_mmap_tmpfile: mmap or mkstemp is not available\n");
51     throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
52 #else
53     gr::thread::scoped_lock guard(s_vm_mutex);
54 
55     if (size <= 0 || (size % gr::pagesize()) != 0) {
56         fprintf(stderr, "gr::vmcircbuf_mmap_tmpfile: invalid size = %d\n", size);
57         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
58     }
59 
60     int seg_fd = -1;
61     char seg_name[1024];
62 
63     static int s_seg_counter = 0;
64 
65     // open a temporary file that we'll map in a bit later
66     while (1) {
67         snprintf(seg_name,
68                  sizeof(seg_name),
69                  "%s/gnuradio-%d-%d-XXXXXX",
70                  gr::tmp_path(),
71                  getpid(),
72                  s_seg_counter);
73         s_seg_counter++;
74 
75         seg_fd = open(seg_name, O_RDWR | O_CREAT | O_EXCL, 0600);
76         if (seg_fd == -1) {
77             if (errno == EEXIST) // File already exists (shouldn't happen).  Try again
78                 continue;
79 
80             char msg[1024];
81             snprintf(msg, sizeof(msg), "gr::vmcircbuf_mmap_tmpfile: open [%s]", seg_name);
82             perror(msg);
83             throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
84         }
85         break;
86     }
87 
88     if (unlink(seg_name) == -1) {
89         perror("gr::vmcircbuf_mmap_tmpfile: unlink");
90         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
91     }
92 
93     // We've got a valid file descriptor to a tmp file.
94     // Now set it's length to 2x what we really want and mmap it in.
95     if (ftruncate(seg_fd, (off_t)2 * size) == -1) {
96         close(seg_fd); // cleanup
97         perror("gr::vmcircbuf_mmap_tmpfile: ftruncate (1)");
98         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
99     }
100 
101     void* first_copy =
102         mmap(0, 2 * size, PROT_READ | PROT_WRITE, MAP_SHARED, seg_fd, (off_t)0);
103 
104     if (first_copy == MAP_FAILED) {
105         close(seg_fd); // cleanup
106         perror("gr::vmcircbuf_mmap_tmpfile: mmap (1)");
107         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
108     }
109 
110     // unmap the 2nd half
111     if (munmap((char*)first_copy + size, size) == -1) {
112         close(seg_fd); // cleanup
113         perror("gr::vmcircbuf_mmap_tmpfile: munmap (1)");
114         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
115     }
116 
117     // map the first half into the now available hole where the
118     // second half used to be.
119     void* second_copy = mmap((char*)first_copy + size,
120                              size,
121                              PROT_READ | PROT_WRITE,
122                              MAP_SHARED,
123                              seg_fd,
124                              (off_t)0);
125 
126     if (second_copy == MAP_FAILED) {
127         munmap(first_copy, size); // cleanup
128         close(seg_fd);
129         perror("gr::vmcircbuf_mmap_tmpfile: mmap(2)");
130         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
131     }
132 
133     // check for contiguity
134     if ((char*)second_copy != (char*)first_copy + size) {
135         munmap(first_copy, size); // cleanup
136         munmap(second_copy, size);
137         close(seg_fd);
138         perror("gr::vmcircbuf_mmap_tmpfile: non-contiguous second copy");
139         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
140     }
141 
142     // cut the tmp file down to size
143     if (ftruncate(seg_fd, (off_t)size) == -1) {
144         munmap(first_copy, size); // cleanup
145         munmap(second_copy, size);
146         close(seg_fd);
147         perror("gr::vmcircbuf_mmap_tmpfile: ftruncate (2)");
148         throw std::runtime_error("gr::vmcircbuf_mmap_tmpfile");
149     }
150 
151     close(seg_fd); // fd no longer needed.  The mapping is retained.
152 
153     // Now remember the important stuff
154 
155     d_base = (char*)first_copy;
156     d_size = size;
157 #endif
158 }
159 
~vmcircbuf_mmap_tmpfile()160 vmcircbuf_mmap_tmpfile::~vmcircbuf_mmap_tmpfile()
161 {
162 #if defined(HAVE_MMAP)
163     gr::thread::scoped_lock guard(s_vm_mutex);
164 
165     if (munmap(d_base, 2 * d_size) == -1) {
166         perror("gr::vmcircbuf_mmap_tmpfile: munmap(2)");
167     }
168 #endif
169 }
170 
171 // ----------------------------------------------------------------
172 //			The factory interface
173 // ----------------------------------------------------------------
174 
175 gr::vmcircbuf_factory* vmcircbuf_mmap_tmpfile_factory::s_the_factory = 0;
176 
singleton()177 gr::vmcircbuf_factory* vmcircbuf_mmap_tmpfile_factory::singleton()
178 {
179     if (s_the_factory)
180         return s_the_factory;
181 
182     s_the_factory = new gr::vmcircbuf_mmap_tmpfile_factory();
183     return s_the_factory;
184 }
185 
granularity()186 int vmcircbuf_mmap_tmpfile_factory::granularity() { return gr::pagesize(); }
187 
make(int size)188 gr::vmcircbuf* vmcircbuf_mmap_tmpfile_factory::make(int size)
189 {
190     try {
191         return new vmcircbuf_mmap_tmpfile(size);
192     } catch (...) {
193         return 0;
194     }
195 }
196 
197 } /* namespace gr */
198