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 }