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 }