r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[samba.git] / source3 / smbd / dmapi.c
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
23 #undef DBGC_CLASS
24 #define DBGC_CLASS DBGC_DMAPI
25
26 #ifndef USE_DMAPI
27
28 int dmapi_init_session(void) { return -1; }
29 uint32 dmapi_file_flags(const char * const path) { return 0; }
30 BOOL dmapi_have_session(void) { return False; }
31
32 #else /* USE_DMAPI */
33
34 #ifdef HAVE_XFS_DMAPI_H
35 #include <xfs/dmapi.h>
36 #elif defined(HAVE_SYS_DMI_H)
37 #include <sys/dmi.h>
38 #elif defined(HAVE_SYS_JFSDMAPI_H)
39 #include <sys/jfsdmapi.h>
40 #elif defined(HAVE_SYS_DMAPI_H)
41 #include <sys/dmapi.h>
42 #elif defined(HAVE_DMAPI_H)
43 #include <dmapi.h>
44 #endif
45
46 #define DMAPI_SESSION_NAME "samba"
47 #define DMAPI_TRACE 10
48
49 static dm_sessid_t dmapi_session = DM_NO_SESSION;
50
51 /* Initialise the DMAPI interface. Make sure that we only end up initialising
52  * once per process to avoid resource leaks across different DMAPI
53  * implementations.
54  */
55 static int init_dmapi_service(void)
56 {
57         static pid_t lastpid;
58
59         pid_t mypid;
60
61         mypid = sys_getpid();
62         if (mypid != lastpid) {
63                 char *version;
64
65                 lastpid = mypid;
66                 if (dm_init_service(&version) < 0) {
67                         return -1;
68                 }
69
70                 DEBUG(0, ("Initializing DMAPI: %s\n", version));
71         }
72
73         return 0;
74 }
75
76 BOOL dmapi_have_session(void)
77 {
78         return dmapi_session != DM_NO_SESSION;
79 }
80
81 static dm_sessid_t *realloc_session_list(dm_sessid_t * sessions, int count)
82 {
83         dm_sessid_t *nsessions;
84
85         nsessions = TALLOC_REALLOC_ARRAY(NULL, sessions, dm_sessid_t, count);
86         if (nsessions == NULL) {
87                 TALLOC_FREE(sessions);
88                 return NULL;
89         }
90
91         return nsessions;
92 }
93
94 /* Initialise DMAPI session. The session is persistant kernel state, so it
95  * might already exist, in which case we merely want to reconnect to it. This
96  * function should be called as root.
97  */
98 int dmapi_init_session(void)
99 {
100         char    buf[DM_SESSION_INFO_LEN];
101         size_t  buflen;
102
103         uint        nsessions = 10;
104         dm_sessid_t *sessions = NULL;
105
106         int i, err;
107
108         /* If we aren't root, something in the following will fail due to lack
109          * of privileges. Aborting seems a little extreme.
110          */
111         SMB_WARN(getuid() == 0, "dmapi_init_session must be called as root");
112
113         dmapi_session = DM_NO_SESSION;
114         if (init_dmapi_service() < 0) {
115                 return -1;
116         }
117
118 retry:
119
120         if ((sessions = realloc_session_list(sessions, nsessions)) == NULL) {
121                 return -1;
122         }
123
124         err = dm_getall_sessions(nsessions, sessions, &nsessions);
125         if (err < 0) {
126                 if (errno == E2BIG) {
127                         nsessions *= 2;
128                         goto retry;
129                 }
130
131                 DEBUGADD(DMAPI_TRACE,
132                         ("failed to retrieve DMAPI sessions: %s\n",
133                         strerror(errno)));
134                 TALLOC_FREE(sessions);
135                 return -1;
136         }
137
138         for (i = 0; i < nsessions; ++i) {
139                 err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
140                 buf[sizeof(buf) - 1] = '\0';
141                 if (err == 0 && strcmp(DMAPI_SESSION_NAME, buf) == 0) {
142                         dmapi_session = sessions[i];
143                         DEBUGADD(DMAPI_TRACE,
144                                 ("attached to existing DMAPI session "
145                                  "named '%s'\n", buf));
146                         break;
147                 }
148         }
149
150         TALLOC_FREE(sessions);
151
152         /* No session already defined. */
153         if (dmapi_session == DM_NO_SESSION) {
154                 err = dm_create_session(DM_NO_SESSION,
155                                         CONST_DISCARD(char *,
156                                                       DMAPI_SESSION_NAME),
157                                         &dmapi_session);
158                 if (err < 0) {
159                         DEBUGADD(DMAPI_TRACE,
160                                 ("failed to create new DMAPI session: %s\n",
161                                 strerror(errno)));
162                         dmapi_session = DM_NO_SESSION;
163                         return -1;
164                 }
165
166                 DEBUGADD(DMAPI_TRACE,
167                         ("created new DMAPI session named '%s'\n",
168                         DMAPI_SESSION_NAME));
169         }
170
171         /* Note that we never end the DMAPI session. This enables child
172          * processes to continue to use the session after we exit. It also lets
173          * you run a second Samba server on different ports without any
174          * conflict.
175          */
176
177         return 0;
178 }
179
180 /* Reattach to an existing dmapi session. Called from service processes that
181  * might not be running as root.
182  */
183 static int reattach_dmapi_session(void)
184 {
185         char    buf[DM_SESSION_INFO_LEN];
186         size_t  buflen;
187
188         if (dmapi_session != DM_NO_SESSION ) {
189                 become_root();
190
191                 /* NOTE: On Linux, this call opens /dev/dmapi, costing us a
192                  * file descriptor. Ideally, we would close this when we fork.
193                  */
194                 if (init_dmapi_service() < 0) {
195                         dmapi_session = DM_NO_SESSION;
196                         unbecome_root();
197                         return -1;
198                 }
199
200                 if (dm_query_session(dmapi_session, sizeof(buf),
201                             buf, &buflen) < 0) {
202                         /* Session is stale. Disable DMAPI. */
203                         dmapi_session = DM_NO_SESSION;
204                         unbecome_root();
205                         return -1;
206                 }
207
208                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
209
210                 DEBUG(DMAPI_TRACE, ("reattached DMAPI session\n"));
211                 unbecome_root();
212         }
213
214         return 0;
215 }
216
217 uint32 dmapi_file_flags(const char * const path)
218 {
219         static int attached = 0;
220
221         int             err;
222         dm_eventset_t   events = {0};
223         uint            nevents;
224
225         void    *dm_handle;
226         size_t  dm_handle_len;
227
228         uint32  flags = 0;
229
230         /* If a DMAPI session has been initialised, then we need to make sure
231          * we are attached to it and have the correct privileges. This is
232          * necessary to be able to do DMAPI operations across a fork(2). If
233          * it fails, there is no liklihood of that failure being transient.
234          *
235          * Note that this use of the static attached flag relies on the fact
236          * that dmapi_file_flags() is never called prior to forking the
237          * per-client server process.
238          */
239         if (dmapi_have_session() && !attached) {
240                 attached++;
241                 if (reattach_dmapi_session() < 0) {
242                         return 0;
243                 }
244         }
245
246         /* AIX has DMAPI but no POSIX capablities support. In this case,
247          * we need to be root to do DMAPI manipulations.
248          */
249 #ifndef HAVE_POSIX_CAPABILITIES
250         become_root();
251 #endif
252
253         err = dm_path_to_handle(CONST_DISCARD(char *, path),
254                 &dm_handle, &dm_handle_len);
255         if (err < 0) {
256                 DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
257                             path, strerror(errno)));
258
259                 if (errno != EPERM) {
260                         goto done;
261                 }
262
263                 /* Linux capabilities are broken in that changing our
264                  * user ID will clobber out effective capabilities irrespective
265                  * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
266                  * capabilities are not removed from our permitted set, so we
267                  * can re-acquire them if necessary.
268                  */
269
270                 set_effective_capability(DMAPI_ACCESS_CAPABILITY);
271
272                 err = dm_path_to_handle(CONST_DISCARD(char *, path),
273                         &dm_handle, &dm_handle_len);
274                 if (err < 0) {
275                         DEBUG(DMAPI_TRACE,
276                             ("retrying dm_path_to_handle(%s): %s\n",
277                             path, strerror(errno)));
278                         goto done;
279                 }
280         }
281
282         err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
283                 DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
284         if (err < 0) {
285                 DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
286                             path, strerror(errno)));
287                 dm_handle_free(dm_handle, dm_handle_len);
288                 goto done;
289         }
290
291         /* We figure that the only reason a DMAPI application would be
292          * interested in trapping read events is that part of the file is
293          * offline.
294          */
295         DEBUG(DMAPI_TRACE, ("DMAPI event list for %s is %#llx\n",
296                     path, events));
297         if (DMEV_ISSET(DM_EVENT_READ, events)) {
298                 flags = FILE_ATTRIBUTE_OFFLINE;
299         }
300
301         dm_handle_free(dm_handle, dm_handle_len);
302
303         if (flags & FILE_ATTRIBUTE_OFFLINE) {
304                 DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
305         }
306
307 done:
308
309 #ifndef HAVE_POSIX_CAPABILITIES
310         unbecome_root();
311 #endif
312
313         return flags;
314 }
315
316 #endif /* USE_DMAPI */