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