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