Ai
3 Star 0 Fork 1

Gitee 极速下载/createrepo_c

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/rpm-software-management/createrepo_c
克隆/下载
misc.c 44.23 KB
一键复制 编辑 原始数据 按行查看 历史
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682
/* createrepo_c - Library of routines for manipulation with repodata
* Copyright (C) 2012 Tomas Mlcoch
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
#include <glib/gstdio.h>
#include <glib.h>
#include <gio/gio.h>
#include <arpa/inet.h>
#include <assert.h>
#include <curl/curl.h>
#include <errno.h>
#include <ftw.h>
#include <rpm/rpmlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "cleanup.h"
#include "error.h"
#include "misc.h"
#include "version.h"
#define ERR_DOMAIN CREATEREPO_C_ERROR
#define BUFFER_SIZE 4096
#define xstr(s) str(s)
#define str(s) #s
const char *
cr_flag_to_str(gint64 flags)
{
flags &= 0xf;
switch(flags) {
case 0:
return NULL;
case 2:
return "LT";
case 4:
return "GT";
case 8:
return "EQ";
case 10:
return "LE";
case 12:
return "GE";
default:
return NULL;
}
}
/*
* BE CAREFUL!
*
* In case chunk param is NULL:
* Returned structure had all strings malloced!!!
* Be so kind and don't forget use free() for all its element, before end of
* structure lifecycle.
*
* In case chunk is pointer to a GStringChunk:
* Returned structure had all string inserted in the passed chunk.
*
*/
cr_EVR *
cr_str_to_evr(const char *string, GStringChunk *chunk)
{
cr_EVR *evr = g_new0(cr_EVR, 1);
evr->epoch = NULL;
evr->version = NULL;
evr->release = NULL;
if (!string || !(strlen(string))) {
return evr;
}
const char *ptr; // These names are totally self explaining
const char *ptr2; //
// Epoch
gboolean bad_epoch = FALSE;
ptr = strstr(string, ":");
if (ptr) {
// Check if epoch str is a number
char *p = NULL;
strtol(string, &p, 10);
if (p == ptr) { // epoch str seems to be a number
size_t len = ptr - string;
if (len) {
if (chunk) {
evr->epoch = g_string_chunk_insert_len(chunk, string, len);
} else {
evr->epoch = g_strndup(string, len);
}
}
} else { // Bad (non-numerical) epoch
bad_epoch = TRUE;
}
} else { // There is no epoch
ptr = (char*) string-1;
}
if (!evr->epoch && !bad_epoch) {
if (chunk) {
evr->epoch = g_string_chunk_insert_const(chunk, "0");
} else {
evr->epoch = g_strdup("0");
}
}
// Version + release
ptr2 = strstr(ptr+1, "-");
if (ptr2) {
// Version
size_t version_len = ptr2 - (ptr+1);
if (chunk) {
evr->version = g_string_chunk_insert_len(chunk, ptr+1, version_len);
} else {
evr->version = g_strndup(ptr+1, version_len);
}
// Release
size_t release_len = strlen(ptr2+1);
if (release_len) {
if (chunk) {
evr->release = g_string_chunk_insert_len(chunk, ptr2+1,
release_len);
} else {
evr->release = g_strndup(ptr2+1, release_len);
}
}
} else { // Release is not here, just version
if (chunk) {
evr->version = g_string_chunk_insert_const(chunk, ptr+1);
} else {
evr->version = g_strdup(ptr+1);
}
}
return evr;
}
void
cr_evr_free(cr_EVR *evr)
{
if (!evr)
return;
g_free(evr->epoch);
g_free(evr->version);
g_free(evr->release);
g_free(evr);
}
/*
inline int
cr_is_primary(const char *filename)
{
This optimal piece of code cannot be used because of yum...
We must match any string that contains "bin/" in dirname
Response to my question from packaging team:
....
It must still contain that. Atm. it's defined as taking anything
with 'bin/' in the path. The idea was that it'd match /usr/kerberos/bin/
and /opt/blah/sbin. So that is what all versions of createrepo generate,
and what yum all versions of yum expect to be generated.
We can't change one side, without breaking the expectation of the
other.
There have been plans to change the repodata, and one of the changes
would almost certainly be in how files are represented ... likely via.
lists of "known" paths, that can be computed at createrepo time.
if (!strncmp(filename, "/bin/", 5)) {
return 1;
}
if (!strncmp(filename, "/sbin/", 6)) {
return 1;
}
if (!strncmp(filename, "/etc/", 5)) {
return 1;
}
if (!strncmp(filename, "/usr/", 5)) {
if (!strncmp(filename+5, "bin/", 4)) {
return 1;
}
if (!strncmp(filename+5, "sbin/", 5)) {
return 1;
}
if (!strcmp(filename+5, "lib/sendmail")) {
return 1;
}
}
if (!strncmp(filename, "/etc/", 5)) {
return 1;
}
if (!strcmp(filename, "/usr/lib/sendmail")) {
return 1;
}
if (strstr(filename, "bin/")) {
return 1;
}
return 0;
}
*/
#define VAL_LEN 4 // Len of numeric values in rpm (bytes)
struct cr_HeaderRangeStruct
cr_get_header_byte_range(const char *filename, GError **err)
{
// Lead is 96 bytes.
//
// Each Header starts like this (16 bytes)
// 3 bytes for the magic
// 1 byte RPM version number - always 1
// 4 reserved bytes (no meaning, just padding)
// 4 bytes for the number of entries in the index (u32)
// 4 bytes for the length of the data section (u32)
//
// Next comes a series of index entries. Each index entry is 16 bytes
// 4 bytes for the tag (u32)
// 4 bytes for the tag data type (u32)
// 4 bytes for the offset relative to the beginning of the data store
// 4 bytes for the count that contains the number of data items pointed to by the index entry
//
// After the header entries comes the data section. This stores the data pointed to by
// the offsets within each index entry.
//
// All numeric values are big-endian and need to be converted into host byte order.
struct cr_HeaderRangeStruct results;
assert(!err || *err == NULL);
results.start = 0;
results.end = 0;
// Open file
FILE *fp = fopen(filename, "rb");
if (!fp) {
const gchar * fopen_error = g_strerror(errno);
g_debug("%s: Cannot open file %s (%s)", __func__, filename,
fopen_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open %s: %s", filename, fopen_error);
return results;
}
// Get header range
// Seek to Lead (96) + 8 bytes and read the number of entries in the signature header, then convert to host byte order
if (fseek(fp, 104, SEEK_SET) != 0) {
const gchar * fseek_error = g_strerror(errno);
g_debug("%s: fseek fail on %s (%s)", __func__, filename, fseek_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot seek over %s: %s", filename, fseek_error);
fclose(fp);
return results;
}
unsigned int sigindex = 0;
unsigned int sigdata = 0;
if (fread(&sigindex, VAL_LEN, 1, fp) != 1) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"fread() error on %s: %s", filename, g_strerror(errno));
fclose(fp);
return results;
}
sigindex = htonl(sigindex);
// Read the length of the data section and convert to host byte order
if (fread(&sigdata, VAL_LEN, 1, fp) != 1) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"fread() error on %s: %s", filename, g_strerror(errno));
fclose(fp);
return results;
}
sigdata = htonl(sigdata);
// Lead (96) + HeaderIndex (16) = 112. Index entries are 16 bytes each. Include padding to align to 8 bytes
unsigned int sigindexsize = sigindex * 16;
unsigned int sigsize = sigdata + sigindexsize;
unsigned int disttoboundary = sigsize % 8;
if (disttoboundary) {
disttoboundary = 8 - disttoboundary;
}
unsigned int hdrstart = 112 + sigsize + disttoboundary;
// Seek to start of header (96) + 8 bytes and read the number of entries in the header, then convert to host byte order
fseek(fp, hdrstart, SEEK_SET);
fseek(fp, 8, SEEK_CUR);
unsigned int hdrindex = 0;
unsigned int hdrdata = 0;
if (fread(&hdrindex, VAL_LEN, 1, fp) != 1) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"fread() error on %s: %s", filename, g_strerror(errno));
fclose(fp);
return results;
}
hdrindex = htonl(hdrindex);
// Read the length of the data section and convert to host byte order
if (fread(&hdrdata, VAL_LEN, 1, fp) != 1) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"fread() error on %s: %s", filename, g_strerror(errno));
fclose(fp);
return results;
}
hdrdata = htonl(hdrdata);
// Calculate the end of the header
unsigned int hdrindexsize = hdrindex * 16;
unsigned int hdrsize = hdrdata + hdrindexsize + 16;
unsigned int hdrend = hdrstart + hdrsize;
fclose(fp);
// Check sanity
if (hdrend < hdrstart) {
g_debug("%s: sanity check fail on %s (%d > %d))", __func__,
filename, hdrstart, hdrend);
g_set_error(err, ERR_DOMAIN, CRE_ERROR,
"sanity check error on %s (hdrstart: %d > hdrend: %d)",
filename, hdrstart, hdrend);
return results;
}
results.start = hdrstart;
results.end = hdrend;
return results;
}
char *
cr_get_filename(const char *filepath)
{
char *filename;
if (!filepath)
return NULL;
filename = (char *) filepath;
size_t x = 0;
while (filepath[x] != '\0') {
if (filepath[x] == '/') {
filename = (char *) filepath+(x+1);
}
x++;
}
return filename;
}
char *
cr_get_cleaned_href(const char *filepath)
{
char *filename;
if (!filepath)
return NULL;
filename = (char *) filepath;
while (filename[0] == '.' && filename[1] == '/')
filename += 2;
return filename;
}
gboolean
cr_copy_file(const char *src, const char *in_dst, GError **err)
{
size_t readed;
char buf[BUFFER_SIZE];
_cleanup_free_ gchar *dst = NULL;
_cleanup_file_fclose_ FILE *orig = NULL;
_cleanup_file_fclose_ FILE *new = NULL;
assert(src);
assert(in_dst);
assert(!err || *err == NULL);
// If destination is dir use filename from src
if (g_str_has_suffix(in_dst, "/"))
dst = g_strconcat(in_dst, cr_get_filename(src), NULL);
else
dst = g_strdup(in_dst);
// Open src file
if ((orig = fopen(src, "rb")) == NULL) {
const gchar * fopen_error = g_strerror(errno);
g_debug("%s: Cannot open source file %s (%s)", __func__, src,
fopen_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open file %s: %s", src, fopen_error);
return FALSE;
}
// Open dst file
if ((new = fopen(dst, "wb")) == NULL) {
const gchar * fopen_error = g_strerror(errno);
g_debug("%s: Cannot open destination file %s (%s)", __func__, dst,
fopen_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open file %s: %s", dst, fopen_error);
return FALSE;
}
// Copy content from src -> dst
while ((readed = fread(buf, 1, BUFFER_SIZE, orig)) > 0) {
if (readed != BUFFER_SIZE && ferror(orig)) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Error while read %s: %s", src, g_strerror(errno));
return FALSE;
}
if (fwrite(buf, 1, readed, new) != readed) {
const gchar * fwrite_error = g_strerror(errno);
g_debug("%s: Error while copy %s -> %s (%s)", __func__, src,
dst, fwrite_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Error while write %s: %s", dst, fwrite_error);
return FALSE;
}
}
return TRUE;
}
int
cr_compress_file_with_stat(const char *src,
const char *in_dst,
cr_CompressionType compression,
cr_ContentStat *stat,
const char *zck_dict_dir,
gboolean zck_auto_chunk,
GError **err)
{
int ret = CRE_OK;
int readed;
char buf[BUFFER_SIZE];
CR_FILE *orig = NULL;
CR_FILE *new = NULL;
gchar *dst = (gchar *) in_dst;
GError *tmp_err = NULL;
assert(src);
assert(!err || *err == NULL);
const char *c_suffix = cr_compression_suffix(compression);
// Src must be a file NOT a directory
if (!g_file_test(src, G_FILE_TEST_IS_REGULAR)) {
g_debug("%s: Source (%s) must be a regular file!", __func__, src);
g_set_error(err, ERR_DOMAIN, CRE_NOFILE,
"Not a regular file: %s", src);
return CRE_NOFILE;
}
if (!dst) {
// If destination is NULL, use src + compression suffix
dst = g_strconcat(src, c_suffix, NULL);
} else if (g_str_has_suffix(dst, "/")) {
// If destination is dir use filename from src + compression suffix
dst = g_strconcat(dst, cr_get_filename(src), c_suffix, NULL);
} else if (c_suffix && !g_str_has_suffix(dst, c_suffix)) {
// If destination is missing compression suffix or has a different one, use specified compression suffix
cr_CompressionType old_type = cr_detect_compression(dst, &tmp_err);
if (tmp_err) {
g_debug("%s: Unable to detect compression type of %s, using the filename as is.", __func__, dst);
g_clear_error(&tmp_err);
} else if (old_type == CR_CW_NO_COMPRESSION) {
dst = g_strconcat(dst, c_suffix, NULL);
} else {
_cleanup_free_ gchar *tmp_file = g_strndup(dst, strlen(dst) - strlen(cr_compression_suffix(old_type)));
dst = g_strconcat(tmp_file, c_suffix, NULL);
}
}
int mode = CR_CW_AUTO_DETECT_COMPRESSION;
orig = cr_open(src,
CR_CW_MODE_READ,
mode,
&tmp_err);
if (!orig) {
ret = tmp_err->code;
g_propagate_prefixed_error(err, tmp_err, "Cannot open %s: ", src);
if (dst != in_dst)
g_free(dst);
return ret;
}
_cleanup_free_ gchar *dict = NULL;
size_t dict_size = 0;
if (compression == CR_CW_ZCK_COMPRESSION && zck_dict_dir) {
/* Find zdict */
_cleanup_free_ gchar *file_basename = NULL;
if (dst) {
_cleanup_free_ gchar *dict_base = NULL;
if (g_str_has_suffix(dst, ".zck"))
dict_base = g_strndup(dst, strlen(dst)-4);
else
dict_base = g_strdup(dst);
file_basename = g_path_get_basename(dict_base);
} else {
file_basename = g_path_get_basename(src);
}
_cleanup_free_ gchar *dict_file = cr_get_dict_file(zck_dict_dir, file_basename);
/* Read dictionary from file */
if (dict_file && !g_file_get_contents(dict_file, &dict,
&dict_size, &tmp_err)) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Error reading zchunk dict %s: %s",
dict_file, tmp_err->message);
g_clear_error(&tmp_err);
ret = CRE_IO;
goto compress_file_cleanup;
}
}
new = cr_sopen(dst, CR_CW_MODE_WRITE, compression, stat, &tmp_err);
if (tmp_err) {
g_debug("%s: Cannot open destination file %s", __func__, dst);
g_propagate_prefixed_error(err, tmp_err, "Cannot open %s: ", dst);
ret = CRE_IO;
goto compress_file_cleanup;
}
if (compression == CR_CW_ZCK_COMPRESSION) {
if (dict && cr_set_dict(new, dict, dict_size, &tmp_err) != CRE_OK) {
ret = tmp_err->code;
g_propagate_prefixed_error(err, tmp_err, "Unable to set zdict for %s: ", dst);
goto compress_file_cleanup;
}
if (zck_auto_chunk && cr_set_autochunk(new, TRUE, &tmp_err) != CRE_OK) {
ret = tmp_err->code;
g_propagate_prefixed_error(err, tmp_err, "Unable to set auto-chunking for %s: ", dst);
goto compress_file_cleanup;
}
}
while ((readed = cr_read(orig, buf, BUFFER_SIZE, &tmp_err)) > 0) {
cr_write(new, buf, readed, &tmp_err);
if (tmp_err) {
ret = tmp_err->code;
g_propagate_prefixed_error(err, tmp_err, "Unable to write to %s: ", dst);
goto compress_file_cleanup;
}
}
compress_file_cleanup:
if (dst != in_dst)
g_free(dst);
if (orig)
cr_close(orig, NULL);
if (new)
cr_close(new, NULL);
return ret;
}
int
cr_decompress_file_with_stat(const char *src,
const char *in_dst,
cr_CompressionType compression,
cr_ContentStat *stat,
GError **err)
{
int ret = CRE_OK;
int readed;
char buf[BUFFER_SIZE];
FILE *new = NULL;
CR_FILE *orig = NULL;
gchar *dst = (gchar *) in_dst;
GError *tmp_err = NULL;
assert(src);
assert(!err || *err == NULL);
// Src must be a file NOT a directory
if (!g_file_test(src, G_FILE_TEST_IS_REGULAR)) {
g_debug("%s: Source (%s) must be a regular file!", __func__, src);
g_set_error(err, ERR_DOMAIN, CRE_NOFILE,
"Not a regular file: %s", src);
return CRE_NOFILE;
}
if (compression == CR_CW_AUTO_DETECT_COMPRESSION ||
compression == CR_CW_UNKNOWN_COMPRESSION)
{
compression = cr_detect_compression(src, NULL);
}
if (compression == CR_CW_UNKNOWN_COMPRESSION) {
g_set_error(err, ERR_DOMAIN, CRE_UNKNOWNCOMPRESSION,
"Cannot detect compression type");
return CRE_UNKNOWNCOMPRESSION;
}
const char *c_suffix = cr_compression_suffix(compression);
if (!in_dst || g_str_has_suffix(in_dst, "/")) {
char *filename = cr_get_filename(src);
if (!filename) {
g_debug("%s: Cannot get filename from: %s", __func__, src);
g_set_error(err, ERR_DOMAIN, CRE_NOFILE,
"Cannot get filename from: %s", src);
return CRE_NOFILE;
}
if (g_str_has_suffix(filename, c_suffix)) {
filename = g_strndup(filename, strlen(filename) - strlen(c_suffix));
} else {
filename = g_strconcat(filename, ".decompressed", NULL);
}
if (!in_dst) {
// in_dst is NULL, use same dir as src
char *src_dir = g_strndup(src,
strlen(src) - strlen(cr_get_filename(src)));
dst = g_strconcat(src_dir, filename, NULL);
g_free(src_dir);
} else {
// in_dst is dir
dst = g_strconcat(in_dst, filename, NULL);
}
g_free(filename);
}
orig = cr_sopen(src, CR_CW_MODE_READ, compression, stat, &tmp_err);
if (orig == NULL) {
g_debug("%s: Cannot open source file %s", __func__, src);
g_propagate_prefixed_error(err, tmp_err, "Cannot open %s: ", src);
ret = CRE_IO;
goto compress_file_cleanup;
}
new = fopen(dst, "wb");
if (!new) {
const gchar * fopen_error = g_strerror(errno);
g_debug("%s: Cannot open destination file %s (%s)",
__func__, dst, fopen_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open %s: %s", src, fopen_error);
ret = CRE_IO;
goto compress_file_cleanup;
}
while ((readed = cr_read(orig, buf, BUFFER_SIZE, &tmp_err)) > 0) {
if (tmp_err) {
g_debug("%s: Error while copy %s -> %s (%s)", __func__, src,
dst, tmp_err->message);
g_propagate_prefixed_error(err, tmp_err,
"Error while read %s: ", src);
ret = CRE_IO;
goto compress_file_cleanup;
}
if (fwrite(buf, 1, readed, new) != (size_t) readed) {
const gchar * fwrite_error = g_strerror(errno);
g_debug("%s: Error while copy %s -> %s (%s)",
__func__, src, dst, fwrite_error);
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Error while write %s: %s", dst, fwrite_error);
ret = CRE_IO;
goto compress_file_cleanup;
}
}
compress_file_cleanup:
if (dst != in_dst)
g_free(dst);
if (orig)
cr_close(orig, NULL);
if (new)
fclose(new);
return ret;
}
int
cr_download(CURL *in_handle,
const char *url,
const char *in_dst,
GError **err)
{
CURL *handle = NULL;
CURLcode rcode;
char errorbuf[CURL_ERROR_SIZE];
_cleanup_free_ gchar *dst = NULL;
_cleanup_file_fclose_ FILE *file = NULL;
assert(in_handle);
assert(!err || *err == NULL);
// If destination is dir use filename from src
if (g_str_has_suffix(in_dst, "/"))
dst = g_strconcat(in_dst, cr_get_filename(url), NULL);
else if (g_file_test(in_dst, G_FILE_TEST_IS_DIR))
dst = g_strconcat(in_dst, "/", cr_get_filename(url), NULL);
else
dst = g_strdup(in_dst);
// Open dst file
file = fopen(dst, "wb");
if (!file) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open %s: %s", dst, g_strerror(errno));
remove(dst);
return CRE_IO;
}
// Dup the input handle
handle = curl_easy_duphandle(in_handle);
// Set error buffer
errorbuf[0] = '\0';
rcode = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorbuf);
if (rcode != CURLE_OK) {
curl_easy_cleanup(handle);
g_set_error(err, ERR_DOMAIN, CRE_CURL,
"curl_easy_setopt failed(CURLOPT_ERRORBUFFER): %s",
curl_easy_strerror(rcode));
return CRE_CURL;
}
// Set URL
rcode = curl_easy_setopt(handle, CURLOPT_URL, url);
if (rcode != CURLE_OK) {
curl_easy_cleanup(handle);
g_set_error(err, ERR_DOMAIN, CRE_CURL,
"curl_easy_setopt failed(CURLOPT_URL): %s",
curl_easy_strerror(rcode));
remove(dst);
return CRE_CURL;
}
// Set output file descriptor
rcode = curl_easy_setopt(handle, CURLOPT_WRITEDATA, file);
if (rcode != CURLE_OK) {
curl_easy_cleanup(handle);
g_set_error(err, ERR_DOMAIN, CRE_CURL,
"curl_easy_setopt(CURLOPT_WRITEDATA) failed: %s",
curl_easy_strerror(rcode));
remove(dst);
return CRE_CURL;
}
// Download the file
rcode = curl_easy_perform(handle);
if (rcode != CURLE_OK) {
curl_easy_cleanup(handle);
g_set_error(err, ERR_DOMAIN, CRE_CURL,
"curl_easy_perform failed: %s: %s",
curl_easy_strerror(rcode), errorbuf);
remove(dst);
return CRE_CURL;
}
curl_easy_cleanup(handle);
g_debug("%s: Successfully downloaded: %s", __func__, dst);
return CRE_OK;
}
gboolean
cr_better_copy_file(const char *src, const char *in_dst, GError **err)
{
GError *tmp_err = NULL;
assert(!err || *err == NULL);
if (!strstr(src, "://")) // Probably local path
return cr_copy_file(src, in_dst, err);
CURL *handle = curl_easy_init();
cr_download(handle, src, in_dst, &tmp_err);
curl_easy_cleanup(handle);
if (tmp_err) {
g_debug("%s: Error while downloading %s: %s", __func__, src,
tmp_err->message);
g_propagate_prefixed_error(err, tmp_err,
"Error while downloading %s: ", src);
return FALSE;
}
return TRUE;
}
int
cr_remove_dir_cb(const char *fpath,
G_GNUC_UNUSED const struct stat *sb,
G_GNUC_UNUSED int typeflag,
G_GNUC_UNUSED struct FTW *ftwbuf)
{
int rv = remove(fpath);
if (rv)
g_warning("%s: Cannot remove: %s: %s", __func__, fpath, g_strerror(errno));
return rv;
}
int
cr_remove_dir(const char *path, GError **err)
{
int ret;
assert(!err || *err == NULL);
ret = nftw(path, cr_remove_dir_cb, 64, FTW_DEPTH | FTW_PHYS);
if (ret != 0) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot remove dir %s: %s", path, g_strerror(errno));
return CRE_IO;
}
return CRE_OK;
}
gboolean
cr_move_recursive(const char *srcDir, const char *dstDir, GError **err)
{
if (rename(srcDir, dstDir) == -1) {
GFile * gsrcDir = g_file_new_for_path(srcDir);
GFile * gdstDir = g_file_new_for_path(dstDir);
if (!cr_gio_cp(gsrcDir, gdstDir, G_FILE_COPY_ALL_METADATA, NULL, err)) {
g_object_unref(gsrcDir);
g_object_unref(gdstDir);
return FALSE;
}
g_object_unref(gsrcDir);
g_object_unref(gdstDir);
return (cr_remove_dir(srcDir, err) == CRE_OK);
}
return TRUE;
}
// Return path with exactly one trailing '/'
char *
cr_normalize_dir_path(const char *path)
{
char *normalized = NULL;
if (!path)
return normalized;
int i = strlen(path);
if (i == 0) {
return g_strdup("./");
}
do { // Skip all trailing '/'
i--;
} while (i >= 0 && path[i] == '/');
normalized = g_strndup(path, i+2);
if (normalized[i+1] != '/') {
normalized[i+1] = '/';
}
return normalized;
}
struct cr_Version
cr_str_to_version(const char *str)
{
char *endptr;
const char *ptr = str;
struct cr_Version ver;
ver.major = 0;
ver.minor = 0;
ver.patch = 0;
ver.suffix = NULL;
if (!str || str[0] == '\0') {
return ver;
}
// Major chunk
ver.major = strtol(ptr, &endptr, 10);
if (!endptr || endptr[0] == '\0') {
// Whole string has been converted successfully
return ver;
} else {
if (endptr[0] == '.') {
// '.' is supposed to be delimiter -> skip it and go to next chunk
ptr = endptr+1;
} else {
ver.suffix = g_strdup(endptr);
return ver;
}
}
// Minor chunk
ver.minor = strtol(ptr, &endptr, 10);
if (!endptr || endptr[0] == '\0') {
// Whole string has been converted successfully
return ver;
} else {
if (endptr[0] == '.') {
// '.' is supposed to be delimiter -> skip it and go to next chunk
ptr = endptr+1;
} else {
ver.suffix = g_strdup(endptr);
return ver;
}
}
// Patch chunk
ver.patch = strtol(ptr, &endptr, 10);
if (!endptr || endptr[0] == '\0') {
// Whole string has been converted successfully
return ver;
} else {
if (endptr[0] != '.') { // '.' is supposed to be delimiter
ver.suffix = g_strdup(endptr);
return ver;
}
}
return ver;
}
static int
cr_compare_values(const char *str1, const char *str2)
{
if (!str1 && !str2)
return 0;
else if (str1 && !str2)
return 1;
else if (!str1 && str2)
return -1;
return rpmvercmp(str1, str2);
}
// Return values:
// 0 - versions are same
// 1 - first string is bigger version
// 2 - second string is bigger version
// Examples:
// "6.3.2azb" > "6.3.2abc"
// "2.1" < "2.1.3"
int
cr_cmp_version_str(const char* str1, const char *str2)
{
int rc = cr_compare_values(str1 ? str1 : "", str2 ? str2 : "");
if (rc == -1) rc = 2;
return rc;
}
void
cr_null_log_fn(G_GNUC_UNUSED const gchar *log_domain,
G_GNUC_UNUSED GLogLevelFlags log_level,
G_GNUC_UNUSED const gchar *message,
G_GNUC_UNUSED gpointer user_data)
{
return;
}
void
cr_log_fn(const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
gint hidden_log_levels = GPOINTER_TO_INT(user_data);
if (log_level & hidden_log_levels)
return;
switch(log_level) {
case G_LOG_LEVEL_ERROR:
if (log_domain) g_printerr("%s: ", log_domain);
g_printerr("Error: %s\n", message);
break;
case G_LOG_LEVEL_CRITICAL:
if (log_domain) g_printerr("%s: ", log_domain);
g_printerr("Critical: %s\n", message);
break;
case G_LOG_LEVEL_WARNING:
if (log_domain) g_printerr("%s: ", log_domain);
g_printerr("Warning: %s\n", message);
break;
case G_LOG_LEVEL_DEBUG: {
time_t rawtime;
struct tm * timeinfo;
char buffer[80];
time ( &rawtime );
timeinfo = localtime ( &rawtime );
strftime (buffer, 80, "%H:%M:%S", timeinfo);
//if (log_domain) g_printerr("%s: ", log_domain);
g_printerr("%s: %s\n", buffer, message);
break;
}
default:
printf("%s\n", message);
}
return;
}
void
cr_slist_free_full(GSList *list, GDestroyNotify free_f)
{
g_slist_free_full(list, free_f);
}
void cr_queue_free_full(GQueue *queue, GDestroyNotify free_f)
{
g_queue_free_full(queue, free_f);
}
cr_NEVRA *
cr_split_rpm_filename(const char *filename)
{
cr_NEVRA *nevra = NULL;
gchar *str, *epoch = NULL;
size_t len;
filename = cr_get_filename(filename);
if (!filename)
return NULL;
str = g_strdup(filename);
// N-V-R.rpm:E
if (strchr(str, ':')) {
gchar **filename_epoch = g_strsplit(str, ":", 2);
if (g_str_has_suffix(filename_epoch[0], ".rpm")) {
g_free(str);
str = filename_epoch[0];
epoch = filename_epoch[1];
g_free(filename_epoch);
} else {
g_strfreev(filename_epoch);
}
}
len = strlen(str);
// Get rid off .rpm suffix
if (len >= 4 && !strcmp(str+(len-4), ".rpm")) {
len -= 4;
str[len] = '\0';
}
nevra = cr_str_to_nevra(str);
g_free(str);
if (!nevra) {
g_free(epoch);
return NULL;
}
if (epoch) {
g_free(nevra->epoch);
nevra->epoch = epoch;
}
return nevra;
}
/** Split N-V-R:E or E:N-V-R or N-E:V-R
*/
cr_NEVR *
cr_str_to_nevr(const char *instr)
{
gchar *nvr = NULL;
gchar *epoch = NULL;
gchar **nvr_epoch_list = NULL;
cr_NEVR *nevr = NULL;
size_t len;
int i;
if (!instr)
return NULL;
// 1)
// Try to split by ':'
// If we have N-V-R:E or E:N-V-R then nvr and epoch will be filled
// If we have N-E:V-R or N-V-R then only nvr will be filed
nvr_epoch_list = g_strsplit(instr, ":", 2);
if (!nvr_epoch_list || !(*nvr_epoch_list)) {
g_strfreev(nvr_epoch_list);
return NULL;
}
nvr = nvr_epoch_list[0];
epoch = nvr_epoch_list[1]; // May be NULL
if (epoch && strchr(epoch, '-')) {
if (!strchr(nvr, '-')) {
// Switch nvr and epoch
char *tmp = nvr;
nvr = epoch;
epoch = tmp;
} else {
// Probably the N-E:V-R format, handle it after the split
g_free(nvr);
g_free(epoch);
nvr = g_strdup(instr);
epoch = NULL;
}
}
g_free(nvr_epoch_list);
// 2)
// Now split the nvr by the '-' into three parts
nevr = g_new0(cr_NEVR, 1);
len = strlen(nvr);
// Get release
for (i = len-1; i >= 0; i--)
if (nvr[i] == '-') {
nevr->release = g_strdup(nvr+i+1);
nvr[i] = '\0';
len = i;
break;
}
// Get version
for (i = len-1; i >= 0; i--)
if (nvr[i] == '-') {
nevr->version = g_strdup(nvr+i+1);
nvr[i] = '\0';
break;
}
// Get name
nevr->name = g_strdup(nvr);
g_free(nvr);
// 3)
// Now split the E:V
if (epoch == NULL && (nevr->version && strchr(nevr->version, ':'))) {
gchar **epoch_version = g_strsplit(nevr->version, ":", 2);
g_free(nevr->version);
nevr->epoch = epoch_version[0];
nevr->version = epoch_version[1];
g_free(epoch_version);
} else {
nevr->epoch = epoch;
}
return nevr;
}
void
cr_nevr_free(cr_NEVR *nevr)
{
if (!nevr)
return;
g_free(nevr->name);
g_free(nevr->epoch);
g_free(nevr->version);
g_free(nevr->release);
g_free(nevr);
}
cr_NEVRA *
cr_str_to_nevra(const char *instr)
{
cr_NEVR *nevr;
cr_NEVRA *nevra = NULL;
gchar *str, *epoch = NULL;
int i;
if (!instr)
return NULL;
nevra = g_new0(cr_NEVRA, 1);
str = g_strdup(instr);
// N-V-R.A:E
if (strchr(str, ':')) {
gchar **nvra_epoch = g_strsplit(str, ":", 2);
char *epoch_candidate = nvra_epoch[1];
if (epoch_candidate
&& !strchr(epoch_candidate, '-')
&& !strchr(epoch_candidate, '.'))
{
// Strip epoch from the very end
epoch = epoch_candidate;
g_free(str);
str = nvra_epoch[0];
g_free(nvra_epoch);
} else {
g_strfreev(nvra_epoch);
}
}
// Get arch
for (i = strlen(str)-1; i >= 0; i--)
if (str[i] == '.') {
nevra->arch = g_strdup(str+i+1);
str[i] = '\0';
break;
}
if (nevra->arch && strchr(nevra->arch, '-')) {
g_warning("Invalid arch %s", nevra->arch);
cr_nevra_free(nevra);
g_free(str);
g_free(epoch);
return NULL;
}
nevr = cr_str_to_nevr(str);
if (!nevr) {
g_warning("Invalid nevr %s", str);
cr_nevra_free(nevra);
g_free(str);
g_free(epoch);
return NULL;
}
nevra->name = nevr->name;
nevra->epoch = nevr->epoch;
nevra->version = nevr->version;
nevra->release = nevr->release;
g_free(nevr);
g_free(str);
if (epoch) {
g_free(nevra->epoch);
nevra->epoch = epoch;
}
return nevra;
}
void
cr_nevra_free(cr_NEVRA *nevra)
{
if (!nevra)
return;
g_free(nevra->name);
g_free(nevra->epoch);
g_free(nevra->version);
g_free(nevra->release);
g_free(nevra->arch);
g_free(nevra);
}
int
cr_cmp_evr(const char *e1, const char *v1, const char *r1,
const char *e2, const char *v2, const char *r2)
{
int rc;
if (e1 == NULL) e1 = "0";
if (e2 == NULL) e2 = "0";
rc = cr_compare_values(e1, e2);
if (rc) return rc;
rc = cr_compare_values(v1, v2);
if (rc) return rc;
rc = cr_compare_values(r1, r2);
return rc;
}
int
cr_warning_cb(G_GNUC_UNUSED cr_XmlParserWarningType type,
char *msg,
void *cbdata,
G_GNUC_UNUSED GError **err)
{
g_warning("%s: %s", (char *) cbdata, msg);
return CR_CB_RET_OK;
}
gboolean
cr_write_to_file(GError **err, gchar *filename, const char *format, ...)
{
assert(filename);
assert(!err || *err == NULL);
if (!format)
return TRUE;
FILE *f = fopen(filename, "w");
if (!f) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot open %s: %s", filename, g_strerror(errno));
return FALSE;
}
va_list args;
va_start(args, format);
vfprintf (f, format, args);
va_end(args);
gboolean ret = TRUE;
if (ferror(f)) {
g_set_error(err, ERR_DOMAIN, CRE_IO,
"Cannot write content to %s: %s",
filename, g_strerror(errno));
ret = FALSE;
}
fclose(f);
return ret;
}
static gboolean
cr_run_command(char **cmd, const char *working_dir, GError **err)
{
assert(cmd);
assert(!err || *err == NULL);
GError *tmp_err = NULL;
gint status = 0;
gchar *error_str = NULL;
int spawn_flags = G_SPAWN_SEARCH_PATH
| G_SPAWN_STDOUT_TO_DEV_NULL;
g_spawn_sync(working_dir,
cmd,
NULL, // envp
spawn_flags,
NULL, // child setup function
NULL, // user data for child setup
NULL, // stdout
&error_str, // stderr
&status,
&tmp_err);
if (tmp_err) {
g_free(error_str);
g_propagate_error(err, tmp_err);
return FALSE;
}
gboolean ret = cr_spawn_check_exit_status(status, &tmp_err);
if (!ret && error_str) {
// Remove newlines from error message
for (char *ptr = error_str; *ptr; ptr++)
if (*ptr == '\n') *ptr = ' ';
g_propagate_prefixed_error(err, tmp_err, "%s: ", error_str);
}
g_free(error_str);
return ret;
}
gboolean
cr_cp(const char *src,
const char *dst,
cr_CpFlags flags,
const char *working_dir,
GError **err)
{
assert(src);
assert(dst);
assert(!err || *err == NULL);
GPtrArray *argv_array = g_ptr_array_new();
g_ptr_array_add(argv_array, "cp");
if (flags & CR_CP_RECURSIVE)
g_ptr_array_add(argv_array, "-r");
if (flags & CR_CP_PRESERVE_ALL)
g_ptr_array_add(argv_array, "--preserve=all");
g_ptr_array_add(argv_array, (char *) src);
g_ptr_array_add(argv_array, (char *) dst);
g_ptr_array_add(argv_array, (char *) NULL);
gboolean ret = cr_run_command((char **) argv_array->pdata,
working_dir,
err);
g_ptr_array_free(argv_array, TRUE);
return ret;
}
gboolean
cr_gio_cp(GFile *src,
GFile *dst,
GFileCopyFlags flags,
GCancellable *cancellable,
GError **err)
{
assert(src);
assert(dst);
assert(!err || *err == NULL);
GFileType type = g_file_query_file_type(src, G_FILE_QUERY_INFO_NONE, NULL);
if (type == G_FILE_TYPE_DIRECTORY) {
g_file_make_directory(dst, cancellable, err);
g_file_copy_attributes(src, dst, flags, cancellable, err);
GFileEnumerator *enumerator = g_file_enumerate_children(src, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, cancellable, err);
for (GFileInfo *info = g_file_enumerator_next_file(enumerator, cancellable, err); info != NULL; info = g_file_enumerator_next_file(enumerator, cancellable, err)) {
const char *relative_path = g_file_info_get_name(info);
cr_gio_cp(
g_file_resolve_relative_path(src, relative_path),
g_file_resolve_relative_path(dst, relative_path),
flags, cancellable, err);
}
} else if (type == G_FILE_TYPE_REGULAR) {
g_file_copy(src, dst, flags, cancellable, NULL, NULL, err);
}
if (err != NULL) {
return TRUE;
}
else {
return FALSE;
}
}
gboolean
cr_rm(const char *path,
cr_RmFlags flags,
const char *working_dir,
GError **err)
{
assert(path);
assert(!err || *err == NULL);
GPtrArray *argv_array = g_ptr_array_new();
g_ptr_array_add(argv_array, "rm");
if (flags & CR_RM_RECURSIVE)
g_ptr_array_add(argv_array, "-r");
if (flags & CR_RM_FORCE)
g_ptr_array_add(argv_array, "-f");
g_ptr_array_add(argv_array, (char *) path);
g_ptr_array_add(argv_array, (char *) NULL);
gboolean ret = cr_run_command((char **) argv_array->pdata,
working_dir,
err);
g_ptr_array_free(argv_array, TRUE);
return ret;
}
gchar *
cr_append_pid_and_datetime(const char *str, const char *suffix)
{
struct tm * timeinfo;
struct timeval tv;
char datetime[80];
gettimeofday(&tv, NULL);
timeinfo = localtime (&(tv.tv_sec));
strftime(datetime, 80, "%Y%m%d%H%M%S", timeinfo);
gchar *result = g_strdup_printf("%s%jd.%s.%jd%s",
str ? str : "",
(intmax_t) getpid(),
datetime,
(intmax_t) tv.tv_usec,
suffix ? suffix : "");
return result;
}
gboolean
cr_spawn_check_exit_status(gint exit_status, GError **err)
{
assert(!err || *err == NULL);
if (WIFEXITED(exit_status)) {
if (WEXITSTATUS(exit_status) == 0) {
// Exit code == 0 means success
return TRUE;
} else {
g_set_error (err, ERR_DOMAIN, CRE_SPAWNERRCODE,
"Child process exited with code %ld",
(long) WEXITSTATUS(exit_status));
}
} else if (WIFSIGNALED(exit_status)) {
g_set_error (err, ERR_DOMAIN, CRE_SPAWNKILLED,
"Child process killed by signal %ld",
(long) WTERMSIG(exit_status));
} else if (WIFSTOPPED(exit_status)) {
g_set_error (err, ERR_DOMAIN, CRE_SPAWNSTOPED,
"Child process stopped by signal %ld",
(long) WSTOPSIG(exit_status));
} else {
g_set_error (err, ERR_DOMAIN, CRE_SPAWNABNORMAL,
"Child process exited abnormally");
}
return FALSE;
}
gboolean
cr_identical_files(const gchar *fn1,
const gchar *fn2,
gboolean *identical,
GError **err)
{
int rc;
GStatBuf buf1, buf2;
*identical = FALSE;
// Stat old file
rc = g_stat(fn1, &buf1);
if (rc == -1) {
if (errno == ENOENT) // The first file doesn't exist
return TRUE;
g_set_error(err, CREATEREPO_C_ERROR, CRE_IO,
"Cannot stat %s: %s", fn1, g_strerror(errno));
return FALSE;
}
// Stat new file
rc = g_stat(fn2, &buf2);
if (rc == -1) {
if (errno == ENOENT) // The second file doesn't exist
return TRUE;
g_set_error(err, CREATEREPO_C_ERROR, CRE_IO,
"Cannot stat %s: %s", fn2, g_strerror(errno));
return FALSE;
}
// Check if both paths point to the same file
if (buf1.st_ino == buf2.st_ino)
*identical = TRUE;
return TRUE;
}
gchar *
cr_cut_dirs(gchar *path, gint cut_dirs)
{
if (!path)
return NULL;
if (cut_dirs < 1)
return path;
gchar *last_component = NULL;
for (gchar *p = path; *p; p++) {
if (*p == '/')
last_component = p;
}
if (last_component == NULL)
return path;
gchar *cut = path;
gint n = 0;
gint state = 0;
for (gchar *p = path; p <= last_component; p++) {
if (state == 0) {
if (*p == '/') {
cut = p;
} else {
state = 1;
if (n == cut_dirs)
break;
}
} else if (state == 1) {
if (*p == '/') {
cut = p;
state = 0;
n++;
}
}
}
return cut+1;
}
const gchar *
cr_version_string_with_features(void)
{
return (xstr(CR_VERSION_MAJOR)
"."
xstr(CR_VERSION_MINOR)
"."
xstr(CR_VERSION_PATCH)
" (Features: "
#ifdef CR_DELTA_RPM_SUPPORT
"DeltaRPM "
#endif
#ifdef ENABLE_LEGACY_WEAKDEPS
"LegacyWeakdeps "
#endif
#ifdef ENABLE_THREADED_XZ_ENCODER
"ThreadedXzEncoder "
#endif
")");
}
gchar *
cr_get_dict_file(const gchar *dir, const gchar *file)
{
gchar *dict_file = malloc(strlen(file) + 7);
assert(dict_file);
snprintf(dict_file, strlen(file) + 7, "%s.zdict", file);
gchar *full_path = g_build_path("/", dir, dict_file, NULL);
assert(full_path);
free(dict_file);
if (!g_file_test(full_path, G_FILE_TEST_EXISTS)) {
g_warning("%s: Zchunk dict %s doesn't exist", __func__, full_path);
g_free(full_path);
return NULL;
}
return full_path;
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/mirrors/createrepo_c.git
git@gitee.com:mirrors/createrepo_c.git
mirrors
createrepo_c
createrepo_c
master

搜索帮助