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