1 /* === This file is part of Calamares - <https://calamares.io> ===
2 *
3 * SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 */
7
8 #include "PreserveFiles.h"
9
10 #include "CalamaresVersion.h"
11 #include "GlobalStorage.h"
12 #include "JobQueue.h"
13 #include "utils/CalamaresUtilsSystem.h"
14 #include "utils/CommandList.h"
15 #include "utils/Logger.h"
16 #include "utils/Permissions.h"
17 #include "utils/Units.h"
18
19 #include <QFile>
20
21 using namespace CalamaresUtils::Units;
22
23 QString
targetPrefix()24 targetPrefix()
25 {
26 if ( CalamaresUtils::System::instance()->doChroot() )
27 {
28 Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
29 if ( gs && gs->contains( "rootMountPoint" ) )
30 {
31 QString r = gs->value( "rootMountPoint" ).toString();
32 if ( !r.isEmpty() )
33 {
34 return r;
35 }
36 else
37 {
38 cDebug() << "RootMountPoint is empty";
39 }
40 }
41 else
42 {
43 cDebug() << "No rootMountPoint defined, preserving files to '/'";
44 }
45 }
46
47 return QLatin1String( "/" );
48 }
49
50 QString
atReplacements(QString s)51 atReplacements( QString s )
52 {
53 Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
54 QString root( "/" );
55 QString user;
56
57 if ( gs && gs->contains( "rootMountPoint" ) )
58 {
59 root = gs->value( "rootMountPoint" ).toString();
60 }
61 if ( gs && gs->contains( "username" ) )
62 {
63 user = gs->value( "username" ).toString();
64 }
65
66 return s.replace( "@@ROOT@@", root ).replace( "@@USER@@", user );
67 }
68
PreserveFiles(QObject * parent)69 PreserveFiles::PreserveFiles( QObject* parent )
70 : Calamares::CppJob( parent )
71 {
72 }
73
~PreserveFiles()74 PreserveFiles::~PreserveFiles() {}
75
76 QString
prettyName() const77 PreserveFiles::prettyName() const
78 {
79 return tr( "Saving files for later ..." );
80 }
81
82 static bool
copy_file(const QString & source,const QString & dest)83 copy_file( const QString& source, const QString& dest )
84 {
85 QFile sourcef( source );
86 if ( !sourcef.open( QFile::ReadOnly ) )
87 {
88 cWarning() << "Could not read" << source;
89 return false;
90 }
91
92 QFile destf( dest );
93 if ( !destf.open( QFile::WriteOnly ) )
94 {
95 sourcef.close();
96 cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source;
97 return false;
98 }
99
100 QByteArray b;
101 do
102 {
103 b = sourcef.read( 1_MiB );
104 destf.write( b );
105 } while ( b.count() > 0 );
106
107 sourcef.close();
108 destf.close();
109
110 return true;
111 }
112
113 Calamares::JobResult
exec()114 PreserveFiles::exec()
115 {
116 if ( m_items.isEmpty() )
117 {
118 return Calamares::JobResult::error( tr( "No files configured to save for later." ) );
119 }
120
121 QString prefix = targetPrefix();
122 if ( !prefix.endsWith( '/' ) )
123 {
124 prefix.append( '/' );
125 }
126
127 int count = 0;
128 for ( const auto& it : m_items )
129 {
130 QString source = it.source;
131 QString bare_dest = atReplacements( it.dest );
132 QString dest = prefix + bare_dest;
133
134 if ( it.type == ItemType::Log )
135 {
136 source = Logger::logFile();
137 }
138 if ( it.type == ItemType::Config )
139 {
140 if ( !Calamares::JobQueue::instance()->globalStorage()->saveJson( dest ) )
141 {
142 cWarning() << "Could not write a JSON dump of global storage to" << dest;
143 }
144 else
145 {
146 ++count;
147 }
148 }
149 else if ( source.isEmpty() )
150 {
151 cWarning() << "Skipping unnamed source file for" << dest;
152 }
153 else
154 {
155 if ( copy_file( source, dest ) )
156 {
157 if ( it.perm.isValid() )
158 {
159 if ( !it.perm.apply( CalamaresUtils::System::instance()->targetPath( bare_dest ) ) )
160 {
161 cWarning() << "Could not set attributes of" << bare_dest;
162 }
163 }
164
165 ++count;
166 }
167 }
168 }
169
170 return count == m_items.count()
171 ? Calamares::JobResult::ok()
172 : Calamares::JobResult::error( tr( "Not all of the configured files could be preserved." ) );
173 }
174
175 void
setConfigurationMap(const QVariantMap & configurationMap)176 PreserveFiles::setConfigurationMap( const QVariantMap& configurationMap )
177 {
178 auto files = configurationMap[ "files" ];
179 if ( !files.isValid() )
180 {
181 cDebug() << "No 'files' key for preservefiles.";
182 return;
183 }
184
185 if ( files.type() != QVariant::List )
186 {
187 cDebug() << "Configuration key 'files' is not a list for preservefiles.";
188 return;
189 }
190
191 QString defaultPermissions = configurationMap[ "perm" ].toString();
192 if ( defaultPermissions.isEmpty() )
193 {
194 defaultPermissions = QStringLiteral( "root:root:0400" );
195 }
196
197 QVariantList l = files.toList();
198 unsigned int c = 0;
199 for ( const auto& li : l )
200 {
201 if ( li.type() == QVariant::String )
202 {
203 QString filename = li.toString();
204 if ( !filename.isEmpty() )
205 m_items.append(
206 Item { filename, filename, CalamaresUtils::Permissions( defaultPermissions ), ItemType::Path } );
207 else
208 {
209 cDebug() << "Empty filename for preservefiles, item" << c;
210 }
211 }
212 else if ( li.type() == QVariant::Map )
213 {
214 const auto map = li.toMap();
215 QString dest = map[ "dest" ].toString();
216 QString from = map[ "from" ].toString();
217 ItemType t = ( from == "log" ) ? ItemType::Log : ( from == "config" ) ? ItemType::Config : ItemType::None;
218 QString perm = map[ "perm" ].toString();
219 if ( perm.isEmpty() )
220 {
221 perm = defaultPermissions;
222 }
223
224 if ( dest.isEmpty() )
225 {
226 cDebug() << "Empty dest for preservefiles, item" << c;
227 }
228 else if ( t == ItemType::None )
229 {
230 cDebug() << "Invalid type for preservefiles, item" << c;
231 }
232 else
233 {
234 m_items.append( Item { QString(), dest, CalamaresUtils::Permissions( perm ), t } );
235 }
236 }
237 else
238 {
239 cDebug() << "Invalid type for preservefiles, item" << c;
240 }
241
242 ++c;
243 }
244 }
245
246 CALAMARES_PLUGIN_FACTORY_DEFINITION( PreserveFilesFactory, registerPlugin< PreserveFiles >(); )
247