57c54cad405b81b1b69f271e304b83ca3ef53624
[metze/samba/wip.git] / source4 / lib / ldb / tools / ldbedit.c
1 /* 
2    ldb database library
3
4    Copyright (C) Andrew Tridgell  2004
5
6      ** NOTE! The following LGPL license applies to the ldb
7      ** library. This does NOT imply that all of Samba is released
8      ** under the LGPL
9    
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Lesser General Public
12    License as published by the Free Software Foundation; either
13    version 2 of the License, or (at your option) any later version.
14
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Lesser General Public License for more details.
19
20    You should have received a copy of the GNU Lesser General Public
21    License along with this library; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24
25 /*
26  *  Name: ldb
27  *
28  *  Component: ldbedit
29  *
30  *  Description: utility for ldb database editing
31  *
32  *  Author: Andrew Tridgell
33  */
34
35 #include "includes.h"
36
37 /*
38   modify a database record so msg1 becomes msg2
39   returns the number of modified elements
40 */
41 static int modify_record(struct ldb_context *ldb, 
42                          struct ldb_message *msg1,
43                          struct ldb_message *msg2)
44 {
45         struct ldb_message mod;
46         struct ldb_message_element *el;
47         int i;
48         int count = 0;
49
50         mod.dn = msg1->dn;
51         mod.num_elements = 0;
52         mod.elements = NULL;
53
54         /* look in msg2 to find elements that need to be added
55            or modified */
56         for (i=0;i<msg2->num_elements;i++) {
57                 el = ldb_msg_find_element(msg1, msg2->elements[i].name);
58
59                 if (el && ldb_msg_element_compare(el, &msg2->elements[i]) == 0) {
60                         continue;
61                 }
62
63                 if (ldb_msg_add(ldb, &mod, 
64                                 &msg2->elements[i],
65                                 el?LDB_FLAG_MOD_REPLACE:LDB_FLAG_MOD_ADD) != 0) {
66                         return -1;
67                 }
68                 count++;
69         }
70
71         /* look in msg1 to find elements that need to be deleted */
72         for (i=0;i<msg1->num_elements;i++) {
73                 el = ldb_msg_find_element(msg2, msg1->elements[i].name);
74                 if (!el) {
75                         if (ldb_msg_add_empty(ldb, &mod, 
76                                               msg1->elements[i].name,
77                                               LDB_FLAG_MOD_DELETE) != 0) {
78                                 return -1;
79                         }
80                         count++;
81                 }
82         }
83
84         if (mod.num_elements == 0) {
85                 return 0;
86         }
87
88         if (ldb_modify(ldb, &mod) != 0) {
89                 fprintf(stderr, "failed to modify %s - %s\n", 
90                         msg1->dn, ldb_errstring(ldb));
91                 return -1;
92         }
93
94         return count;
95 }
96
97 /*
98   find dn in msgs[]
99 */
100 static struct ldb_message *msg_find(struct ldb_message **msgs, int count,
101                                     const char *dn)
102 {
103         int i;
104         for (i=0;i<count;i++) {
105                 if (ldb_dn_cmp(dn, msgs[i]->dn) == 0) {
106                         return msgs[i];
107                 }
108         }
109         return NULL;
110 }
111
112 /*
113   merge the changes in msgs2 into the messages from msgs1
114 */
115 static int merge_edits(struct ldb_context *ldb,
116                        struct ldb_message **msgs1, int count1,
117                        struct ldb_message **msgs2, int count2)
118 {
119         int i;
120         struct ldb_message *msg;
121         int ret = 0;
122         int adds=0, modifies=0, deletes=0;
123
124         /* do the adds and modifies */
125         for (i=0;i<count2;i++) {
126                 msg = msg_find(msgs1, count1, msgs2[i]->dn);
127                 if (!msg) {
128                         if (ldb_add(ldb, msgs2[i]) != 0) {
129                                 fprintf(stderr, "failed to add %s - %s\n",
130                                         msgs2[i]->dn, ldb_errstring(ldb));
131                                 return -1;
132                         }
133                         adds++;
134                 } else {
135                         if (modify_record(ldb, msg, msgs2[i]) > 0) {
136                                 modifies++;
137                         }
138                 }
139         }
140
141         /* do the deletes */
142         for (i=0;i<count1;i++) {
143                 msg = msg_find(msgs2, count2, msgs1[i]->dn);
144                 if (!msg) {
145                         if (ldb_delete(ldb, msgs1[i]->dn) != 0) {
146                                 fprintf(stderr, "failed to delete %s - %s\n",
147                                         msgs1[i]->dn, ldb_errstring(ldb));
148                                 return -1;
149                         }
150                         deletes++;
151                 }
152         }
153
154         printf("# %d adds  %d modifies  %d deletes\n", adds, modifies, deletes);
155
156         return ret;
157 }
158
159 /*
160   save a set of messages as ldif to a file
161 */
162 static int save_ldif(struct ldb_context *ldb, 
163                      FILE *f, struct ldb_message **msgs, int count)
164 {
165         int i;
166
167         fprintf(f, "# editing %d records\n", count);
168
169         for (i=0;i<count;i++) {
170                 struct ldb_ldif ldif;
171                 fprintf(f, "# record %d\n", i+1);
172
173                 ldif.changetype = LDB_CHANGETYPE_NONE;
174                 ldif.msg = *msgs[i];
175
176                 ldif_write_file(ldb, f, &ldif);
177         }
178
179         return 0;
180 }
181
182
183 /*
184   edit the ldb search results in msgs using the user selected editor
185 */
186 static int do_edit(struct ldb_context *ldb, struct ldb_message **msgs1, int count1,
187                    const char *editor)
188 {
189         int fd, ret;
190         FILE *f;
191         char template[] = "/tmp/ldbedit.XXXXXX";
192         char *cmd;
193         struct ldb_ldif *ldif;
194         struct ldb_message **msgs2 = NULL;
195         int count2 = 0;
196
197         /* write out the original set of messages to a temporary
198            file */
199         fd = mkstemp(template);
200
201         if (fd == -1) {
202                 perror(template);
203                 return -1;
204         }
205
206         f = fdopen(fd, "r+");
207
208         if (!f) {
209                 perror("fopen");
210                 close(fd);
211                 unlink(template);
212                 return -1;
213         }
214
215         if (save_ldif(ldb, f, msgs1, count1) != 0) {
216                 return -1;
217         }
218
219         fclose(f);
220
221         ldb_asprintf(ldb, &cmd, "%s %s", editor, template);
222
223         if (!cmd) {
224                 unlink(template);
225                 fprintf(stderr, "out of memory\n");
226                 return -1;
227         }
228
229         /* run the editor */
230         ret = system(cmd);
231         free(cmd);
232
233         if (ret != 0) {
234                 unlink(template);
235                 fprintf(stderr, "edit with %s failed\n", editor);
236                 return -1;
237         }
238
239         /* read the resulting ldif into msgs2 */
240         f = fopen(template, "r");
241         if (!f) {
242                 perror(template);
243                 return -1;
244         }
245
246         while ((ldif = ldif_read_file(ldb, f))) {
247                 msgs2 = ldb_realloc_p(ldb, msgs2, struct ldb_message *, count2+1);
248                 if (!msgs2) {
249                         fprintf(stderr, "out of memory");
250                         return -1;
251                 }
252                 msgs2[count2++] = &ldif->msg;
253         }
254
255         fclose(f);
256         unlink(template);
257
258         return merge_edits(ldb, msgs1, count1, msgs2, count2);
259 }
260
261 static void usage(void)
262 {
263         printf("Usage: ldbedit <options> <expression>\n");
264         printf("Options:\n");
265         printf("  -H ldb_url       choose the database (or $LDB_URL)\n");
266         printf("  -s base|sub|one  choose search scope\n");
267         printf("  -b basedn        choose baseDN\n");
268         printf("  -a               edit all records (expression 'objectclass=*')\n");
269         printf("  -e editor        choose editor (or $VISUAL or $EDITOR)\n");
270         exit(1);
271 }
272
273  int main(int argc, char * const argv[])
274 {
275         struct ldb_context *ldb;
276         struct ldb_message **msgs;
277         int ret;
278         const char *expression = NULL;
279         const char *ldb_url;
280         const char *basedn = NULL;
281         int opt;
282         enum ldb_scope scope = LDB_SCOPE_SUBTREE;
283         const char *editor;
284
285         ldb_url = getenv("LDB_URL");
286
287         /* build the editor command to run -
288            use the same editor priorities as vipw */
289         editor = getenv("VISUAL");
290         if (!editor) {
291                 editor = getenv("EDITOR");
292         }
293         if (!editor) {
294                 editor = "vi";
295         }
296
297         while ((opt = getopt(argc, argv, "hab:e:H:s:")) != EOF) {
298                 switch (opt) {
299                 case 'b':
300                         basedn = optarg;
301                         break;
302
303                 case 'H':
304                         ldb_url = optarg;
305                         break;
306
307                 case 's':
308                         if (strcmp(optarg, "base") == 0) {
309                                 scope = LDB_SCOPE_BASE;
310                         } else if (strcmp(optarg, "sub") == 0) {
311                                 scope = LDB_SCOPE_SUBTREE;
312                         } else if (strcmp(optarg, "one") == 0) {
313                                 scope = LDB_SCOPE_ONELEVEL;
314                         }
315                         break;
316
317                 case 'e':
318                         editor = optarg;
319                         break;
320
321                 case 'a':
322                         expression = "(|(objectclass=*)(dn=*))";
323                         break;
324                         
325                 case 'h':
326                 default:
327                         usage();
328                         break;
329                 }
330         }
331
332         if (!ldb_url) {
333                 fprintf(stderr, "You must specify a ldb URL\n\n");
334                 usage();
335         }
336
337         argc -= optind;
338         argv += optind;
339
340         if (!expression) {
341                 if (argc == 0) {
342                         usage();
343                 }
344                 expression = argv[0];
345         }
346
347         ldb = ldb_connect(ldb_url, 0, NULL);
348
349         if (!ldb) {
350                 perror("ldb_connect");
351                 exit(1);
352         }
353
354         ret = ldb_search(ldb, basedn, scope, expression, NULL, &msgs);
355
356         if (ret == -1) {
357                 printf("search failed - %s\n", ldb_errstring(ldb));
358                 exit(1);
359         }
360
361         if (ret == 0) {
362                 printf("no matching records - cannot edit\n");
363                 return 0;
364         }
365
366         do_edit(ldb, msgs, ret, editor);
367
368         if (ret > 0) {
369                 ret = ldb_search_free(ldb, msgs);
370                 if (ret == -1) {
371                         fprintf(stderr, "search_free failed - %s\n", ldb_errstring(ldb));
372                         exit(1);
373                 }
374         }
375
376         ldb_close(ldb);
377         return 0;
378 }