Merge "Remove shflags from flash block device script"
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index 6c88f0c..e296526 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -35,7 +35,8 @@
         "boot_image_unpacker.cc",
         "data_image.cc",
         "flags.cc",
-        "image_aggregator.cc"
+        "image_aggregator.cc",
+        "super_image_mixer.cc",
     ],
     header_libs: [
         "cuttlefish_glog",
@@ -50,6 +51,8 @@
         "libbase",
         "libnl",
         "libprotobuf-cpp-full",
+        "libziparchive",
+        "libz",
     ],
     static_libs: [
         "libcuttlefish_host_config",
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 9efc0dd..6eab814 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -17,8 +17,32 @@
 
 #include <glog/logging.h>
 
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/strings/str_split.h"
 #include "host/commands/assemble_cvd/assembler_defs.h"
 #include "host/commands/assemble_cvd/flags.h"
+#include "host/libs/config/fetcher_config.h"
+
+namespace {
+
+std::string kFetcherConfigFile = "fetcher_config.json";
+
+cvd::FetcherConfig FindFetcherConfig(const std::vector<std::string>& files) {
+  cvd::FetcherConfig fetcher_config;
+  for (const auto& file : files) {
+    auto expected_pos = file.size() - kFetcherConfigFile.size();
+    if (file.rfind(kFetcherConfigFile) == expected_pos) {
+      if (fetcher_config.LoadFromFile(file)) {
+        return fetcher_config;
+      }
+      LOG(ERROR) << "Could not load fetcher config file.";
+    }
+  }
+  return fetcher_config;
+}
+
+} // namespace
 
 int main(int argc, char** argv) {
   ::android::base::InitLogging(argv, android::base::StderrLogger);
@@ -36,7 +60,17 @@
     }
   }
 
-  auto config = InitFilesystemAndCreateConfig(&argc, &argv);
+  std::string input_files_str;
+  {
+    auto input_fd = cvd::SharedFD::Dup(0);
+    auto bytes_read = cvd::ReadAll(input_fd, &input_files_str);
+    if (bytes_read < 0) {
+      LOG(FATAL) << "Failed to read input files. Error was \"" << input_fd->StrError() << "\"";
+    }
+  }
+  std::vector<std::string> input_files = cvd::StrSplit(input_files_str, '\n');
+
+  auto config = InitFilesystemAndCreateConfig(&argc, &argv, FindFetcherConfig(input_files));
 
   std::cout << GetConfigFilePath(*config) << "\n";
   std::cout << std::flush;
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 5374358..c5a8c36 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -14,6 +14,8 @@
 #include "host/commands/assemble_cvd/data_image.h"
 #include "host/commands/assemble_cvd/image_aggregator.h"
 #include "host/commands/assemble_cvd/assembler_defs.h"
+#include "host/commands/assemble_cvd/super_image_mixer.h"
+#include "host/libs/config/fetcher_config.h"
 #include "host/libs/vm_manager/crosvm_manager.h"
 #include "host/libs/vm_manager/qemu_manager.h"
 #include "host/libs/vm_manager/vm_manager.h"
@@ -733,7 +735,8 @@
 
 } // namespace
 
