Report history log open failure only once per file.
[platform/upstream/libzypp.git] / zypp / HistoryLog.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/target/HistoryLog.cc
10  *
11  */
12 #include <iostream>
13 #include <fstream>
14 #include <unistd.h>
15
16 #include "zypp/ZConfig.h"
17 #include "zypp/base/String.h"
18 #include "zypp/base/Logger.h"
19
20 #include "zypp/PathInfo.h"
21 #include "zypp/Date.h"
22
23 #include "zypp/PoolItem.h"
24 #include "zypp/Package.h"
25 #include "zypp/RepoInfo.h"
26
27 #include "zypp/HistoryLog.h"
28 #include "zypp/HistoryLogData.h"
29
30 using std::endl;
31 using std::string;
32
33 namespace
34 {
35   inline string timestamp()
36   { return zypp::Date::now().form( HISTORY_LOG_DATE_FORMAT ); }
37
38   inline string userAtHostname()
39   {
40     static char buf[256];
41     string result;
42     char * tmp = ::cuserid(buf);
43     if (tmp)
44     {
45       result = string(tmp);
46       if (!::gethostname(buf, 255))
47         result += "@" + string(buf);
48     }
49     return result;
50   }
51
52   static std::string pidAndAppname()
53   {
54     static std::string _val;
55     if ( _val.empty() )
56     {
57       pid_t mypid = getpid();
58       zypp::Pathname p( "/proc/"+zypp::str::numstring(mypid)+"/exe" );
59       zypp::Pathname myname( zypp::filesystem::readlink( p ) );
60
61       _val += zypp::str::numstring(mypid);
62       _val += ":";
63       _val += myname.basename();
64     }
65     return _val;
66   }
67 }
68
69 namespace zypp
70 {
71   namespace
72   {
73     const char          _sep = '|';
74     std::ofstream       _log;
75     unsigned            _refcnt = 0;
76     Pathname            _fname;
77     Pathname            _fnameLastFail;
78
79     inline void openLog()
80     {
81       if ( _fname.empty() )
82         _fname = ZConfig::instance().historyLogFile();
83
84       _log.clear();
85       _log.open( _fname.asString().c_str(), std::ios::out|std::ios::app );
86       if( !_log && _fnameLastFail != _fname )
87       {
88         ERR << "Could not open logfile '" << _fname << "'" << endl;
89         _fnameLastFail = _fname;
90       }
91     }
92
93     inline void closeLog()
94     {
95       _log.clear();
96       _log.close();
97     }
98
99     inline void refUp()
100     {
101       if ( !_refcnt )
102         openLog();
103       ++_refcnt;
104     }
105
106     inline void refDown()
107     {
108       --_refcnt;
109       if ( !_refcnt )
110         closeLog();
111     }
112   }
113
114   ///////////////////////////////////////////////////////////////////
115   //
116   //    CLASS NAME : HistoryLog
117   //
118   ///////////////////////////////////////////////////////////////////
119
120   HistoryLog::HistoryLog( const Pathname & rootdir )
121   {
122     setRoot( rootdir );
123     refUp();
124   }
125
126   HistoryLog::~HistoryLog()
127   {
128     refDown();
129   }
130
131   void HistoryLog::setRoot( const Pathname & rootdir )
132   {
133     if ( ! rootdir.absolute() )
134       return;
135
136     if ( _refcnt )
137       closeLog();
138
139     _fname = rootdir / ZConfig::instance().historyLogFile();
140     filesystem::assert_dir( _fname.dirname() );
141     MIL << "installation log file " << _fname << endl;
142
143     if ( _refcnt )
144       openLog();
145   }
146
147   const Pathname & HistoryLog::fname()
148   {
149     if ( _fname.empty() )
150       _fname = ZConfig::instance().historyLogFile();
151     return _fname;
152   }
153
154   /////////////////////////////////////////////////////////////////////////
155
156   void HistoryLog::comment( const string & comment, bool timestamp )
157   {
158     if (comment.empty())
159       return;
160
161     _log << "# ";
162     if ( timestamp )
163       _log << ::timestamp() << " ";
164
165     const char * s = comment.c_str();
166     const char * c = s;
167     unsigned size = comment.size();
168
169     // ignore the last newline
170     if (comment[size-1] == '\n')
171       --size;
172
173     for ( unsigned i = 0; i < size; ++i, ++c )
174       if ( *c == '\n' )
175       {
176         _log << string( s, c + 1 - s ) << "# ";
177         s = c + 1;
178       }
179
180     if ( s < c )
181       _log << std::string( s, c-s );
182
183     _log << endl;
184   }
185
186   /////////////////////////////////////////////////////////////////////////
187
188   void HistoryLog::install( const PoolItem & pi )
189   {
190     const Package::constPtr p = asKind<Package>(pi.resolvable());
191     if (!p)
192       return;
193
194     _log
195       << timestamp()                                   // 1 timestamp
196       << _sep << HistoryActionID::INSTALL.asString(true) // 2 action
197       << _sep << p->name()                             // 3 name
198       << _sep << p->edition()                          // 4 evr
199       << _sep << p->arch();                            // 5 arch
200
201     // ApplLow is what the solver selected on behalf of the user.
202     if (pi.status().isByUser() || pi.status().isByApplLow() )
203       _log << _sep << userAtHostname();                // 6 reqested by
204     else if (pi.status().isByApplHigh())
205       _log << _sep << pidAndAppname();
206     else
207       _log << _sep;
208
209     _log
210       << _sep << p->repoInfo().alias()                 // 7 repo alias
211       << _sep << p->checksum().checksum();             // 8 checksum
212
213     _log << endl;
214
215     //_log << pi << endl;
216   }
217
218
219   void HistoryLog::remove( const PoolItem & pi )
220   {
221     const Package::constPtr p = asKind<Package>(pi.resolvable());
222     if (!p)
223       return;
224
225     _log
226       << timestamp()                                   // 1 timestamp
227       << _sep << HistoryActionID::REMOVE.asString(true) // 2 action
228       << _sep << p->name()                             // 3 name
229       << _sep << p->edition()                          // 4 evr
230       << _sep << p->arch();                            // 5 arch
231
232     // ApplLow is what the solver selected on behalf of the user.
233     if ( pi.status().isByUser() || pi.status().isByApplLow() )
234       _log << _sep << userAtHostname();                // 6 reqested by
235     else if (pi.status().isByApplHigh())
236       _log << _sep << pidAndAppname();
237     else
238       _log << _sep;
239
240     // we don't have checksum in rpm db
241     //  << _sep << p->checksum().checksum();           // x checksum
242
243     _log << endl;
244
245     //_log << pi << endl;
246   }
247
248   /////////////////////////////////////////////////////////////////////////
249
250   void HistoryLog::addRepository(const RepoInfo & repo)
251   {
252     _log
253       << timestamp()                                   // 1 timestamp
254       << _sep << HistoryActionID::REPO_ADD.asString(true) // 2 action
255       << _sep << str::escape(repo.alias(), _sep)       // 3 alias
256       // what about the rest of the URLs??
257       << _sep << *repo.baseUrlsBegin()                 // 4 primary URL
258       << endl;
259   }
260
261
262   void HistoryLog::removeRepository(const RepoInfo & repo)
263   {
264     _log
265       << timestamp()                                   // 1 timestamp
266       << _sep << HistoryActionID::REPO_REMOVE.asString(true) // 2 action
267       << _sep << str::escape(repo.alias(), _sep)       // 3 alias
268       << endl;
269   }
270
271
272   void HistoryLog::modifyRepository(
273       const RepoInfo & oldrepo, const RepoInfo & newrepo)
274   {
275     if (oldrepo.alias() != newrepo.alias())
276     {
277       _log
278         << timestamp()                                    // 1 timestamp
279         << _sep << HistoryActionID::REPO_CHANGE_ALIAS.asString(true) // 2 action
280         << _sep << str::escape(oldrepo.alias(), _sep)     // 3 old alias
281         << _sep << str::escape(newrepo.alias(), _sep)     // 4 new alias
282         << endl;
283     }
284
285     if (*oldrepo.baseUrlsBegin() != *newrepo.baseUrlsBegin())
286     {
287       _log
288         << timestamp()                                             //1 timestamp
289         << _sep << HistoryActionID::REPO_CHANGE_URL.asString(true) // 2 action
290         << _sep << str::escape(oldrepo.alias(), _sep)              // 3 old url
291         << _sep << *newrepo.baseUrlsBegin()                        // 4 new url
292         << endl;
293     }
294   }
295
296   ///////////////////////////////////////////////////////////////////
297
298 } // namespace zypp