/src/samba/source3/smbd/dmapi.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | DMAPI Support routines |
4 | | |
5 | | Copyright (C) James Peach 2006 |
6 | | |
7 | | This program 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 of the License, or |
10 | | (at your option) any later version. |
11 | | |
12 | | This program 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 this program. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include "includes.h" |
22 | | #include "smbd/smbd.h" |
23 | | #include "smbd/globals.h" |
24 | | |
25 | | #undef DBGC_CLASS |
26 | | #define DBGC_CLASS DBGC_DMAPI |
27 | | |
28 | | #ifndef USE_DMAPI |
29 | | |
30 | 0 | uint32_t dmapi_file_flags(const char * const path) { return 0; } |
31 | 0 | bool dmapi_have_session(void) { return False; } |
32 | 0 | const void * dmapi_get_current_session(void) { return NULL; } |
33 | | |
34 | | #else /* USE_DMAPI */ |
35 | | |
36 | | #ifdef HAVE_XFS_DMAPI_H |
37 | | #include <xfs/dmapi.h> |
38 | | #elif defined(HAVE_SYS_DMI_H) |
39 | | #include <sys/dmi.h> |
40 | | #elif defined(HAVE_SYS_JFSDMAPI_H) |
41 | | #include <sys/jfsdmapi.h> |
42 | | #elif defined(HAVE_SYS_DMAPI_H) |
43 | | #include <sys/dmapi.h> |
44 | | #elif defined(HAVE_DMAPI_H) |
45 | | #include <dmapi.h> |
46 | | #endif |
47 | | |
48 | | #define DMAPI_SESSION_NAME "samba" |
49 | | #define DMAPI_TRACE 10 |
50 | | |
51 | | struct smbd_dmapi_context { |
52 | | dm_sessid_t session; |
53 | | unsigned session_num; |
54 | | }; |
55 | | |
56 | | /* |
57 | | Initialise DMAPI session. The session is persistent kernel state, |
58 | | so it might already exist, in which case we merely want to |
59 | | reconnect to it. This function should be called as root. |
60 | | */ |
61 | | static int dmapi_init_session(struct smbd_dmapi_context *ctx) |
62 | | { |
63 | | char buf[DM_SESSION_INFO_LEN]; |
64 | | size_t buflen; |
65 | | uint nsessions = 5; |
66 | | dm_sessid_t *sessions = NULL; |
67 | | char *version; |
68 | | char *session_name; |
69 | | TALLOC_CTX *tmp_ctx = talloc_new(NULL); |
70 | | |
71 | | int i, err; |
72 | | |
73 | | if (ctx->session_num == 0) { |
74 | | session_name = talloc_strdup(tmp_ctx, DMAPI_SESSION_NAME); |
75 | | } else { |
76 | | session_name = talloc_asprintf(tmp_ctx, "%s%u", DMAPI_SESSION_NAME, |
77 | | ctx->session_num); |
78 | | } |
79 | | |
80 | | if (session_name == NULL) { |
81 | | DEBUG(0,("Out of memory in dmapi_init_session\n")); |
82 | | talloc_free(tmp_ctx); |
83 | | return -1; |
84 | | } |
85 | | |
86 | | |
87 | | if (dm_init_service(&version) < 0) { |
88 | | DEBUG(0, ("dm_init_service failed - disabling DMAPI\n")); |
89 | | talloc_free(tmp_ctx); |
90 | | return -1; |
91 | | } |
92 | | |
93 | | ZERO_STRUCT(buf); |
94 | | |
95 | | /* Fetch kernel DMAPI sessions until we get any of them */ |
96 | | do { |
97 | | dm_sessid_t *new_sessions; |
98 | | nsessions *= 2; |
99 | | new_sessions = talloc_realloc(tmp_ctx, sessions, |
100 | | dm_sessid_t, nsessions); |
101 | | if (new_sessions == NULL) { |
102 | | talloc_free(tmp_ctx); |
103 | | return -1; |
104 | | } |
105 | | |
106 | | sessions = new_sessions; |
107 | | err = dm_getall_sessions(nsessions, sessions, &nsessions); |
108 | | } while (err == -1 && errno == E2BIG); |
109 | | |
110 | | if (err == -1) { |
111 | | DEBUGADD(DMAPI_TRACE, |
112 | | ("failed to retrieve DMAPI sessions: %s\n", |
113 | | strerror(errno))); |
114 | | talloc_free(tmp_ctx); |
115 | | return -1; |
116 | | } |
117 | | |
118 | | /* Look through existing kernel DMAPI sessions to find out ours */ |
119 | | for (i = 0; i < nsessions; ++i) { |
120 | | err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen); |
121 | | buf[sizeof(buf) - 1] = '\0'; |
122 | | if (err == 0 && strcmp(session_name, buf) == 0) { |
123 | | ctx->session = sessions[i]; |
124 | | DEBUGADD(DMAPI_TRACE, |
125 | | ("attached to existing DMAPI session " |
126 | | "named '%s'\n", buf)); |
127 | | break; |
128 | | } |
129 | | } |
130 | | |
131 | | /* No session already defined. */ |
132 | | if (ctx->session == DM_NO_SESSION) { |
133 | | err = dm_create_session(DM_NO_SESSION, |
134 | | session_name, |
135 | | &ctx->session); |
136 | | if (err < 0) { |
137 | | DEBUGADD(DMAPI_TRACE, |
138 | | ("failed to create new DMAPI session: %s\n", |
139 | | strerror(errno))); |
140 | | ctx->session = DM_NO_SESSION; |
141 | | talloc_free(tmp_ctx); |
142 | | return -1; |
143 | | } |
144 | | |
145 | | DEBUG(0, ("created new DMAPI session named '%s' for %s\n", |
146 | | session_name, version)); |
147 | | } |
148 | | |
149 | | if (ctx->session != DM_NO_SESSION) { |
150 | | set_dmapi_capability(true); |
151 | | } |
152 | | |
153 | | /* |
154 | | Note that we never end the DMAPI session. It gets re-used if possiblie. |
155 | | DMAPI session is a kernel resource that is usually lives until server reboot |
156 | | and doesn't get destroed when an application finishes. |
157 | | |
158 | | However, we free list of references to DMAPI sessions we've got from the kernel |
159 | | as it is not needed anymore once we have found (or created) our session. |
160 | | */ |
161 | | |
162 | | talloc_free(tmp_ctx); |
163 | | return 0; |
164 | | } |
165 | | |
166 | | /* |
167 | | Return a pointer to our DMAPI session, if available. |
168 | | This assumes that you have called dmapi_have_session() first. |
169 | | */ |
170 | | const void *dmapi_get_current_session(void) |
171 | | { |
172 | | if (!dmapi_ctx) { |
173 | | return NULL; |
174 | | } |
175 | | |
176 | | if (dmapi_ctx->session == DM_NO_SESSION) { |
177 | | return NULL; |
178 | | } |
179 | | |
180 | | return (void *)&dmapi_ctx->session; |
181 | | } |
182 | | |
183 | | /* |
184 | | dmapi_have_session() must be the first DMAPI call you make in Samba. It will |
185 | | initialize DMAPI, if available, and tell you if you can get a DMAPI session. |
186 | | This should be called in the client-specific child process. |
187 | | */ |
188 | | |
189 | | bool dmapi_have_session(void) |
190 | | { |
191 | | if (!dmapi_ctx) { |
192 | | dmapi_ctx = talloc(NULL, struct smbd_dmapi_context); |
193 | | if (!dmapi_ctx) { |
194 | | exit_server("unable to allocate smbd_dmapi_context"); |
195 | | } |
196 | | dmapi_ctx->session = DM_NO_SESSION; |
197 | | dmapi_ctx->session_num = 0; |
198 | | |
199 | | become_root(); |
200 | | dmapi_init_session(dmapi_ctx); |
201 | | unbecome_root(); |
202 | | |
203 | | } |
204 | | |
205 | | return dmapi_ctx->session != DM_NO_SESSION; |
206 | | } |
207 | | |
208 | | /* |
209 | | only call this when you get back an EINVAL error indicating that the |
210 | | session you are using is invalid. This destroys the existing session |
211 | | and creates a new one. |
212 | | */ |
213 | | bool dmapi_new_session(void) |
214 | | { |
215 | | if (dmapi_have_session()) { |
216 | | /* try to destroy the old one - this may not succeed */ |
217 | | dm_destroy_session(dmapi_ctx->session); |
218 | | } |
219 | | dmapi_ctx->session = DM_NO_SESSION; |
220 | | become_root(); |
221 | | dmapi_ctx->session_num++; |
222 | | dmapi_init_session(dmapi_ctx); |
223 | | unbecome_root(); |
224 | | return dmapi_ctx->session != DM_NO_SESSION; |
225 | | } |
226 | | |
227 | | /* |
228 | | only call this when exiting from master smbd process. DMAPI sessions |
229 | | are long-lived kernel resources we ought to share across smbd processes. |
230 | | However, we must free them when all smbd processes are finished to |
231 | | allow other subsystems clean up properly. Not freeing DMAPI session |
232 | | blocks certain HSM implementations from proper shutdown. |
233 | | */ |
234 | | bool dmapi_destroy_session(void) |
235 | | { |
236 | | if (!dmapi_ctx) { |
237 | | return true; |
238 | | } |
239 | | if (dmapi_ctx->session != DM_NO_SESSION) { |
240 | | become_root(); |
241 | | if (0 == dm_destroy_session(dmapi_ctx->session)) { |
242 | | dmapi_ctx->session_num--; |
243 | | dmapi_ctx->session = DM_NO_SESSION; |
244 | | } else { |
245 | | DEBUG(0,("Couldn't destroy DMAPI session: %s\n", |
246 | | strerror(errno))); |
247 | | } |
248 | | unbecome_root(); |
249 | | } |
250 | | return dmapi_ctx->session == DM_NO_SESSION; |
251 | | } |
252 | | |
253 | | |
254 | | /* |
255 | | This is default implementation of dmapi_file_flags() that is |
256 | | called from VFS is_offline() call to know whether file is offline. |
257 | | For GPFS-specific version see modules/vfs_tsmsm.c. It might be |
258 | | that approach on querying existence of a specific attribute that |
259 | | is used in vfs_tsmsm.c will work with other DMAPI-based HSM |
260 | | implementations as well. |
261 | | */ |
262 | | uint32_t dmapi_file_flags(const char * const path) |
263 | | { |
264 | | int err; |
265 | | dm_eventset_t events = {0}; |
266 | | uint nevents; |
267 | | |
268 | | dm_sessid_t dmapi_session; |
269 | | dm_sessid_t *dmapi_session_ptr; |
270 | | const void *_dmapi_session_ptr; |
271 | | void *dm_handle = NULL; |
272 | | size_t dm_handle_len = 0; |
273 | | |
274 | | uint32_t flags = 0; |
275 | | |
276 | | _dmapi_session_ptr = dmapi_get_current_session(); |
277 | | if (_dmapi_session_ptr == NULL) { |
278 | | return 0; |
279 | | } |
280 | | |
281 | | dmapi_session_ptr = discard_const_p(dm_sessid_t, _dmapi_session_ptr); |
282 | | dmapi_session = *dmapi_session_ptr; |
283 | | if (dmapi_session == DM_NO_SESSION) { |
284 | | return 0; |
285 | | } |
286 | | |
287 | | /* AIX has DMAPI but no POSIX capabilities support. In this case, |
288 | | * we need to be root to do DMAPI manipulations. |
289 | | */ |
290 | | #ifndef HAVE_POSIX_CAPABILITIES |
291 | | become_root(); |
292 | | #endif |
293 | | |
294 | | err = dm_path_to_handle(discard_const_p(char, path), |
295 | | &dm_handle, &dm_handle_len); |
296 | | if (err < 0) { |
297 | | DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n", |
298 | | path, strerror(errno))); |
299 | | |
300 | | if (errno != EPERM) { |
301 | | goto done; |
302 | | } |
303 | | |
304 | | /* Linux capabilities are broken in that changing our |
305 | | * user ID will clobber out effective capabilities irrespective |
306 | | * of whether we have set PR_SET_KEEPCAPS. Fortunately, the |
307 | | * capabilities are not removed from our permitted set, so we |
308 | | * can re-acquire them if necessary. |
309 | | */ |
310 | | |
311 | | set_dmapi_capability(true); |
312 | | |
313 | | err = dm_path_to_handle(discard_const_p(char, path), |
314 | | &dm_handle, &dm_handle_len); |
315 | | if (err < 0) { |
316 | | DEBUG(DMAPI_TRACE, |
317 | | ("retrying dm_path_to_handle(%s): %s\n", |
318 | | path, strerror(errno))); |
319 | | goto done; |
320 | | } |
321 | | } |
322 | | |
323 | | err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len, |
324 | | DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents); |
325 | | if (err < 0) { |
326 | | DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n", |
327 | | path, strerror(errno))); |
328 | | dm_handle_free(dm_handle, dm_handle_len); |
329 | | goto done; |
330 | | } |
331 | | |
332 | | /* We figure that the only reason a DMAPI application would be |
333 | | * interested in trapping read events is that part of the file is |
334 | | * offline. |
335 | | */ |
336 | | DEBUG(DMAPI_TRACE, ("DMAPI event list for %s\n", path)); |
337 | | if (DMEV_ISSET(DM_EVENT_READ, events)) { |
338 | | flags = FILE_ATTRIBUTE_OFFLINE; |
339 | | } |
340 | | |
341 | | dm_handle_free(dm_handle, dm_handle_len); |
342 | | |
343 | | if (flags & FILE_ATTRIBUTE_OFFLINE) { |
344 | | DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path)); |
345 | | } |
346 | | |
347 | | done: |
348 | | |
349 | | #ifndef HAVE_POSIX_CAPABILITIES |
350 | | unbecome_root(); |
351 | | #endif |
352 | | |
353 | | return flags; |
354 | | } |
355 | | |
356 | | |
357 | | #endif /* USE_DMAPI */ |