1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright (c) 1998-2001 by Sun Microsystems, Inc.
24 * All rights reserved.
25 */
26 package com.sun.dhcpmgr.server;
27
28 import java.io.*;
29 import java.util.zip.*;
30 import java.util.*;
31
32 import com.sun.dhcpmgr.bridge.*;
33 import com.sun.dhcpmgr.data.*;
34
35 public class DhcpMgrImpl implements DhcpMgr {
36 private Bridge bridge;
37 private DhcpNetMgrImpl netMgr;
38 private DhcptabMgrImpl dtMgr;
39 private DhcpServiceMgrImpl srvMgr;
40 // Global lock file used to ensure only one export or import is running
41 private static final String LOCK_FILE = "/var/run/dhcp_import_export_lock";
42 private static final File lockFile = new File(LOCK_FILE);
43 private File currentlyOpenFile = null;
44 private Object currentStream = null;
45
46 public DhcpMgrImpl() {
47 bridge = new Bridge();
48 }
49
50 public DhcpNetMgr getNetMgr() {
51 if (netMgr == null) {
52 netMgr = new DhcpNetMgrImpl(bridge);
53 }
54 return netMgr;
55 }
56
57 public DhcptabMgr getDhcptabMgr() {
58 if (dtMgr == null) {
59 dtMgr = new DhcptabMgrImpl(bridge);
60 }
61 return dtMgr;
62 }
63
64 public DhcpServiceMgr getDhcpServiceMgr() {
65 if (srvMgr == null) {
66 srvMgr = new DhcpServiceMgrImpl(bridge);
67 }
68 return srvMgr;
69 }
70
71 /**
72 * Set the file which is currently open.
73 */
74 private synchronized File setFile(String name) throws IOException {
75 // Some other file is currently listed as open; deny the request
76 if (currentlyOpenFile != null) {
77 return null;
78 }
79
80 // Get system-wide lock by atomically creating lockfile
81 if (!lockFile.createNewFile()) {
82 return null;
83 }
84 currentlyOpenFile = new File(name);
85 currentStream = null;
86 return currentlyOpenFile;
87 }
88
89 private synchronized void clearFile(File file) {
90 // If this is truly the currently open file, then clear our reference
91 if (isFileOpen(file)) {
92 currentlyOpenFile = null;
93 currentStream = null;
94 // Release system-wide lock by deleting lockfile
95 lockFile.delete();
96 }
97 }
98
99 /**
100 * Get the file which is currently open.
101 */
102 private synchronized File getFile() {
103 return currentlyOpenFile;
104 }
105
106 /**
107 * Test whether a file is currently open.
108 */
109 private synchronized boolean isFileOpen(File file) {
110 return (file == currentlyOpenFile);
111 }
112
113 /**
114 * Returns the fullpath to the lock file.
115 * @return the fullpath to the lock file.
116 */
117 public String getLockPath() {
118 return (LOCK_FILE);
119 }
120
121 /**
122 * Opens an export file and writes a header record to it.
123 * @param fileName the fullpath to the export file to create.
124 * @param user the name of the user creating this file
125 * @param nets an array of networks which will be exported
126 * @param overwrite true if file should be forcible overwritten
127 * @return a reference key for this file instance, or null if there is
128 * another file already open for import or export
129 */
130 public Object openExportFile(String fileName, String user,
131 int recCount, Network [] nets, boolean overwrite)
132 throws ExistsException, IOException {
133
134 // Grab the lock
135 File file = setFile(fileName);
136
137 if (file != null) {
138 // File exists and not supposed to overwrite, throw exception
139 if (!overwrite && file.exists()) {
140 clearFile(file);
141 throw new ExistsException(fileName);
142 }
143
144 try {
145 // Open a stream to write on
146 ObjectOutputStream export = new ObjectOutputStream(
147 new GZIPOutputStream(new FileOutputStream(file)));
148
149 // Construct a header record, and write it
150 ExportHeader header = new ExportHeader(
151 getDhcpServiceMgr().getServerName(), user, recCount, nets);
152 export.writeObject(header);
153
154 // Save stream reference
155 currentStream = export;
156 } catch (IOException e) {
157 // Something went wrong, release the lock and re-throw
158 clearFile(file);
159 throw e;
160 }
161 }
162 // Give caller a reference to use for writing additional data
163 return file;
164 } // openExportFile
165
166 /**
167 * Close an export file, delete it if need be
168 * @param ref Reference to the open file, returned from openExportFile
169 * @param delete true if file is to be deleted on close, false otherwise.
170 */
171 public void closeExportFile(Object ref, boolean delete) throws IOException {
172 if (!isFileOpen((File)ref)) {
173 throw new FileNotFoundException(((File)ref).getName());
174 }
175 try {
176 ObjectOutputStream oos = (ObjectOutputStream)currentStream;
177 oos.flush();
178 oos.close();
179 if (delete) {
180 ((File)ref).delete();
181 }
182 } catch (IOException e) {
183 // Just re-throw, let finally block clean up
184 throw e;
185 } finally {
186 /*
187 * Always release the lock, we consider the file no longer useful
188 * no matter the outcome above.
189 */
190 clearFile((File)ref);
191 }
192 }
193
194 /**
195 * Open an import file
196 * @param fileName Name of file to open
197 * @return A reference to the opened import file, or null if another
198 * export or import is already in progress.
199 */
200 public Object openImportFile(String fileName) throws IOException {
201 File file = setFile(fileName);
202 if (file != null) {
203 if (!file.exists()) {
204 clearFile(file);
205 throw new FileNotFoundException(fileName);
206 }
207
208 try {
209 currentStream = new ObjectInputStream(new GZIPInputStream(
210 new FileInputStream(file)));
211 } catch (IOException e) {
212 clearFile(file);
213 throw e;
214 }
215 }
216 // Return reference caller can use to actually do the import
217 return file;
218 }
219
220 /**
221 * Close an import file, delete it if need be
222 * @param ref Reference to the open file, returned from openImportFile
223 * @param delete true if file is to be deleted on close, false otherwise.
224 */
225 public void closeImportFile(Object ref, boolean delete) throws IOException {
226 if (!isFileOpen((File)ref)) {
227 throw new FileNotFoundException(((File)ref).getName());
228 }
229 try {
230 ((ObjectInputStream)currentStream).close();
231 if (delete) {
232 ((File)ref).delete();
233 }
234 } catch (IOException e) {
235 // Just re-throw and let finally do the cleanup
236 throw e;
237 } finally {
238 clearFile((File)ref);
239 }
240 }
241
242 /**
243 * Retrieve the export header for the import file
244 * @param ref Reference to the file we're reading from
245 * @return The ExportHeader written at export time.
246 */
247 public ExportHeader getExportHeader(Object ref)
248 throws IOException, ClassNotFoundException {
249 if (!isFileOpen((File)ref)) {
250 // No such file open; throw an exception
251 throw new FileNotFoundException(((File)ref).getName());
252 } else {
253 ObjectInputStream ois = (ObjectInputStream)currentStream;
254 ExportHeader rec = (ExportHeader)ois.readObject();
255 return rec;
256 }
257 }
258
259 // Get the desired records out of an array
260 private ArrayList getSelectedRecs(String [] names, DhcptabRecord [] recs)
261 throws NoEntryException {
262 // Grab only the ones we want
263 TreeSet nameSet = new TreeSet(Arrays.asList(names));
264 ArrayList recArr = new ArrayList();
265 for (int i = 0; i < recs.length; ++i) {
266 if (nameSet.contains(recs[i].getKey())) {
267 recArr.add(recs[i]);
268 nameSet.remove(recs[i].getKey());
269 }
270 }
271 if (!nameSet.isEmpty()) {
272 // We didn't find one of the requested records
273 throw new NoEntryException((String)nameSet.first());
274 }
275 return recArr;
276 }
277
278 /**
279 * Export a list of macros specified by name to a file.
280 * @param ref A reference to the file, acquired from openExportFile()
281 * @param allMacros true if all macros are to be exported
282 * @param names names of macros to be exported if allMacros is false
283 */
284 public void exportMacros(Object ref, boolean allMacros, String [] names)
285 throws BridgeException, IOException {
286 if (!isFileOpen((File)ref)) {
287 // throw an exception that this is a bad reference
288 throw new FileNotFoundException(((File)ref).getName());
289 }
290
291 Macro [] macros = getDhcptabMgr().getMacros();
292 if (!allMacros) {
293 // Grab only the ones we want
294 ArrayList macArr = getSelectedRecs(names, macros);
295 macros = (Macro [])macArr.toArray(new Macro[0]);
296 }
297
298 ObjectOutputStream oos = (ObjectOutputStream)currentStream;
299 oos.writeObject(macros);
300 }
301
302 /**
303 * Export a list of options specified by name to a file.
304 * @param ref A reference to the file, acquired from openExportFile()
305 * @param allOptions true if all options are to be exported
306 * @param names names of options to be exported if allOptions is false
307 */
308 public void exportOptions(Object ref, boolean allOptions, String [] names)
309 throws BridgeException, IOException {
310 if (!isFileOpen((File)ref)) {
311 // throw an exception that this is a bad reference
312 throw new FileNotFoundException(((File)ref).getName());
313 }
314
315 Option [] options = getDhcptabMgr().getOptions();
316 if (!allOptions) {
317 // Grab only the ones we want
318 ArrayList optArr = getSelectedRecs(names, options);
319 options = (Option [])optArr.toArray(new Option[0]);
320 }
321
322 ObjectOutputStream oos = (ObjectOutputStream)currentStream;
323 oos.writeObject(options);
324 }
325
326 /**
327 * Export a network and its client records to a file
328 * @param ref A reference to the file, acquired from openExportFile()
329 * @param net Network to be exported
330 */
331 public void exportNetwork(Object ref, Network net)
332 throws BridgeException, IOException {
333 if (!isFileOpen((File)ref)) {
334 // throw an exception that this is a bad reference
335 throw new FileNotFoundException(((File)ref).getName());
336 }
337
338 // Get clients from database
339 DhcpClientRecord [] clients =
340 getNetMgr().loadNetworkCompletely(net.toString());
341
342 // Now write client array for this net
343 ObjectOutputStream oos = (ObjectOutputStream)currentStream;
344 oos.writeObject(clients);
345 }
346
347 /**
348 * Import dhcptab records from an export file into the configuration.
349 * @param recType The type of record to import; must be either
350 * DhcptabRecord.MACRO or DhcptabRecord.OPTION
351 * @param ref The file reference returned by openImportFile()
352 * @param overwrite true if this data should overwrite existing data
353 * @return An array of import results; empty if all records were imported.
354 */
355 private ActionError [] importDhcptabRecs(String recType, Object ref,
356 boolean overwrite)
357 throws IOException, OptionalDataException, ClassNotFoundException {
358
359 ArrayList resultList = new ArrayList();
360 DhcptabRecord [] recs = new DhcptabRecord[0];
361
362 if (!isFileOpen((File)ref)) {
363 // No such file open; throw an exception
364 throw new FileNotFoundException(((File)ref).getName());
365 }
366 ObjectInputStream ois = (ObjectInputStream)currentStream;
367 recs = (DhcptabRecord [])ois.readObject();
368 // Try to cast to appropriate type to ensure data is OK.
369 if (recType.equals(DhcptabRecord.MACRO)) {
370 Macro [] macros = (Macro []) recs;
371 } else {
372 Option [] options = (Option []) recs;
373 }
374
375 DhcptabMgr mgr = getDhcptabMgr();
376 for (int i = 0; recs != null && i < recs.length; ++i) {
377 try {
378 if (overwrite) {
379 /*
380 * Hack alert! We reset the signature to a default value
381 * that the datastores will not interpret. This allows us
382 * to forcibly delete the record, even if it came from a
383 * previous attempt to import this record. Without this
384 * step, the datastore may (correctly) signal an update
385 * collision and refuse to perform the delete. An
386 * alternative that might be used is to mark the signature
387 * member of DhcptabRecord as transient; however, that would
388 * have the future undesirable effect of dropping that
389 * field when we put a remote communication method
390 * in the mix which uses serialization, such as RMI.
391 */
392 recs[i].setSignature(DhcptabRecord.DEFAULT_SIGNATURE);
393 mgr.deleteRecord(recs[i], false);
394 }
395 } catch (Throwable t) {
396 // Do nothing; we'll probably have an error on the create
397 }
398 try {
399 mgr.createRecord(recs[i], false);
400 } catch (Exception e) {
401 // Record the error, we try all of them no matter what
402 resultList.add(new ActionError(recs[i].getKey(), e));
403 }
404 }
405
406 return (ActionError [])resultList.toArray(new ActionError[0]);
407 }
408
409 /**
410 * Import options from an export file.
411 * @param ref Reference to import file returned by openImportFile
412 * @param overwrite true if existing data should be overwritten.
413 * @return An array of errors in the import process; empty if all OK
414 */
415 public ActionError [] importOptions(Object ref, boolean overwrite)
416 throws IOException, OptionalDataException, ClassNotFoundException {
417 return importDhcptabRecs(DhcptabRecord.OPTION, ref, overwrite);
418 }
419
420 /**
421 * Import macros from an export file.
422 * @param ref Reference to import file returned by openImportFile
423 * @param overwrite true if existing data should be overwritten.
424 * @return An array of errors in the import process; empty if all OK
425 */
426 public ActionError [] importMacros(Object ref, boolean overwrite)
427 throws IOException, OptionalDataException, ClassNotFoundException {
428 return importDhcptabRecs(DhcptabRecord.MACRO, ref, overwrite);
429 }
430
431
432
433 /**
434 * Import network records from an export file into the configuration.
435 * @param net The network which is expected to be imported
436 * @param ref The file reference returned by openImportFile()
437 * @param overwrite true if this data should overwrite existing data
438 * @return An array of import results; empty if all records were imported.
439 */
440 public ActionError [] importNetwork(Network net, Object ref,
441 boolean overwrite) throws IOException, OptionalDataException,
442 ClassNotFoundException, BridgeException {
443
444 if (!isFileOpen((File)ref)) {
445 // No such file open; throw an exception
446 throw new FileNotFoundException(((File)ref).getName());
447 }
448
449 ArrayList resultList = new ArrayList();
450 DhcpClientRecord [] clients = null;
451 ObjectInputStream ois = (ObjectInputStream)currentStream;
452 clients = (DhcpClientRecord [])ois.readObject();
453
454 String networkName = net.toString();
455 DhcpNetMgr mgr = getNetMgr();
456
457 // Create the network table. It may already exist.
458 boolean netExisted = false;
459 try {
460 mgr.createNetwork(networkName);
461 } catch (TableExistsException e) {
462 /*
463 * This is o.k. no matter whether overwrite is true or not;
464 * however, record the fact that it existed so that we don't
465 * optimize out the delete in the loop below.
466 */
467 netExisted = true;
468 }
469
470 // Add the addresses to the table and record any exceptions.
471 for (int i = 0; clients != null && i < clients.length; ++i) {
472 /*
473 * If we're supposed to overwrite and the network table
474 * existed before we started, then try to delete the client
475 */
476 if (overwrite && netExisted) {
477 try {
478 /*
479 * Hack alert! We reset the signature to a default value
480 * that the datastores will not interpret. This allows us
481 * to forcibly delete the record, even if it came from a
482 * previous attempt to import this record. Without this
483 * step, the datastore may "correctly" signal an update
484 * collision and refuse to perform the delete. An
485 * alternative that might be used is to mark the signature
486 * member of DhcptabRecord as transient; however, that would
487 * have the future undesirable effect of dropping that
488 * field when we put a remote communication method
489 * in the mix which uses serialization, such as RMI.
490 */
491 clients[i].setSignature(DhcpClientRecord.DEFAULT_SIGNATURE);
492 mgr.deleteClient(clients[i], networkName);
493 } catch (Throwable t) {
494 // Ignore delete error, we'll probably have an error on add
495 }
496 }
497 try {
498 // Now add the client
499 mgr.addClient(clients[i], networkName);
500 } catch (Exception e) {
501 String address = clients[i].getClientIPAddress();
502 resultList.add(new ActionError(address, e));
503 }
504 }
505
506 return (ActionError [])resultList.toArray(new ActionError[0]);
507 }
508 }