-const vsoc::CuttlefishConfig* InitFilesystemAndCreateConfig(int* argc, char*** argv) {
+const vsoc::CuttlefishConfig* InitFilesystemAndCreateConfig(
+    int* argc, char*** argv, cvd::FetcherConfig fetcher_config) {
   if (!ParseCommandLineFlags(argc, argv)) {
     LOG(ERROR) << "Failed to parse command arguments";
     exit(AssemblerExitCodes::kArgumentParsingError);
@@ -823,6 +826,13 @@
     CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none");
   }
 
+  if (SuperImageNeedsRebuilding(fetcher_config, *config)) {
+    if (!RebuildSuperImage(fetcher_config, *config, FLAGS_super_image)) {
+      LOG(ERROR) << "Super image rebuilding requested but could not be completed.";
+      exit(cvd::kCuttlefishConfigurationInitError);
+    }
+  }
+
   if (ShouldCreateCompositeDisk()) {
     CreateCompositeDisk(*config);
   }
diff --git a/host/commands/assemble_cvd/flags.h b/host/commands/assemble_cvd/flags.h
index e8c542f..0373388 100644
--- a/host/commands/assemble_cvd/flags.h
+++ b/host/commands/assemble_cvd/flags.h
@@ -1,6 +1,8 @@
 #pragma once
 
 #include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/fetcher_config.h"
 
-const vsoc::CuttlefishConfig* InitFilesystemAndCreateConfig(int* argc, char*** argv);
+const vsoc::CuttlefishConfig* InitFilesystemAndCreateConfig(
+    int* argc, char*** argv, cvd::FetcherConfig config);
 std::string GetConfigFilePath(const vsoc::CuttlefishConfig& config);
diff --git a/host/commands/assemble_cvd/super_image_mixer.cc b/host/commands/assemble_cvd/super_image_mixer.cc
new file mode 100644
index 0000000..061497f
--- /dev/null
+++ b/host/commands/assemble_cvd/super_image_mixer.cc
@@ -0,0 +1,256 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "super_image_mixer.h"
+
+#include <cstdio>
+#include <functional>
+#include <memory>
+
+#include <glog/logging.h>
+
+#include "ziparchive/zip_archive.h"
+#include "ziparchive/zip_writer.h"
+
+#include "common/libs/utils/files.h"
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/fetcher_config.h"
+
+namespace {
+
+using cvd::FileExists;
+using vsoc::DefaultHostArtifactsPath;
+
+std::string TargetFilesZip(const cvd::FetcherConfig& fetcher_config,
+                           cvd::FileSource source) {
+  for (const auto& file_iter : fetcher_config.get_cvd_files()) {
+    if (file_iter.second.source != source) {
+      continue;
+    }
+    std::string expected_substr = "target_files-" + file_iter.second.build_id + ".zip";
+    auto expected_pos = file_iter.first.size() - expected_substr.size();
+    if (file_iter.first.rfind(expected_substr) == expected_pos) {
+      return file_iter.first;
+    }
+  }
+  return "";
+}
+
+class ZipArchiveDeleter {
+public:
+  void operator()(ZipArchive* archive) {
+    CloseArchive(archive);
+  }
+};
+
+using ManagedZipArchive = std::unique_ptr<ZipArchive, ZipArchiveDeleter>;
+
+class CFileCloser {
+public:
+  void operator()(FILE* file) {
+    fclose(file);
+  }
+};
+
+using ManagedCFile = std::unique_ptr<FILE, CFileCloser>;
+
+ManagedZipArchive OpenZipArchive(const std::string& path) {
+  ZipArchive* ptr;
+  int status = OpenArchive(path.c_str(), &ptr);
+  if (status != 0) {
+    LOG(ERROR) << "Could not open archive \"" << path << "\": " << status;
+    return {};
+  }
+  return ManagedZipArchive(ptr);
+}
+
+ManagedCFile OpenFile(const std::string& path, const std::string& mode) {
+  FILE* ptr = fopen(path.c_str(), mode.c_str());
+  if (ptr == nullptr) {
+    int error_num = errno;
+    LOG(ERROR) << "Could not open \"" << path << "\". Error was "
+               << strerror(error_num);
+    return {};
+  }
+  return ManagedCFile(ptr);
+}
+
+const std::string kMiscInfoPath = "META/misc_info.txt";
+const std::string kSystemPath = "IMAGES/system.img";
+const std::string kSystemExtPath = "IMAGES/system_ext.img";
+
+bool CopyZipFileContents(const uint8_t* buf, size_t buf_size, void* cookie) {
+  ZipWriter* out_writer = (ZipWriter*) cookie;
+  int32_t status = out_writer->WriteBytes(buf, buf_size);
+  if (status != 0) {
+    LOG(ERROR) << "Could not write zip file contents, error code " << status;
+    return false;
+  }
+  return true;
+}
+
+bool CombineTargetZipFiles(const std::string& default_target_zip,
+                           const std::string& system_target_zip,
+                           const std::string& output_path) {
+  auto default_target = OpenZipArchive(default_target_zip);
+  if (!default_target) {
+    LOG(ERROR) << "Could not open " << default_target_zip;
+    return false;
+  }
+  auto system_target = OpenZipArchive(system_target_zip);
+  if (!system_target) {
+    LOG(ERROR) << "Could not open " << system_target_zip;
+    return false;
+  }
+  auto out_file = OpenFile(output_path, "wb");
+  if (!out_file) {
+    LOG(ERROR) << "Could not open " << output_path;
+    return false;
+  }
+  ZipWriter out_writer{out_file.get()};
+
+  ZipEntry entry;
+  if (FindEntry(system_target.get(), kSystemPath, &entry) != 0) {
+    LOG(ERROR) << "System target files zip does not have " << kSystemPath;
+    return false;
+  }
+
+  if (FindEntry(default_target.get(), kMiscInfoPath, &entry) != 0) {
+    LOG(ERROR) << "Default target files zip does not have " << kMiscInfoPath;
+    return false;
+  }
+  out_writer.StartEntry(kMiscInfoPath, 0);
+  ProcessZipEntryContents(
+      default_target.get(), &entry, CopyZipFileContents, (void*) &out_writer);
+  out_writer.FinishEntry();
+
+  bool system_target_has_ext =
+      FindEntry(system_target.get(), kSystemExtPath, &entry) == 0;
+
+  void* iteration_cookie;
+  std::string name;
+
+  StartIteration(default_target.get(), &iteration_cookie, "IMAGES/", ".img");
+  for (int status = 0; status != -1; status = Next(iteration_cookie, &entry, &name)) {
+    if (name == "") {
+      continue;
+    }
+    LOG(INFO) << "Name is \"" << name << "\"";
+    if (name == kSystemPath) {
+      continue;
+    } else if (system_target_has_ext && name == kSystemExtPath) {
+      continue;
+    }
+    LOG(INFO) << "Writing " << name;
+    out_writer.StartEntry(name, 0);
+    ProcessZipEntryContents(
+        default_target.get(), &entry, CopyZipFileContents, (void*) &out_writer);
+    out_writer.FinishEntry();
+  }
+  EndIteration(iteration_cookie);
+
+  StartIteration(system_target.get(), &iteration_cookie, "IMAGES/", ".img");
+  for (int status = 0; status != -1; status = Next(iteration_cookie, &entry, &name)) {
+    bool is_system_image = name == kSystemPath;
+    bool is_system_ext_image = name == kSystemExtPath;
+    if (!is_system_image && !is_system_ext_image) {
+      continue;
+    }
+    LOG(INFO) << "Writing " << name;
+    out_writer.StartEntry(name, 0);
+    ProcessZipEntryContents(
+        system_target.get(), &entry, CopyZipFileContents, (void*) &out_writer);
+    out_writer.FinishEntry();
+  }
+  EndIteration(iteration_cookie);
+
+  int success = out_writer.Finish();
+  if (success != 0) {
+    LOG(ERROR) << "Unable to write combined image zip archive: " << success;
+    return false;
+  }
+
+  return true;
+}
+
+bool BuildSuperImage(const std::string& combined_target_zip,
+                     const std::string& output_path) {
+  std::string build_super_image_binary;
+  std::string otatools_path;
+  if (FileExists(DefaultHostArtifactsPath("otatools/bin/build_super_image"))) {
+    build_super_image_binary =
+        DefaultHostArtifactsPath("otatools/bin/build_super_image");
+    otatools_path = DefaultHostArtifactsPath("otatools");
+  } else if (FileExists(DefaultHostArtifactsPath("bin/build_super_image"))) {
+    build_super_image_binary =
+        DefaultHostArtifactsPath("bin/build_super_image");
+    otatools_path = DefaultHostArtifactsPath("");
+  } else {
+    LOG(ERROR) << "Could not find otatools";
+    return false;
+  }
+  return cvd::execute({
+    build_super_image_binary,
+    "--path=" + otatools_path,
+    combined_target_zip,
+    output_path,
+  }) == 0;
+}
+
+} // namespace
+
+bool SuperImageNeedsRebuilding(const cvd::FetcherConfig& fetcher_config,
+                               const vsoc::CuttlefishConfig&) {
+  bool has_default_build = false;
+  bool has_system_build = false;
+  for (const auto& file_iter : fetcher_config.get_cvd_files()) {
+    if (file_iter.second.source == cvd::FileSource::DEFAULT_BUILD) {
+      has_default_build = true;
+    } else if (file_iter.second.source == cvd::FileSource::SYSTEM_BUILD) {
+      has_system_build = true;
+    }
+  }
+  return has_default_build && has_system_build;
+}
+
+bool RebuildSuperImage(const cvd::FetcherConfig& fetcher_config,
+                       const vsoc::CuttlefishConfig& config,
+                       const std::string& output_path) {
+  std::string default_target_zip =
+      TargetFilesZip(fetcher_config, cvd::FileSource::DEFAULT_BUILD);
+  if (default_target_zip == "") {
+    LOG(ERROR) << "Unable to find default target zip file.";
+    return false;
+  }
+  std::string system_target_zip =
+      TargetFilesZip(fetcher_config, cvd::FileSource::SYSTEM_BUILD);
+  if (system_target_zip == "") {
+    LOG(ERROR) << "Unable to find system target zip file.";
+    return false;
+  }
+  std::string combined_target_zip = config.PerInstancePath("target_combined.zip");
+  // TODO(schuffelen): Use otatools/bin/merge_target_files
+  if (!CombineTargetZipFiles(default_target_zip, system_target_zip,
+                             combined_target_zip)) {
+    LOG(ERROR) << "Could not combine target zip files.";
+    return false;
+  }
+  bool success = BuildSuperImage(combined_target_zip, output_path);
+  if (!success) {
+    LOG(ERROR) << "Could not write the final output super image.";
+  }
+  return success;
+}
diff --git a/host/commands/assemble_cvd/super_image_mixer.h b/host/commands/assemble_cvd/super_image_mixer.h
new file mode 100644
index 0000000..1b8c420
--- /dev/null
+++ b/host/commands/assemble_cvd/super_image_mixer.h
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/config/fetcher_config.h"
+
+bool SuperImageNeedsRebuilding(const cvd::FetcherConfig& fetcher_config,
+                               const vsoc::CuttlefishConfig& config);
+bool RebuildSuperImage(const cvd::FetcherConfig& fetcher_config,
+                       const vsoc::CuttlefishConfig& config,
+                       const std::string& output_path);
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/launch/launch_cvd.cc
index 01d6799..9d3b108 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/launch/launch_cvd.cc
@@ -37,7 +37,7 @@
  * Controllable with a flag for extraordinary scenarios such as running from a
  * daemon which closes its own stdin.
  */
-DEFINE_bool(run_file_discovery, (bool) isatty(0),
+DEFINE_bool(run_file_discovery, true,
             "Whether to run file discovery or get input files from stdin.");
 
 namespace {
diff --git a/host/libs/vm_manager/vm_manager.cpp b/host/libs/vm_manager/vm_manager.cpp
index d613a56..ac5531a 100644
--- a/host/libs/vm_manager/vm_manager.cpp
+++ b/host/libs/vm_manager/vm_manager.cpp
@@ -19,6 +19,7 @@
 #include <memory>
 
 #include <glog/logging.h>
+#include <sys/utsname.h>
 
 #include "common/libs/utils/users.h"
 #include "host/libs/config/cuttlefish_config.h"
@@ -126,12 +127,31 @@
   return true;
 }
 
+bool VmManager::LinuxVersionAtLeast4_8(std::vector<std::string>* config_commands) {
+  struct utsname info;
+  if (!uname(&info)) {
+    char* digit = strtok(info.release, "+.-");
+    int major = atoi(digit);
+    if (digit) {
+      digit = strtok(NULL, "+.-");
+      int minor = atoi(digit);
+      if (major > 4 || (major == 4 && minor >= 8)) {
+        return true;
+      }
+    }
+  }
+  LOG(ERROR) << "Kernel version must be >=4.8";
+  config_commands->push_back("# Please upgrade your kernel to >=4.8");
+  return false;
+}
+
 bool VmManager::ValidateHostConfiguration(
     std::vector<std::string>* config_commands) const {
   // the check for cvdnetwork needs to happen even if the user is not in kvm, so
-  // we cant just say UserInGroup("kvm") && UserInGroup("cvdnetwork")
+  // we can't just say UserInGroup("kvm") && UserInGroup("cvdnetwork")
   auto in_kvm = VmManager::UserInGroup("kvm", config_commands);
   auto in_cvdnetwork = VmManager::UserInGroup("cvdnetwork", config_commands);
-  return in_kvm && in_cvdnetwork;
+  auto linux_ver_4_8 = VmManager::LinuxVersionAtLeast4_8(config_commands);
+  return in_kvm && in_cvdnetwork && linux_ver_4_8;
 }
 }  // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 79d5bbd..62adbf8 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -54,6 +54,8 @@
  protected:
   static bool UserInGroup(const std::string& group,
                           std::vector<std::string>* config_commands);
+  static bool LinuxVersionAtLeast4_8(std::vector<std::string>* config_commands);
+
   const vsoc::CuttlefishConfig* config_;
   VmManager(const vsoc::CuttlefishConfig* config);
 
diff --git a/tools/flash-blk-dev.sh b/tools/flash-blk-dev.sh
index 9378b20..e5381e0 100755
--- a/tools/flash-blk-dev.sh
+++ b/tools/flash-blk-dev.sh
@@ -86,6 +86,10 @@
 	else
 		dd if=${image} of=/dev/${blk_dev} bs=1M conv=sync,noerror status=progress
 	fi
+	if [ $? != 0 ]; then
+		echo "error: failed to write to device. aborting..."
+		exit 1
+	fi
 
 	if [ ${expand} -eq 1 ]; then
 		echo "Expanding partition and filesystem..."
diff --git a/tools/network-setup.sh b/tools/network-setup.sh
index 0556b6e..2913c0d 100755
--- a/tools/network-setup.sh
+++ b/tools/network-setup.sh
@@ -19,67 +19,61 @@
 	exit 1
 fi
 
+sleep_time=0.1
 DEFAULTNET=$1
 if [ "$DEFAULTNET" == "" ]; then
-	warn1=0
-	warn2=0
-	attempts=0
-	sleep_time=0.1
-	warn_after=2.0
-	max_attempts=`echo "(${warn_after}/${sleep_time})-1" | bc`
+	warn_no_default_network=0
+	warn_multiple_networks=0
+	warn_disconnect_rockpi=0
 	while true; do
 		DEFAULTNET=`ip link | grep "state UP" | sed 's/[0-9]*: \([^:]*\):.*/\1/'`
 		if [[ "${DEFAULTNET}" == "" ]]; then
-			if [[ $warn1 -eq 0 ]]; then
+			if [[ $warn_no_default_network -eq 0 ]]; then
 				echo "error: couldn't detect any connected default network"
-				warn1=1
-				warn2=0
+				warn_no_default_network=1
+				warn_multiple_networks=0
 			fi
 			continue
 		elif [ `echo "$DEFAULTNET" | wc -l` -eq 1 ]; then
 			break
 		elif [ `echo "$DEFAULTNET" | wc -l` -ne 1 ]; then
-			if [[ $attempts -eq 0 ]]; then
+			if [[ $warn_disconnect_rockpi -eq 0 ]]; then
 				echo "Please disconnect the network cable from the Rock Pi"
+				warn_disconnect_rockpi=1
 			fi
-			if [[ $warn2 -eq 0 ]] && [[ $attempts -ge $max_attempts ]]; then
+			if [[ $warn_multiple_networks -eq 0 ]]; then
 				echo "error: detected multiple connected networks, not sure which to use as default:"
 				for net in $DEFAULTNET; do echo "    $net"; done
-				warn1=0
-				warn2=1
+				warn_no_default_network=0
+				warn_multiple_networks=1
 			fi
 			sleep $sleep_time
-			attempts=$((attempts+1))
 		fi
 	done
-	echo "Found default network at ${DEFAULTNET}"
-	echo "Attach network cable from Rock Pi to PC's spare network port"
 fi
 
 # escalate to superuser
 if [ "$UID" -ne 0 ]; then
-	exec sudo bash "$0" "${CORPNET}"
+	exec sudo bash "$0" "${DEFAULTNET}"
 fi
 
-warn3=0
-attempts=0
+echo "Found default network at ${DEFAULTNET}"
+echo "Please reconnect network cable from Rock Pi to PC's spare network port"
+
+ROCKNETinit=`ip link | grep "state UP" | grep -v $DEFAULTNET | sed 's/[0-9]*: \([^:]*\):.*/\1/' | awk 'NF'`
 while true; do
 	ROCKNET=`ip link | grep "state UP" | grep -v $DEFAULTNET | sed 's/[0-9]*: \([^:]*\):.*/\1/' | awk 'NF'`
+	networks=`echo "$ROCKNET" | wc -l`
 	if [[ "${ROCKNET}" == "" ]]; then
 		continue
-	elif [ `echo "$ROCKNET" | wc -l` -eq 1 ]; then
+	elif [ $networks -eq 1 ]; then
 		break
-	elif [ `echo "$ROCKNET" | wc -l` -gt 1 ]; then
-		if [[ $attempts -eq 0 ]]; then
-			echo "Please keep the default network and rock pi network connected; disconnect the rest"
-		fi
-		if [[ $warn3 -eq 0 ]]; then
-			echo "error: detected multiple additional networks, not sure which is the Rock Pi:"
-			for net in $ROCKNET; do echo "    $net"; done
-			warn3=1
+	elif [ $networks -gt 1 ]; then
+		ROCKNET=`comm -3 <(echo "$ROCKNETinit" | sort) <(echo "$ROCKNET" | sort) | awk '{$1=$1};1'`
+		if [ "${ROCKNET}" != "" ]; then
+			break
 		fi
 		sleep $sleep_time
-		attempts=$((attempts+1))
 	fi
 done
 echo "Found Rock Pi network at ${ROCKNET}"
@@ -129,7 +123,6 @@
 if [ $? == 0 ]; then
 	sed -i 's/.*ENABLED.*/ENABLED=1/' /etc/default/dnsmasq
 else
-	sed -i 's/.*ENABLED.*/ENABLED=1/' /etc/default/dnsmasq
 	echo "ENABLED=1" >> /etc/default/dnsmasq
 fi