001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * Copyright 2015 ForgeRock AS 024 */ 025package org.opends.server.backends.pluggable.spi; 026 027import static org.opends.messages.BackendMessages.*; 028import static org.opends.messages.ConfigMessages.*; 029import static org.opends.server.util.StaticUtils.*; 030 031import java.io.File; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.opendj.config.server.ConfigChangeResult; 035import org.forgerock.opendj.config.server.ConfigException; 036import org.opends.server.core.DirectoryServer; 037import org.opends.server.types.DN; 038import org.opends.server.types.FilePermission; 039 040/** Utility class for implementations of {@link Storage}. */ 041public final class StorageUtils 042{ 043 private StorageUtils() 044 { 045 // do not instantiate utility classes 046 } 047 048 /** 049 * Returns a database directory file from the provided parent database directory and backendId. 050 * 051 * @param parentDbDirectory the parent database directory 052 * @param backendId the backend id 053 * @return a database directory file where to store data for the provided backendId 054 */ 055 public static File getDBDirectory(String parentDbDirectory, String backendId) 056 { 057 return new File(getFileForPath(parentDbDirectory), backendId); 058 } 059 060 /** 061 * Ensure backendDir exists (creating it if not) and has the specified dbDirPermissions. 062 * 063 * @param backendDir the backend directory where to set the storage files 064 * @param dbDirPermissions the permissions to set for the database directory 065 * @param configDN the backend configuration DN 066 * @throws ConfigException if configuration fails 067 */ 068 public static void setupStorageFiles(File backendDir, String dbDirPermissions, DN configDN) throws ConfigException 069 { 070 ConfigChangeResult ccr = new ConfigChangeResult(); 071 072 checkDBDirExistsOrCanCreate(backendDir, ccr, false); 073 if (!ccr.getMessages().isEmpty()) 074 { 075 throw new ConfigException(ccr.getMessages().get(0)); 076 } 077 checkDBDirPermissions(dbDirPermissions, configDN, ccr); 078 if (!ccr.getMessages().isEmpty()) 079 { 080 throw new ConfigException(ccr.getMessages().get(0)); 081 } 082 setDBDirPermissions(backendDir, dbDirPermissions, configDN, ccr); 083 if (!ccr.getMessages().isEmpty()) 084 { 085 throw new ConfigException(ccr.getMessages().get(0)); 086 } 087 } 088 089 /** 090 * Checks a directory exists or can actually be created. 091 * 092 * @param backendDir the directory to check for 093 * @param ccr the list of reasons to return upstream or null if called from setupStorage() 094 * @param cleanup true if the directory should be deleted after creation 095 */ 096 public static void checkDBDirExistsOrCanCreate(File backendDir, ConfigChangeResult ccr, boolean cleanup) 097 { 098 if (!backendDir.exists()) 099 { 100 if (!backendDir.mkdirs()) 101 { 102 addErrorMessage(ccr, ERR_CREATE_FAIL.get(backendDir.getPath())); 103 } 104 if (cleanup) 105 { 106 backendDir.delete(); 107 } 108 } 109 else if (!backendDir.isDirectory()) 110 { 111 addErrorMessage(ccr, ERR_DIRECTORY_INVALID.get(backendDir.getPath())); 112 } 113 } 114 115 /** 116 * Returns false if directory permissions in the configuration are invalid. 117 * Otherwise returns the same value as it was passed in. 118 * 119 * @param dbDirPermissions the permissions to set for the database directory 120 * @param configDN the backend configuration DN 121 * @param ccr the current list of change results 122 */ 123 public static void checkDBDirPermissions(String dbDirPermissions, DN configDN, ConfigChangeResult ccr) 124 { 125 try 126 { 127 FilePermission backendPermission = decodeDBDirPermissions(dbDirPermissions, configDN); 128 // Make sure the mode will allow the server itself access to the database 129 if (!backendPermission.isOwnerWritable() 130 || !backendPermission.isOwnerReadable() 131 || !backendPermission.isOwnerExecutable()) 132 { 133 addErrorMessage(ccr, ERR_CONFIG_BACKEND_INSANE_MODE.get(dbDirPermissions)); 134 } 135 } 136 catch (ConfigException ce) 137 { 138 addErrorMessage(ccr, ce.getMessageObject()); 139 } 140 } 141 142 /** 143 * Sets files permissions on the backend directory. 144 * 145 * @param backendDir the directory to setup 146 * @param dbDirPermissions the permissions to set for the database directory 147 * @param configDN the backend configuration DN 148 * @param ccr the current list of change results 149 * @throws ConfigException if configuration fails 150 */ 151 public static void setDBDirPermissions(File backendDir, String dbDirPermissions, DN configDN, ConfigChangeResult ccr) 152 throws ConfigException 153 { 154 try 155 { 156 FilePermission backendPermission = decodeDBDirPermissions(dbDirPermissions, configDN); 157 if (!FilePermission.setPermissions(backendDir, backendPermission)) 158 { 159 addErrorMessage(ccr, WARN_UNABLE_SET_PERMISSIONS.get(backendPermission, backendDir)); 160 } 161 } 162 catch (Exception e) 163 { 164 addErrorMessage(ccr, WARN_SET_PERMISSIONS_FAILED.get(backendDir, stackTraceToSingleLineString(e))); 165 } 166 } 167 168 private static FilePermission decodeDBDirPermissions(String dbDirPermissions, DN configDN) throws ConfigException 169 { 170 try 171 { 172 return FilePermission.decodeUNIXMode(dbDirPermissions); 173 } 174 catch (Exception e) 175 { 176 throw new ConfigException(ERR_CONFIG_BACKEND_MODE_INVALID.get(configDN)); 177 } 178 } 179 180 /** 181 * Adds the provided message to the provided config change result. 182 * 183 * @param ccr the config change result 184 * @param message the message to add 185 */ 186 public static void addErrorMessage(ConfigChangeResult ccr, LocalizableMessage message) 187 { 188 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 189 ccr.addMessage(message); 190 } 191 192 /** 193 * Removes the storage files from the provided backend directory. 194 * 195 * @param backendDir 196 * the backend directory where to remove storage files 197 */ 198 public static void removeStorageFiles(File backendDir) 199 { 200 if (!backendDir.exists()) 201 { 202 return; 203 } 204 if (!backendDir.isDirectory()) 205 { 206 throw new StorageRuntimeException(ERR_DIRECTORY_INVALID.get(backendDir.getPath()).toString()); 207 } 208 209 try 210 { 211 File[] files = backendDir.listFiles(); 212 for (File f : files) 213 { 214 f.delete(); 215 } 216 } 217 catch (Exception e) 218 { 219 throw new StorageRuntimeException(ERR_REMOVE_FAIL.get(e.getMessage()).toString(), e); 220 } 221 } 222 223 /** 224 * Creates a new unusable {@link StorageStatus} for the disk full threshold. 225 * 226 * @param directory the directory which reached the disk full threshold 227 * @param thresholdInBytes the threshold in bytes 228 * @param backendId the backend id 229 * @return a new unusable {@link StorageStatus} 230 */ 231 public static StorageStatus statusWhenDiskSpaceFull(File directory, long thresholdInBytes, String backendId) 232 { 233 return StorageStatus.unusable(WARN_DISK_SPACE_FULL_THRESHOLD_CROSSED.get( 234 directory.getFreeSpace(), directory.getAbsolutePath(), thresholdInBytes, backendId)); 235 } 236 237 /** 238 * Creates a new locked down {@link StorageStatus} for the disk low threshold. 239 * 240 * @param directory the directory which reached the disk low threshold 241 * @param thresholdInBytes the threshold in bytes 242 * @param backendId the backend id 243 * @return a new locked down {@link StorageStatus} 244 */ 245 public static StorageStatus statusWhenDiskSpaceLow(File directory, long thresholdInBytes, String backendId) 246 { 247 return StorageStatus.lockedDown(WARN_DISK_SPACE_LOW_THRESHOLD_CROSSED.get( 248 directory.getFreeSpace(), directory.getAbsolutePath(), thresholdInBytes, backendId)); 249 } 250}