Fossil SCM

fossil-scm / src / xfer.c
Blame History Raw 3083 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code to implement the file transfer protocol.
19
*/
20
#include "config.h"
21
#include "xfer.h"
22
23
#include <time.h>
24
25
/*
26
** Maximum number of HTTP redirects that any http_exchange() call will
27
** follow before throwing a fatal error. Most browsers use a limit of 20.
28
*/
29
#define MAX_REDIRECTS 20
30
31
/*
32
** This structure holds information about the current state of either
33
** a client or a server that is participating in xfer.
34
*/
35
typedef struct Xfer Xfer;
36
struct Xfer {
37
Blob *pIn; /* Input text from the other side */
38
Blob *pOut; /* Compose our reply here */
39
Blob line; /* The current line of input */
40
Blob aToken[6]; /* Tokenized version of line */
41
Blob err; /* Error message text */
42
int nToken; /* Number of tokens in line */
43
int nIGotSent; /* Number of "igot" cards sent */
44
int nPrivIGot; /* Number of private "igot" cards */
45
int nGimmeSent; /* Number of gimme cards sent */
46
int nFileSent; /* Number of files sent */
47
int nDeltaSent; /* Number of deltas sent */
48
int nFileRcvd; /* Number of files received */
49
int nDeltaRcvd; /* Number of deltas received */
50
int nDanglingFile; /* Number of dangling deltas received */
51
int mxSend; /* Stop sending "file" when pOut reaches this size */
52
int resync; /* Send igot cards for all holdings */
53
u8 syncPrivate; /* True to enable syncing private content */
54
u8 nextIsPrivate; /* If true, next "file" received is a private */
55
u32 remoteVersion; /* Version of fossil running on the other side */
56
u32 remoteDate; /* Date for specific client software edition */
57
u32 remoteTime; /* Time of date corresponding on remoteDate */
58
time_t maxTime; /* Time when this transfer should be finished */
59
};
60
61
62
/*
63
** The input blob contains an artifact. Convert it into a record ID.
64
** Create a phantom record if no prior record exists and
65
** phantomize is true.
66
**
67
** Compare to uuid_to_rid(). This routine takes a blob argument
68
** and does less error checking.
69
*/
70
static int rid_from_uuid(Blob *pUuid, int phantomize, int isPrivate){
71
static Stmt q;
72
int rid;
73
74
db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
75
db_bind_str(&q, ":uuid", pUuid);
76
if( db_step(&q)==SQLITE_ROW ){
77
rid = db_column_int(&q, 0);
78
}else{
79
rid = 0;
80
}
81
db_reset(&q);
82
if( rid==0 && phantomize ){
83
rid = content_new(blob_str(pUuid), isPrivate);
84
}
85
return rid;
86
}
87
88
/*
89
** Remember that the other side of the connection already has a copy
90
** of the file rid.
91
*/
92
static void remote_has(int rid){
93
if( rid ){
94
static Stmt q;
95
db_static_prepare(&q, "INSERT OR IGNORE INTO onremote VALUES(:r)");
96
db_bind_int(&q, ":r", rid);
97
db_step(&q);
98
db_reset(&q);
99
}
100
}
101
102
/*
103
** Remember that the other side of the connection lacks a copy of
104
** the artifact with the given hash.
105
*/
106
static void remote_unk(Blob *pHash){
107
static Stmt q;
108
db_static_prepare(&q, "INSERT OR IGNORE INTO unk VALUES(:h)");
109
db_bind_text(&q, ":h", blob_str(pHash));
110
db_step(&q);
111
db_reset(&q);
112
}
113
114
/*
115
** The aToken[0..nToken-1] blob array is a parse of a "file" line
116
** message. This routine finishes parsing that message and does
117
** a record insert of the file.
118
**
119
** The file line is in one of the following two forms:
120
**
121
** file HASH SIZE \n CONTENT
122
** file HASH DELTASRC SIZE \n CONTENT
123
**
124
** The content is SIZE bytes immediately following the newline.
125
** If DELTASRC exists, then the CONTENT is a delta against the
126
** content of DELTASRC.
127
**
128
** If any error occurs, write a message into pErr which has already
129
** be initialized to an empty string.
130
**
131
** Any artifact successfully received by this routine is considered to
132
** be public and is therefore removed from the "private" table.
133
*/
134
static void xfer_accept_file(
135
Xfer *pXfer,
136
int cloneFlag,
137
char **pzUuidList,
138
int *pnUuidList
139
){
140
int n;
141
int rid;
142
int srcid = 0;
143
Blob content;
144
int isPriv;
145
Blob *pUuid;
146
147
isPriv = pXfer->nextIsPrivate;
148
pXfer->nextIsPrivate = 0;
149
if( pXfer->nToken<3
150
|| pXfer->nToken>4
151
|| !blob_is_hname(&pXfer->aToken[1])
152
|| !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
153
|| n<0
154
|| (pXfer->nToken==4 && !blob_is_hname(&pXfer->aToken[2]))
155
){
156
blob_appendf(&pXfer->err, "malformed file line");
157
return;
158
}
159
blob_zero(&content);
160
blob_extract(pXfer->pIn, n, &content);
161
pUuid = &pXfer->aToken[1];
162
if( !cloneFlag && uuid_is_shunned(blob_str(pUuid)) ){
163
/* Ignore files that have been shunned */
164
blob_reset(&content);
165
return;
166
}
167
if( isPriv && !g.perm.Private ){
168
/* Do not accept private files if not authorized */
169
blob_reset(&content);
170
return;
171
}
172
if( cloneFlag ){
173
if( pXfer->nToken==4 ){
174
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
175
pXfer->nDeltaRcvd++;
176
}else{
177
srcid = 0;
178
pXfer->nFileRcvd++;
179
}
180
rid = content_put_ex(&content, blob_str(pUuid), srcid,
181
0, isPriv);
182
Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid),
183
blob_size(pUuid));
184
remote_has(rid);
185
blob_reset(&content);
186
return;
187
}
188
if( pXfer->nToken==4 ){
189
Blob src, next;
190
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
191
if( content_get(srcid, &src)==0 ){
192
rid = content_put_ex(&content, blob_str(pUuid), srcid,
193
0, isPriv);
194
Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid),
195
blob_size(pUuid));
196
pXfer->nDanglingFile++;
197
db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
198
if( !isPriv ) content_make_public(rid);
199
blob_reset(&src);
200
blob_reset(&content);
201
return;
202
}
203
pXfer->nDeltaRcvd++;
204
blob_delta_apply(&src, &content, &next);
205
blob_reset(&src);
206
blob_reset(&content);
207
content = next;
208
}else{
209
pXfer->nFileRcvd++;
210
}
211
if( hname_verify_hash(&content, blob_buffer(pUuid), blob_size(pUuid))==0 ){
212
blob_appendf(&pXfer->err, "wrong hash on received artifact: %b", pUuid);
213
}
214
rid = content_put_ex(&content, blob_str(pUuid), 0, 0, isPriv);
215
Th_AppendToList(pzUuidList, pnUuidList, blob_str(pUuid), blob_size(pUuid));
216
if( rid==0 ){
217
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
218
blob_reset(&content);
219
}else{
220
if( !isPriv ) content_make_public(rid);
221
manifest_crosslink(rid, &content, MC_NO_ERRORS);
222
}
223
assert( blob_is_reset(&content) );
224
remote_has(rid);
225
}
226
227
/*
228
** The aToken[0..nToken-1] blob array is a parse of a "cfile" line
229
** message. This routine finishes parsing that message and does
230
** a record insert of the file. The difference between "file" and
231
** "cfile" is that with "cfile" the content is already compressed.
232
**
233
** The file line is in one of the following two forms:
234
**
235
** cfile HASH USIZE CSIZE \n CONTENT
236
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
237
**
238
** The content is CSIZE bytes immediately following the newline.
239
** If DELTASRC exists, then the CONTENT is a delta against the
240
** content of DELTASRC.
241
**
242
** The original size of the HASH artifact is USIZE.
243
**
244
** If any error occurs, write a message into pErr which has already
245
** be initialized to an empty string.
246
**
247
** Any artifact successfully received by this routine is considered to
248
** be public and is therefore removed from the "private" table.
249
*/
250
static void xfer_accept_compressed_file(
251
Xfer *pXfer,
252
char **pzUuidList,
253
int *pnUuidList
254
){
255
int szC; /* CSIZE */
256
int szU; /* USIZE */
257
int rid;
258
int srcid = 0;
259
Blob content;
260
int isPriv;
261
262
isPriv = pXfer->nextIsPrivate;
263
pXfer->nextIsPrivate = 0;
264
if( pXfer->nToken<4
265
|| pXfer->nToken>5
266
|| !blob_is_hname(&pXfer->aToken[1])
267
|| !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
268
|| !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
269
|| szC<0 || szU<0
270
|| (pXfer->nToken==5 && !blob_is_hname(&pXfer->aToken[2]))
271
){
272
blob_appendf(&pXfer->err, "malformed cfile line");
273
return;
274
}
275
if( isPriv && !g.perm.Private ){
276
/* Do not accept private files if not authorized */
277
return;
278
}
279
blob_zero(&content);
280
blob_extract(pXfer->pIn, szC, &content);
281
if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
282
/* Ignore files that have been shunned */
283
blob_reset(&content);
284
return;
285
}
286
if( pXfer->nToken==5 ){
287
srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
288
pXfer->nDeltaRcvd++;
289
}else{
290
srcid = 0;
291
pXfer->nFileRcvd++;
292
}
293
rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
294
szC, isPriv);
295
Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]),
296
blob_size(&pXfer->aToken[1]));
297
remote_has(rid);
298
blob_reset(&content);
299
}
300
301
/*
302
** The aToken[0..nToken-1] blob array is a parse of a "uvfile" line
303
** message. This routine finishes parsing that message and adds the
304
** unversioned file to the "unversioned" table.
305
**
306
** The file line is in one of the following two forms:
307
**
308
** uvfile NAME MTIME HASH SIZE FLAGS
309
** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
310
**
311
** If the 0x0001 bit of FLAGS is set, that means the file has been
312
** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted.
313
**
314
** SIZE is the number of bytes of CONTENT. The CONTENT is uncompressed.
315
** HASH is the artifact hash of CONTENT.
316
**
317
** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted.
318
** The sender might have omitted the content because it is too big to
319
** transmit, or because it is unchanged and this record exists purely
320
** to update the MTIME.
321
*/
322
static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){
323
sqlite3_int64 mtime; /* The MTIME */
324
Blob *pHash; /* The HASH value */
325
int sz; /* The SIZE */
326
int flags; /* The FLAGS */
327
Blob content; /* The CONTENT */
328
Blob x; /* Compressed content */
329
Stmt q; /* SQL statements for comparison and insert */
330
int isDelete; /* HASH is "-" indicating this is a delete */
331
int nullContent; /* True of CONTENT is NULL */
332
int iStatus; /* Result from unversioned_status() */
333
334
pHash = &pXfer->aToken[3];
335
if( pXfer->nToken==5
336
|| !blob_is_filename(&pXfer->aToken[1])
337
|| !blob_is_int64(&pXfer->aToken[2], &mtime)
338
|| (!blob_eq(pHash,"-") && !blob_is_hname(pHash))
339
|| !blob_is_int(&pXfer->aToken[4], &sz)
340
|| !blob_is_int(&pXfer->aToken[5], &flags)
341
|| (mtime<0 || sz<0 || flags<0)
342
){
343
blob_appendf(&pXfer->err, "malformed uvfile line");
344
return;
345
}
346
blob_init(&content, 0, 0);
347
blob_init(&x, 0, 0);
348
if( sz>0 && (flags & 0x0005)==0 ){
349
blob_extract(pXfer->pIn, sz, &content);
350
nullContent = 0;
351
if( hname_verify_hash(&content, blob_buffer(pHash), blob_size(pHash))==0 ){
352
blob_appendf(&pXfer->err, "in uvfile line, HASH does not match CONTENT");
353
goto end_accept_unversioned_file;
354
}
355
}else{
356
nullContent = 1;
357
}
358
359
/* The isWriter flag must be true in order to land the new file */
360
if( !isWriter ){
361
blob_appendf(&pXfer->err,"Write permissions for unversioned files missing");
362
goto end_accept_unversioned_file;
363
}
364
365
/* Make sure we have a valid g.rcvid marker */
366
content_rcvid_init(0);
367
368
/* Check to see if current content really should be overwritten. Ideally,
369
** a uvfile card should never have been sent unless the overwrite should
370
** occur. But do not trust the sender. Double-check.
371
*/
372
iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime,
373
blob_str(pHash));
374
if( iStatus>=3 ) goto end_accept_unversioned_file;
375
376
/* Store the content */
377
isDelete = blob_eq(pHash, "-");
378
if( isDelete ){
379
db_prepare(&q,
380
"UPDATE unversioned"
381
" SET rcvid=:rcvid, mtime=:mtime, hash=NULL,"
382
" sz=0, encoding=0, content=NULL"
383
" WHERE name=:name"
384
);
385
db_bind_int(&q, ":rcvid", g.rcvid);
386
}else if( iStatus==2 ){
387
db_prepare(&q, "UPDATE unversioned SET mtime=:mtime WHERE name=:name");
388
}else{
389
db_prepare(&q,
390
"REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
391
" VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
392
);
393
db_bind_int(&q, ":rcvid", g.rcvid);
394
db_bind_text(&q, ":hash", blob_str(pHash));
395
db_bind_int(&q, ":sz", blob_size(&content));
396
if( !nullContent ){
397
blob_compress(&content, &x);
398
if( blob_size(&x) < 0.8*blob_size(&content) ){
399
db_bind_blob(&q, ":content", &x);
400
db_bind_int(&q, ":encoding", 1);
401
}else{
402
db_bind_blob(&q, ":content", &content);
403
db_bind_int(&q, ":encoding", 0);
404
}
405
}else{
406
db_bind_int(&q, ":encoding", 0);
407
}
408
}
409
db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1]));
410
db_bind_int64(&q, ":mtime", mtime);
411
db_step(&q);
412
db_finalize(&q);
413
db_unset("uv-hash", 0);
414
415
end_accept_unversioned_file:
416
blob_reset(&x);
417
blob_reset(&content);
418
}
419
420
/*
421
** Try to send a file as a delta against its parent.
422
** If successful, return the number of bytes in the delta.
423
** If we cannot generate an appropriate delta, then send
424
** nothing and return zero.
425
**
426
** Never send a delta against a private artifact.
427
*/
428
static int send_delta_parent(
429
Xfer *pXfer, /* The transfer context */
430
int rid, /* record id of the file to send */
431
int isPrivate, /* True if rid is a private artifact */
432
Blob *pContent, /* The content of the file to send */
433
Blob *pUuid /* The HASH of the file to send */
434
){
435
static const char *const azQuery[] = {
436
"SELECT pid FROM plink x"
437
" WHERE cid=%d"
438
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",
439
440
"SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
441
" WHERE fid=%d"
442
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
443
};
444
int i;
445
Blob src, delta;
446
int size = 0;
447
int srcId = 0;
448
449
for(i=0; srcId==0 && i<count(azQuery); i++){
450
srcId = db_int(0, azQuery[i] /*works-like:"%d"*/, rid);
451
}
452
if( srcId>0
453
&& (pXfer->syncPrivate || !content_is_private(srcId))
454
&& content_get(srcId, &src)
455
){
456
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
457
blob_delta_create(&src, pContent, &delta);
458
size = blob_size(&delta);
459
if( size>=(int)blob_size(pContent)-50 ){
460
size = 0;
461
}else if( uuid_is_shunned(zUuid) ){
462
size = 0;
463
}else{
464
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
465
blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
466
blob_append(pXfer->pOut, blob_buffer(&delta), size);
467
}
468
blob_reset(&delta);
469
free(zUuid);
470
blob_reset(&src);
471
}
472
return size;
473
}
474
475
/*
476
** Try to send a file as a native delta.
477
** If successful, return the number of bytes in the delta.
478
** If we cannot generate an appropriate delta, then send
479
** nothing and return zero.
480
**
481
** Never send a delta against a private artifact.
482
*/
483
static int send_delta_native(
484
Xfer *pXfer, /* The transfer context */
485
int rid, /* record id of the file to send */
486
int isPrivate, /* True if rid is a private artifact */
487
Blob *pUuid /* The HASH of the file to send */
488
){
489
Blob src, delta;
490
int size = 0;
491
int srcId;
492
493
srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
494
if( srcId>0
495
&& (pXfer->syncPrivate || !content_is_private(srcId))
496
){
497
blob_zero(&src);
498
db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
499
if( uuid_is_shunned(blob_str(&src)) ){
500
blob_reset(&src);
501
return 0;
502
}
503
blob_zero(&delta);
504
db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
505
blob_uncompress(&delta, &delta);
506
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
507
blob_appendf(pXfer->pOut, "file %b %b %d\n",
508
pUuid, &src, blob_size(&delta));
509
blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
510
size = blob_size(&delta);
511
blob_reset(&delta);
512
blob_reset(&src);
513
}else{
514
size = 0;
515
}
516
return size;
517
}
518
519
/*
520
** Push an error message to alert the older client that the repository
521
** has SHA3 content and cannot be synced or cloned.
522
*/
523
static void xfer_cannot_send_sha3_error(Xfer *pXfer){
524
blob_appendf(pXfer->pOut,
525
"error Fossil\\sversion\\s2.0\\sor\\slater\\srequired.\n"
526
);
527
}
528
529
530
/*
531
** Send the file identified by rid.
532
**
533
** The pUuid can be NULL in which case the correct hash is computed
534
** from the rid.
535
**
536
** Try to send the file as a native delta if nativeDelta is true, or
537
** as a parent delta if nativeDelta is false.
538
**
539
** It should never be the case that rid is a private artifact. But
540
** as a precaution, this routine does check on rid and if it is private
541
** this routine becomes a no-op.
542
*/
543
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
544
Blob content, uuid;
545
int size = 0;
546
int isPriv = content_is_private(rid);
547
548
if( isPriv && pXfer->syncPrivate==0 ){
549
if( pXfer->remoteDate>=20200413 && pUuid && blob_size(pUuid)>0 ){
550
/* If the artifact is private and we are not doing a private sync,
551
** at least tell the other side that the artifact exists and is
552
** known to be private. But only do this for newer clients since
553
** older ones will throw an error if they get a private igot card
554
** and private syncing is disallowed */
555
blob_appendf(pXfer->pOut, "igot %b 1\n", pUuid);
556
pXfer->nIGotSent++;
557
pXfer->nPrivIGot++;
558
}
559
return;
560
}
561
if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
562
return;
563
}
564
blob_zero(&uuid);
565
db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
566
if( blob_size(&uuid)==0 ){
567
return;
568
}
569
if( blob_size(&uuid)>HNAME_LEN_SHA1 && pXfer->remoteVersion<20000 ){
570
xfer_cannot_send_sha3_error(pXfer);
571
return;
572
}
573
if( pUuid ){
574
if( blob_compare(pUuid, &uuid)!=0 ){
575
blob_reset(&uuid);
576
return;
577
}
578
}else{
579
pUuid = &uuid;
580
}
581
if( uuid_is_shunned(blob_str(pUuid)) ){
582
blob_reset(&uuid);
583
return;
584
}
585
if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) ||
586
pXfer->mxSend<=(int)blob_size(pXfer->pOut) ){
587
const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
588
blob_appendf(pXfer->pOut, zFormat /*works-like:"%b"*/, pUuid);
589
pXfer->nIGotSent++;
590
blob_reset(&uuid);
591
return;
592
}
593
if( nativeDelta ){
594
size = send_delta_native(pXfer, rid, isPriv, pUuid);
595
if( size ){
596
pXfer->nDeltaSent++;
597
}
598
}
599
if( size==0 ){
600
content_get(rid, &content);
601
602
if( !nativeDelta && blob_size(&content)>100 ){
603
size = send_delta_parent(pXfer, rid, isPriv, &content, pUuid);
604
}
605
if( size==0 ){
606
int size = blob_size(&content);
607
if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
608
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
609
blob_append(pXfer->pOut, blob_buffer(&content), size);
610
pXfer->nFileSent++;
611
}else{
612
pXfer->nDeltaSent++;
613
}
614
blob_reset(&content);
615
}
616
remote_has(rid);
617
blob_reset(&uuid);
618
#if 0
619
if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
620
blob_append(pXfer->pOut, "\n", 1);
621
}
622
#endif
623
}
624
625
/*
626
** Send the file identified by rid as a compressed artifact. Basically,
627
** send the content exactly as it appears in the BLOB table using
628
** a "cfile" card.
629
*/
630
static void send_compressed_file(Xfer *pXfer, int rid){
631
const char *zContent;
632
const char *zUuid;
633
const char *zDelta;
634
int szU;
635
int szC;
636
int rc;
637
int isPrivate;
638
int srcIsPrivate;
639
static Stmt q1;
640
Blob fullContent;
641
642
isPrivate = content_is_private(rid);
643
if( isPrivate && pXfer->syncPrivate==0 ) return;
644
db_static_prepare(&q1,
645
"SELECT uuid, size, content, delta.srcid IN private,"
646
" (SELECT uuid FROM blob WHERE rid=delta.srcid)"
647
" FROM blob LEFT JOIN delta ON (blob.rid=delta.rid)"
648
" WHERE blob.rid=:rid"
649
" AND blob.size>=0"
650
" AND NOT EXISTS(SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)"
651
);
652
db_bind_int(&q1, ":rid", rid);
653
rc = db_step(&q1);
654
if( rc==SQLITE_ROW ){
655
zUuid = db_column_text(&q1, 0);
656
szU = db_column_int(&q1, 1);
657
szC = db_column_bytes(&q1, 2);
658
zContent = db_column_raw(&q1, 2);
659
srcIsPrivate = db_column_int(&q1, 3);
660
zDelta = db_column_text(&q1, 4);
661
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
662
if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,0)!=HNAME_LEN_SHA1 ){
663
xfer_cannot_send_sha3_error(pXfer);
664
db_reset(&q1);
665
return;
666
}
667
blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
668
if( !isPrivate && srcIsPrivate ){
669
content_get(rid, &fullContent);
670
szU = blob_size(&fullContent);
671
blob_compress(&fullContent, &fullContent);
672
szC = blob_size(&fullContent);
673
zContent = blob_buffer(&fullContent);
674
zDelta = 0;
675
}
676
if( zDelta ){
677
blob_appendf(pXfer->pOut, "%s ", zDelta);
678
pXfer->nDeltaSent++;
679
}else{
680
pXfer->nFileSent++;
681
}
682
blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
683
blob_append(pXfer->pOut, zContent, szC);
684
if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
685
blob_append(pXfer->pOut, "\n", 1);
686
}
687
if( !isPrivate && srcIsPrivate ){
688
blob_reset(&fullContent);
689
}
690
}
691
db_reset(&q1);
692
}
693
694
/*
695
** Send the unversioned file identified by zName by generating the
696
** appropriate "uvfile" card.
697
**
698
** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
699
**
700
** If the noContent flag is set, omit the CONTENT and set the 0x0004
701
** flag in FLAGS.
702
*/
703
static void send_unversioned_file(
704
Xfer *pXfer, /* Transfer context */
705
const char *zName, /* Name of unversioned file to be sent */
706
int noContent /* True to omit the content */
707
){
708
Stmt q1;
709
710
if( (int)blob_size(pXfer->pOut)>=pXfer->mxSend ) noContent = 1;
711
if( noContent ){
712
db_prepare(&q1,
713
"SELECT mtime, hash, encoding, sz FROM unversioned WHERE name=%Q",
714
zName
715
);
716
}else{
717
db_prepare(&q1,
718
"SELECT mtime, hash, encoding, sz, content FROM unversioned"
719
" WHERE name=%Q",
720
zName
721
);
722
}
723
if( db_step(&q1)==SQLITE_ROW ){
724
sqlite3_int64 mtime = db_column_int64(&q1, 0);
725
const char *zHash = db_column_text(&q1, 1);
726
if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,1)>HNAME_LEN_SHA1 ){
727
xfer_cannot_send_sha3_error(pXfer);
728
db_reset(&q1);
729
return;
730
}
731
if( (int)blob_size(pXfer->pOut)>=pXfer->mxSend ){
732
/* If we have already reached the send size limit, send a (short)
733
** uvigot card rather than a uvfile card. This only happens on the
734
** server side. The uvigot card will provoke the client to resend
735
** another uvgimme on the next cycle. */
736
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
737
zName, mtime, zHash, db_column_int(&q1,3));
738
}else{
739
blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime);
740
if( zHash==0 ){
741
blob_append(pXfer->pOut, " - 0 1\n", -1);
742
}else if( noContent ){
743
blob_appendf(pXfer->pOut, " %s %d 4\n", zHash, db_column_int(&q1,3));
744
}else{
745
Blob content;
746
blob_init(&content, 0, 0);
747
db_column_blob(&q1, 4, &content);
748
if( db_column_int(&q1, 2) ){
749
blob_uncompress(&content, &content);
750
}
751
blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content));
752
blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content));
753
blob_reset(&content);
754
}
755
}
756
}
757
db_finalize(&q1);
758
}
759
760
/*
761
** Send a gimme message for every phantom.
762
**
763
** Except: do not request shunned artifacts. And do not request
764
** private artifacts if we are not doing a private transfer.
765
*/
766
static void request_phantoms(Xfer *pXfer, int maxReq){
767
Stmt q;
768
db_prepare(&q,
769
"SELECT uuid FROM phantom CROSS JOIN blob USING(rid) /*scan*/"
770
" WHERE NOT EXISTS(SELECT 1 FROM unk WHERE unk.uuid=blob.uuid)"
771
" AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
772
(pXfer->syncPrivate ? "" :
773
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
774
);
775
while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
776
const char *zUuid = db_column_text(&q, 0);
777
blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
778
pXfer->nGimmeSent++;
779
}
780
db_finalize(&q);
781
}
782
783
/*
784
** Compute an hash on the tail of pMsg. Verify that it matches the
785
** the hash given in pHash. Return non-zero for an error and 0 on success.
786
**
787
** The type of hash computed (SHA1, SHA3-256) is determined by
788
** the length of the input hash in pHash.
789
*/
790
static int check_tail_hash(Blob *pHash, Blob *pMsg){
791
Blob tail;
792
int rc;
793
blob_tail(pMsg, &tail);
794
rc = hname_verify_hash(&tail, blob_buffer(pHash), blob_size(pHash));
795
blob_reset(&tail);
796
return rc==HNAME_ERROR;
797
}
798
799
/*
800
** Check the signature on an application/x-fossil payload received by
801
** the HTTP server. The signature is a line of the following form:
802
**
803
** login LOGIN NONCE SIGNATURE
804
**
805
** The NONCE is the SHA1 hash of the remainder of the input.
806
** SIGNATURE is the SHA1 checksum of the NONCE concatenated
807
** with the sha1_shared_secret() encoding of the users password.
808
**
809
** SIGNATURE = sha1_sum( NONCE + sha1_shared_secret(PASSWORD) );
810
**
811
** The parameters to this routine are ephemeral blobs holding the
812
** LOGIN, NONCE and SIGNATURE.
813
**
814
** This routine attempts to locate the user and verify the signature.
815
** If everything checks out, the USER.CAP column for the USER table
816
** is consulted to set privileges in the global g variable.
817
**
818
** If anything fails to check out, no changes are made to privileges.
819
**
820
** Signature generation on the client side is handled by the
821
** http_exchange() routine.
822
**
823
** Return non-zero for a login failure and zero for success.
824
*/
825
static int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
826
Stmt q;
827
int rc = -1;
828
char *zLogin = blob_terminate(pLogin);
829
defossilize(zLogin);
830
831
if( fossil_strcmp(zLogin, "nobody")==0
832
|| fossil_strcmp(zLogin, "anonymous")==0
833
){
834
return 0; /* Anybody is allowed to sync as "nobody" or "anonymous" */
835
}
836
if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0
837
&& db_get_boolean("remote_user_ok",0) ){
838
return 0; /* Accept Basic Authorization */
839
}
840
db_prepare(&q,
841
"SELECT pw, cap, uid FROM user"
842
" WHERE login=%Q"
843
" AND login NOT IN ('anonymous','nobody','developer','reader')"
844
" AND length(pw)>0",
845
zLogin
846
);
847
if( db_step(&q)==SQLITE_ROW ){
848
int szPw;
849
Blob pw, combined, hash;
850
blob_zero(&pw);
851
db_ephemeral_blob(&q, 0, &pw);
852
szPw = blob_size(&pw);
853
blob_zero(&combined);
854
blob_copy(&combined, pNonce);
855
blob_append(&combined, blob_buffer(&pw), szPw);
856
sha1sum_blob(&combined, &hash);
857
assert( blob_size(&hash)==40 );
858
rc = blob_constant_time_cmp(&hash, pSig);
859
blob_reset(&hash);
860
blob_reset(&combined);
861
if( rc!=0 && szPw!=40 ){
862
/* If this server stores cleartext passwords and the password did not
863
** match, then perhaps the client is sending SHA1 passwords. Try
864
** again with the SHA1 password.
865
*/
866
const char *zPw = db_column_text(&q, 0);
867
char *zSecret = sha1_shared_secret(zPw, blob_str(pLogin), 0);
868
blob_zero(&combined);
869
blob_copy(&combined, pNonce);
870
blob_append(&combined, zSecret, -1);
871
fossil_free(zSecret);
872
sha1sum_blob(&combined, &hash);
873
rc = blob_constant_time_cmp(&hash, pSig);
874
blob_reset(&hash);
875
blob_reset(&combined);
876
}
877
if( rc==0 ){
878
const char *zCap;
879
zCap = db_column_text(&q, 1);
880
login_set_capabilities(zCap, 0);
881
g.userUid = db_column_int(&q, 2);
882
g.zLogin = mprintf("%b", pLogin);
883
g.zNonce = mprintf("%b", pNonce);
884
}
885
}
886
db_finalize(&q);
887
return rc;
888
}
889
890
/*
891
** Send the content of all files in the unsent table.
892
**
893
** This is really just an optimization. If you clear the
894
** unsent table, all the right files will still get transferred.
895
** It just might require an extra round trip or two.
896
*/
897
static void send_unsent(Xfer *pXfer){
898
Stmt q;
899
db_prepare(&q, "SELECT rid FROM unsent EXCEPT SELECT rid FROM private");
900
while( db_step(&q)==SQLITE_ROW ){
901
int rid = db_column_int(&q, 0);
902
send_file(pXfer, rid, 0, 0);
903
}
904
db_finalize(&q);
905
db_multi_exec("DELETE FROM unsent");
906
}
907
908
/*
909
** Check to see if the number of unclustered entries is greater than
910
** 100 and if it is, form a new cluster. Unclustered phantoms do not
911
** count toward the 100 total. And phantoms are never added to a new
912
** cluster.
913
*/
914
void create_cluster(void){
915
Blob cluster, cksum;
916
Blob deleteWhere;
917
Stmt q;
918
int nUncl;
919
int nRow = 0;
920
int rid;
921
922
#if 0
923
/* We should not ever get any private artifacts in the unclustered table.
924
** But if we do (because of a bug) now is a good time to delete them. */
925
db_multi_exec(
926
"DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
927
);
928
#endif
929
930
nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/"
931
" WHERE NOT EXISTS(SELECT 1 FROM phantom"
932
" WHERE rid=unclustered.rid)");
933
if( nUncl>=100 ){
934
blob_zero(&cluster);
935
blob_zero(&deleteWhere);
936
db_prepare(&q, "SELECT uuid FROM unclustered, blob"
937
" WHERE NOT EXISTS(SELECT 1 FROM phantom"
938
" WHERE rid=unclustered.rid)"
939
" AND unclustered.rid=blob.rid"
940
" AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
941
" ORDER BY 1");
942
while( db_step(&q)==SQLITE_ROW ){
943
blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0));
944
nRow++;
945
if( nRow>=800 && nUncl>nRow+100 ){
946
md5sum_blob(&cluster, &cksum);
947
blob_appendf(&cluster, "Z %b\n", &cksum);
948
blob_reset(&cksum);
949
rid = content_put(&cluster);
950
manifest_crosslink(rid, &cluster, MC_NONE);
951
blob_reset(&cluster);
952
nUncl -= nRow;
953
nRow = 0;
954
blob_append_sql(&deleteWhere, ",%d", rid);
955
}
956
}
957
db_finalize(&q);
958
db_multi_exec(
959
"DELETE FROM unclustered WHERE rid NOT IN (0 %s)"
960
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=unclustered.rid)",
961
blob_sql_text(&deleteWhere)
962
);
963
blob_reset(&deleteWhere);
964
if( nRow>0 ){
965
md5sum_blob(&cluster, &cksum);
966
blob_appendf(&cluster, "Z %b\n", &cksum);
967
blob_reset(&cksum);
968
rid = content_put(&cluster);
969
manifest_crosslink(rid, &cluster, MC_NONE);
970
blob_reset(&cluster);
971
}
972
}
973
}
974
975
/*
976
** Send igot messages for every private artifact
977
*/
978
static int send_private(Xfer *pXfer){
979
int cnt = 0;
980
Stmt q;
981
if( pXfer->syncPrivate ){
982
db_prepare(&q, "SELECT uuid FROM private JOIN blob USING(rid)");
983
while( db_step(&q)==SQLITE_ROW ){
984
blob_appendf(pXfer->pOut, "igot %s 1\n", db_column_text(&q,0));
985
cnt++;
986
}
987
db_finalize(&q);
988
}
989
return cnt;
990
}
991
992
/*
993
** Send an igot message for every entry in unclustered table.
994
** Return the number of cards sent.
995
**
996
** Except:
997
** * Do not send igot cards for shunned artifacts
998
** * Do not send igot cards for phantoms
999
** * Do not send igot cards for private artifacts
1000
** * Do not send igot cards for any artifact that is in the
1001
** ONREMOTE table, if that table exists.
1002
**
1003
** If the pXfer->resync flag is set, that means we are doing a "--verily"
1004
** sync and all artifacts that don't meet the restrictions above should
1005
** be sent.
1006
*/
1007
static int send_unclustered(Xfer *pXfer){
1008
Stmt q;
1009
int cnt = 0;
1010
const char *zExtra;
1011
if( db_table_exists("temp","onremote") ){
1012
zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
1013
}else{
1014
zExtra = "";
1015
}
1016
if( pXfer->resync ){
1017
db_prepare(&q,
1018
"SELECT uuid, rid FROM blob"
1019
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1020
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1021
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s"
1022
" AND blob.rid<=%d"
1023
" ORDER BY blob.rid DESC",
1024
zExtra /*safe-for-%s*/, pXfer->resync
1025
);
1026
}else{
1027
db_prepare(&q,
1028
"SELECT uuid FROM unclustered JOIN blob USING(rid) /*scan*/"
1029
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1030
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1031
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
1032
zExtra /*safe-for-%s*/
1033
);
1034
}
1035
while( db_step(&q)==SQLITE_ROW ){
1036
blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1037
cnt++;
1038
if( pXfer->resync && pXfer->mxSend<(int)blob_size(pXfer->pOut) ){
1039
pXfer->resync = db_column_int(&q, 1)-1;
1040
}
1041
}
1042
db_finalize(&q);
1043
if( cnt==0 ) pXfer->resync = 0;
1044
return cnt;
1045
}
1046
1047
/*
1048
** Send an igot message for every cluster artifact that is not a phantom,
1049
** is not shunned, is not private, and that is not in the UNCLUSTERED table.
1050
** Return the number of cards sent.
1051
*/
1052
static int send_all_clusters(Xfer *pXfer){
1053
Stmt q;
1054
int cnt = 0;
1055
const char *zExtra;
1056
if( db_table_exists("temp","onremote") ){
1057
zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
1058
}else{
1059
zExtra = "";
1060
}
1061
db_prepare(&q,
1062
"SELECT uuid"
1063
" FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d"
1064
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1065
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1066
" AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)"
1067
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
1068
TAG_CLUSTER, zExtra /*safe-for-%s*/
1069
);
1070
while( db_step(&q)==SQLITE_ROW ){
1071
if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n");
1072
blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1073
cnt++;
1074
}
1075
db_finalize(&q);
1076
if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n");
1077
return cnt;
1078
}
1079
1080
/*
1081
** Send an igot message for every artifact.
1082
*/
1083
static void send_all(Xfer *pXfer){
1084
Stmt q;
1085
db_prepare(&q,
1086
"SELECT uuid FROM blob "
1087
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1088
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
1089
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1090
);
1091
while( db_step(&q)==SQLITE_ROW ){
1092
blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1093
}
1094
db_finalize(&q);
1095
}
1096
1097
/*
1098
** pXfer is a "pragma uv-hash HASH" card.
1099
**
1100
** If HASH is different from the unversioned content hash on this server,
1101
** then send a bunch of uvigot cards, one for each entry unversioned file
1102
** on this server.
1103
*/
1104
static void send_unversioned_catalog(Xfer *pXfer){
1105
Stmt uvq;
1106
unversioned_schema();
1107
db_prepare(&uvq,
1108
"SELECT name, mtime, hash, sz FROM unversioned"
1109
);
1110
while( db_step(&uvq)==SQLITE_ROW ){
1111
const char *zName = db_column_text(&uvq,0);
1112
sqlite3_int64 mtime = db_column_int64(&uvq,1);
1113
const char *zHash = db_column_text(&uvq,2);
1114
int sz = db_column_int(&uvq,3);
1115
if( zHash==0 ){ sz = 0; zHash = "-"; }
1116
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
1117
zName, mtime, zHash, sz);
1118
}
1119
db_finalize(&uvq);
1120
}
1121
1122
/*
1123
** Return a string that contains supplemental information about a
1124
** "not authorized" error. The string might be empty if no additional
1125
** information is available.
1126
*/
1127
static char *whyNotAuth(void){
1128
if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
1129
return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
1130
}
1131
return "";
1132
}
1133
1134
/*
1135
** Called when there is an attempt to transfer private content to and
1136
** from a server without authorization.
1137
*/
1138
static void server_private_xfer_not_authorized(void){
1139
@ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
1140
}
1141
1142
/*
1143
** Return the common TH1 code to evaluate prior to evaluating any other
1144
** TH1 transfer notification scripts.
1145
*/
1146
const char *xfer_common_code(void){
1147
return db_get("xfer-common-script", 0);
1148
}
1149
1150
/*
1151
** Return the TH1 code to evaluate when a push is processed.
1152
*/
1153
const char *xfer_push_code(void){
1154
return db_get("xfer-push-script", 0);
1155
}
1156
1157
/*
1158
** Return the TH1 code to evaluate when a commit is processed.
1159
*/
1160
const char *xfer_commit_code(void){
1161
return db_get("xfer-commit-script", 0);
1162
}
1163
1164
/*
1165
** Return the TH1 code to evaluate when a ticket change is processed.
1166
*/
1167
const char *xfer_ticket_code(void){
1168
return db_get("xfer-ticket-script", 0);
1169
}
1170
1171
/*
1172
** Reset the CGI content, roll back any pending db transaction, and
1173
** emit an "error" xfer message. The message text gets fossil-encoded
1174
** by this function. This is only intended for use with
1175
** fail-fast/fatal errors, not ones which can be skipped over.
1176
*/
1177
static void xfer_fatal_error(const char *zMsg){
1178
cgi_reset_content();
1179
if( db_transaction_nesting_depth()>0 ){
1180
db_rollback_transaction();
1181
}
1182
@ error %F(zMsg)
1183
}
1184
1185
/*
1186
** Run the specified TH1 script, if any, and returns 1 on error.
1187
*/
1188
int xfer_run_script(
1189
const char *zScript,
1190
const char *zUuidOrList,
1191
int bIsList
1192
){
1193
int rc = TH_OK;
1194
if( !zScript ) return rc;
1195
Th_FossilInit(TH_INIT_DEFAULT);
1196
Th_Store(bIsList ? "uuids" : "uuid", zUuidOrList ? zUuidOrList : "");
1197
rc = Th_Eval(g.interp, 0, zScript, -1);
1198
if( rc!=TH_OK ){
1199
fossil_error(1, "%s", Th_GetResult(g.interp, 0));
1200
}
1201
return rc;
1202
}
1203
1204
/*
1205
** Runs the pre-transfer TH1 script, if any, and returns its return code.
1206
** This script may be run multiple times. If the script performs actions
1207
** that cannot be redone, it should use an internal [if] guard similar to
1208
** the following:
1209
**
1210
** if {![info exists common_done]} {
1211
** # ... code here
1212
** set common_done 1
1213
** }
1214
*/
1215
int xfer_run_common_script(void){
1216
return xfer_run_script(xfer_common_code(), 0, 0);
1217
}
1218
1219
/*
1220
** This routine makes a "syncwith:URL" entry in the CONFIG table to
1221
** indicate that a sync is occurring with zUrl.
1222
**
1223
** Add a "syncfrom:URL" entry instead of "syncwith:URL" if bSyncFrom is true.
1224
*/
1225
static void xfer_syncwith(const char *zUrl, int bSyncFrom){
1226
UrlData x;
1227
memset(&x, 0, sizeof(x));
1228
url_parse_local(zUrl, URL_OMIT_USER, &x);
1229
if( x.protocol && strncmp(x.protocol,"http",4)==0
1230
&& x.name && sqlite3_strlike("%localhost%", x.name, 0)!=0
1231
){
1232
db_unprotect(PROTECT_CONFIG);
1233
db_multi_exec("REPLACE INTO config(name,value,mtime)"
1234
"VALUES('sync%q:%q','{}',now())",
1235
bSyncFrom ? "from" : "with", x.canonical);
1236
db_protect_pop();
1237
}
1238
url_unparse(&x);
1239
}
1240
1241
/*
1242
** If this variable is set, disable login checks. Used for debugging
1243
** only.
1244
*/
1245
static int disableLogin = 0;
1246
1247
/*
1248
** Must be passed the version info from pragmas
1249
** client-version/server-version cards. If the version info is "new
1250
** enough" then the loginCardMode is ORd into the X-Fossil-Xfer-Login
1251
** card flag, else this is a no-op.
1252
*/
1253
static void xfer_xflc_check(int iRemoteVersion, int iDate, int iTime,
1254
int fLoginCardMode){
1255
if( iRemoteVersion>=22700
1256
&& (iDate > 20250727
1257
|| (iDate == 20250727 && iTime >= 110500)) ){
1258
g.syncInfo.fLoginCardMode |= fLoginCardMode;
1259
}
1260
}
1261
1262
/*
1263
** The CGI/HTTP preprocessor always redirects requests with a content-type
1264
** of application/x-fossil or application/x-fossil-debug to this page,
1265
** regardless of what path was specified in the HTTP header. This allows
1266
** clone clients to specify a URL that omits default pathnames, such
1267
** as "http://fossil-scm.org/" instead of "http://fossil-scm.org/index.cgi".
1268
**
1269
** WEBPAGE: xfer raw-content loadavg-exempt
1270
**
1271
** This is the transfer handler on the server side. The transfer
1272
** message has been uncompressed and placed in the g.cgiIn blob.
1273
** Process this message and form an appropriate reply.
1274
*/
1275
void page_xfer(void){
1276
int isPull = 0;
1277
int isPush = 0;
1278
int nErr = 0;
1279
Xfer xfer;
1280
int deltaFlag = 0;
1281
int isClone = 0;
1282
int nGimme = 0;
1283
int size;
1284
char *zNow;
1285
int rc;
1286
const char *zScript = 0;
1287
char *zUuidList = 0;
1288
int nUuidList = 0;
1289
char **pzUuidList = 0;
1290
int *pnUuidList = 0;
1291
int uvCatalogSent = 0;
1292
int bSendLinks = 0;
1293
int nLogin = 0;
1294
1295
if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
1296
fossil_redirect_home();
1297
}
1298
g.zLogin = "anonymous";
1299
login_set_anon_nobody_capabilities();
1300
login_check_credentials();
1301
cgi_check_for_malice();
1302
memset(&xfer, 0, sizeof(xfer));
1303
blobarray_zero(xfer.aToken, count(xfer.aToken));
1304
cgi_set_content_type(g.zContentType);
1305
cgi_reset_content();
1306
if( db_schema_is_outofdate() ){
1307
@ error database\sschema\sis\sout-of-date\son\sthe\sserver.
1308
return;
1309
}
1310
blob_zero(&xfer.err);
1311
xfer.pIn = &g.cgiIn;
1312
xfer.pOut = cgi_output_blob();
1313
xfer.mxSend = db_get_int("max-download", 5000000);
1314
xfer.maxTime = db_get_int("max-download-time", 30);
1315
if( xfer.maxTime<1 ) xfer.maxTime = 1;
1316
xfer.maxTime += time(NULL);
1317
g.xferPanic = 1;
1318
1319
db_begin_write();
1320
db_multi_exec(
1321
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
1322
"CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;"
1323
);
1324
manifest_crosslink_begin();
1325
rc = xfer_run_common_script();
1326
if( rc==TH_ERROR ){
1327
cgi_reset_content();
1328
@ error common\sscript\sfailed:\s%F(g.zErrMsg)
1329
nErr++;
1330
}
1331
zScript = xfer_push_code();
1332
if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
1333
pzUuidList = &zUuidList;
1334
pnUuidList = &nUuidList;
1335
}
1336
if( g.syncInfo.zLoginCard ){
1337
/* Login card received via HTTP Cookie header */
1338
blob_zero(&xfer.line);
1339
blob_append(&xfer.line, g.syncInfo.zLoginCard, -1);
1340
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
1341
count(xfer.aToken));
1342
fossil_free( g.syncInfo.zLoginCard );
1343
g.syncInfo.zLoginCard = 0;
1344
if( xfer.nToken==4
1345
&& blob_eq(&xfer.aToken[0], "login") ){
1346
goto handle_login_card;
1347
}
1348
}
1349
while( blob_line(xfer.pIn, &xfer.line) ){
1350
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
1351
if( blob_size(&xfer.line)==0 ) continue;
1352
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
1353
1354
/* file HASH SIZE \n CONTENT
1355
** file HASH DELTASRC SIZE \n CONTENT
1356
**
1357
** Server accepts a file from the client.
1358
*/
1359
if( blob_eq(&xfer.aToken[0], "file") ){
1360
if( !isPush ){
1361
cgi_reset_content();
1362
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
1363
nErr++;
1364
break;
1365
}
1366
xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
1367
if( blob_size(&xfer.err) ){
1368
cgi_reset_content();
1369
@ error %T(blob_str(&xfer.err))
1370
nErr++;
1371
break;
1372
}
1373
}else
1374
1375
/* cfile HASH USIZE CSIZE \n CONTENT
1376
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
1377
**
1378
** Server accepts a compressed file from the client.
1379
*/
1380
if( blob_eq(&xfer.aToken[0], "cfile") ){
1381
if( !isPush ){
1382
cgi_reset_content();
1383
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
1384
nErr++;
1385
break;
1386
}
1387
xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
1388
if( blob_size(&xfer.err) ){
1389
cgi_reset_content();
1390
@ error %T(blob_str(&xfer.err))
1391
nErr++;
1392
break;
1393
}
1394
}else
1395
1396
/* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
1397
**
1398
** Server accepts an unversioned file from the client.
1399
*/
1400
if( blob_eq(&xfer.aToken[0], "uvfile") ){
1401
xfer_accept_unversioned_file(&xfer, g.perm.WrUnver);
1402
if( blob_size(&xfer.err) ){
1403
cgi_reset_content();
1404
@ error %T(blob_str(&xfer.err))
1405
fossil_print("%%%%%%%% xfer.err: '%s'\n", blob_str(&xfer.err));
1406
nErr++;
1407
break;
1408
}
1409
}else
1410
1411
/* gimme HASH
1412
**
1413
** Client is requesting a file from the server. Send it.
1414
*/
1415
if( blob_eq(&xfer.aToken[0], "gimme")
1416
&& xfer.nToken==2
1417
&& blob_is_hname(&xfer.aToken[1])
1418
){
1419
nGimme++;
1420
remote_unk(&xfer.aToken[1]);
1421
if( isPull ){
1422
int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
1423
if( rid ){
1424
send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
1425
}
1426
}
1427
}else
1428
1429
/* uvgimme NAME
1430
**
1431
** Client is requesting an unversioned file from the server. Send it.
1432
*/
1433
if( blob_eq(&xfer.aToken[0], "uvgimme")
1434
&& xfer.nToken==2
1435
&& blob_is_filename(&xfer.aToken[1])
1436
){
1437
send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0);
1438
}else
1439
1440
/* igot HASH ?ISPRIVATE?
1441
**
1442
** Client announces that it has a particular file. If the ISPRIVATE
1443
** argument exists and is "1", then the file is a private file.
1444
*/
1445
if( xfer.nToken>=2
1446
&& blob_eq(&xfer.aToken[0], "igot")
1447
&& blob_is_hname(&xfer.aToken[1])
1448
){
1449
if( isPush ){
1450
int rid = 0;
1451
int isPriv = 0;
1452
if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
1453
/* Client says the artifact is public */
1454
rid = rid_from_uuid(&xfer.aToken[1], 1, 0);
1455
}else if( g.perm.Private ){
1456
/* Client says the artifact is private and the client has
1457
** permission to push private content. Create a new phantom
1458
** artifact that is marked private. */
1459
rid = rid_from_uuid(&xfer.aToken[1], 1, 1);
1460
isPriv = 1;
1461
}else{
1462
/* Client says the artifact is private and the client is unable
1463
** or unwilling to send us the artifact. If we already hold the
1464
** artifact here on the server as a phantom, make sure that
1465
** phantom is marked as private so that we don't keep asking about
1466
** it in subsequent sync requests. */
1467
rid = rid_from_uuid(&xfer.aToken[1], 0, 1);
1468
isPriv = 1;
1469
}
1470
if( rid ){
1471
remote_has(rid);
1472
if( isPriv ){
1473
content_make_private(rid);
1474
}else{
1475
content_make_public(rid);
1476
}
1477
}
1478
}
1479
}else
1480
1481
1482
/* pull SERVERCODE PROJECTCODE
1483
** push SERVERCODE PROJECTCODE
1484
**
1485
** The client wants either send or receive. The server should
1486
** verify that the project code matches. The server code is ignored.
1487
*/
1488
if( xfer.nToken==3
1489
&& (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
1490
&& blob_is_hname(&xfer.aToken[2])
1491
){
1492
const char *zPCode;
1493
zPCode = db_get("project-code", 0);
1494
if( zPCode==0 ){
1495
fossil_fatal("missing project code");
1496
}
1497
if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
1498
cgi_reset_content();
1499
@ error wrong\sproject
1500
nErr++;
1501
break;
1502
}
1503
login_check_credentials();
1504
if( blob_eq(&xfer.aToken[0], "pull") ){
1505
if( !g.perm.Read ){
1506
cgi_reset_content();
1507
@ error not\sauthorized\sto\sread%s(whyNotAuth())
1508
nErr++;
1509
break;
1510
}
1511
isPull = 1;
1512
}else{
1513
if( !g.perm.Write ){
1514
if( !isPull ){
1515
cgi_reset_content();
1516
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
1517
nErr++;
1518
}else{
1519
@ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
1520
}
1521
}else{
1522
isPush = 1;
1523
}
1524
}
1525
}else
1526
1527
/* clone ?PROTOCOL-VERSION? ?SEQUENCE-NUMBER?
1528
**
1529
** The client knows nothing. Tell all.
1530
*/
1531
if( blob_eq(&xfer.aToken[0], "clone") ){
1532
int iVers;
1533
login_check_credentials();
1534
if( !g.perm.Clone ){
1535
cgi_reset_content();
1536
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1537
@ error not\sauthorized\sto\sclone%s(whyNotAuth())
1538
nErr++;
1539
break;
1540
}
1541
if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
1542
@ pragma uv-pull-only
1543
send_unversioned_catalog(&xfer);
1544
uvCatalogSent = 1;
1545
}
1546
if( xfer.nToken==3
1547
&& blob_is_int(&xfer.aToken[1], &iVers)
1548
&& iVers>=2
1549
){
1550
int seqno, max;
1551
if( iVers>=3 ){
1552
cgi_set_content_type("application/x-fossil-uncompressed");
1553
}
1554
blob_is_int(&xfer.aToken[2], &seqno);
1555
if( seqno<=0 ){
1556
xfer_fatal_error("invalid clone sequence number");
1557
db_rollback_transaction();
1558
return;
1559
}
1560
max = db_int(0, "SELECT max(rid) FROM blob");
1561
while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){
1562
if( time(NULL) >= xfer.maxTime ) break;
1563
if( iVers>=3 ){
1564
send_compressed_file(&xfer, seqno);
1565
}else{
1566
send_file(&xfer, seqno, 0, 1);
1567
}
1568
seqno++;
1569
}
1570
if( seqno>max ) seqno = 0;
1571
@ clone_seqno %d(seqno)
1572
}else{
1573
isClone = 1;
1574
isPull = 1;
1575
deltaFlag = 1;
1576
}
1577
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1578
}else
1579
1580
/* login USER NONCE SIGNATURE
1581
**
1582
** The client has sent login credentials to the server.
1583
** Validate the login. This has to happen before anything else.
1584
**
1585
** For many years, Fossil would accept multiple login cards with
1586
** cumulative permissions. But that feature was never used. Hence
1587
** it is now prohibited. Any login card after the first generates
1588
** a fatal error.
1589
*/
1590
if( blob_eq(&xfer.aToken[0], "login")
1591
&& xfer.nToken==4
1592
){
1593
handle_login_card:
1594
nLogin++;
1595
if( disableLogin ){
1596
g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
1597
}else if( nLogin > 1 ){
1598
cgi_reset_content();
1599
@ error multiple\slogin\cards
1600
nErr++;
1601
break;
1602
}else{
1603
if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
1604
|| check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
1605
){
1606
cgi_reset_content();
1607
@ error login\sfailed
1608
nErr++;
1609
break;
1610
}
1611
}
1612
}else
1613
1614
/* reqconfig NAME
1615
**
1616
** Client is requesting a configuration value from the server
1617
*/
1618
if( blob_eq(&xfer.aToken[0], "reqconfig")
1619
&& xfer.nToken==2
1620
){
1621
if( g.perm.Read ){
1622
char *zName = blob_str(&xfer.aToken[1]);
1623
if( zName[0]=='/' ){
1624
/* New style configuration transfer */
1625
int groupMask = configure_name_to_mask(&zName[1], 0);
1626
if( !g.perm.Admin ) groupMask &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
1627
if( !g.perm.RdAddr ) groupMask &= ~CONFIGSET_ADDR;
1628
configure_send_group(xfer.pOut, groupMask, 0);
1629
}
1630
}
1631
}else
1632
1633
/* config NAME SIZE \n CONTENT
1634
**
1635
** Client has sent a configuration value to the server.
1636
** This is only permitted for high-privilege users.
1637
*/
1638
if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
1639
&& blob_is_int(&xfer.aToken[2], &size) ){
1640
const char *zName = blob_str(&xfer.aToken[1]);
1641
Blob content;
1642
if( size<0 ){
1643
xfer_fatal_error("invalid config record");
1644
db_rollback_transaction();
1645
return;
1646
}
1647
blob_zero(&content);
1648
blob_extract(xfer.pIn, size, &content);
1649
if( !g.perm.Admin ){
1650
cgi_reset_content();
1651
@ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
1652
nErr++;
1653
break;
1654
}
1655
configure_receive(zName, &content, CONFIGSET_ALL);
1656
blob_reset(&content);
1657
blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
1658
}else
1659
1660
1661
/* cookie TEXT
1662
**
1663
** A cookie contains an arbitrary-length argument that is server-defined.
1664
** The argument must be encoded so as not to contain any whitespace.
1665
** The server can optionally send a cookie to the client. The client
1666
** might then return the same cookie back to the server on its next
1667
** communication. The cookie might record information that helps
1668
** the server optimize a push or pull.
1669
**
1670
** The client is not required to return a cookie. So the server
1671
** must not depend on the cookie. The cookie should be an optimization
1672
** only. The client might also send a cookie that came from a different
1673
** server. So the server must be prepared to distinguish its own cookie
1674
** from cookies originating from other servers. The client might send
1675
** back several different cookies to the server. The server should be
1676
** prepared to sift through the cookies and pick the one that it wants.
1677
*/
1678
if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
1679
/* Process the cookie */
1680
}else
1681
1682
1683
/* private
1684
**
1685
** The client card indicates that the next "file" or "cfile" will contain
1686
** private content.
1687
*/
1688
if( blob_eq(&xfer.aToken[0], "private") ){
1689
if( !g.perm.Private ){
1690
server_private_xfer_not_authorized();
1691
}else{
1692
xfer.nextIsPrivate = 1;
1693
}
1694
}else
1695
1696
/* pragma NAME VALUE...
1697
**
1698
** The client issues pragmas to try to influence the behavior of the
1699
** server. These are requests only. Unknown pragmas are silently
1700
** ignored.
1701
*/
1702
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
1703
1704
/* pragma send-private
1705
**
1706
** The client is requesting private artifacts.
1707
**
1708
** If the user has the "x" privilege (which must be set explicitly -
1709
** it is not automatic with "a" or "s") then this pragma causes
1710
** private information to be pulled in addition to public records.
1711
*/
1712
if( blob_eq(&xfer.aToken[1], "send-private") ){
1713
login_check_credentials();
1714
if( !g.perm.Private ){
1715
server_private_xfer_not_authorized();
1716
}else{
1717
xfer.syncPrivate = 1;
1718
}
1719
}else
1720
1721
/* pragma send-catalog
1722
**
1723
** The client wants to see igot cards for all known artifacts.
1724
** This is used as part of "sync --verily" to help ensure that
1725
** no artifacts have been missed on prior syncs.
1726
*/
1727
if( blob_eq(&xfer.aToken[1], "send-catalog") ){
1728
xfer.resync = 0x7fffffff;
1729
}else
1730
1731
/* pragma client-version VERSION ?DATE? ?TIME?
1732
**
1733
** The client announces to the server what version of Fossil it
1734
** is running. The DATE and TIME are a pure numeric ISO8601 time
1735
** for the specific check-in of the client.
1736
*/
1737
if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){
1738
xfer.remoteVersion = g.syncInfo.remoteVersion =
1739
atoi(blob_str(&xfer.aToken[2]));
1740
if( xfer.nToken>=5 ){
1741
xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
1742
xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
1743
@ pragma server-version %d(RELEASE_VERSION_NUMBER) \
1744
@ %d(MANIFEST_NUMERIC_DATE) %d(MANIFEST_NUMERIC_TIME)
1745
}
1746
xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
1747
xfer.remoteTime, 0x04 );
1748
}else
1749
1750
/* pragma uv-hash HASH
1751
**
1752
** The client wants to make sure that unversioned files are all synced.
1753
** If the HASH does not match, send a complete catalog of
1754
** "uvigot" cards.
1755
*/
1756
if( blob_eq(&xfer.aToken[1], "uv-hash")
1757
&& blob_is_hname(&xfer.aToken[2])
1758
){
1759
if( !uvCatalogSent
1760
&& g.perm.Read
1761
&& !blob_eq_str(&xfer.aToken[2], unversioned_content_hash(0),-1)
1762
){
1763
if( g.perm.WrUnver ){
1764
@ pragma uv-push-ok
1765
}else if( g.perm.Read ){
1766
@ pragma uv-pull-only
1767
}
1768
send_unversioned_catalog(&xfer);
1769
}
1770
uvCatalogSent = 1;
1771
}else
1772
1773
/* pragma ci-lock CHECKIN-HASH CLIENT-ID
1774
**
1775
** The client wants to make non-branch commit against the check-in
1776
** identified by CHECKIN-HASH. The server will remember this and
1777
** subsequent ci-lock requests from different clients will generate
1778
** a ci-lock-fail pragma in the reply.
1779
*/
1780
if( blob_eq(&xfer.aToken[1], "ci-lock")
1781
&& xfer.nToken==4
1782
&& blob_is_hname(&xfer.aToken[2])
1783
){
1784
Stmt q;
1785
sqlite3_int64 iNow = time(0);
1786
sqlite3_int64 maxAge = db_get_int("lock-timeout",60);
1787
int seenFault = 0;
1788
db_prepare(&q,
1789
"SELECT value->>'login',"
1790
" mtime,"
1791
" value->>'clientid',"
1792
" (SELECT rid FROM blob WHERE uuid=substr(name,9)),"
1793
" name"
1794
" FROM config"
1795
" WHERE name GLOB 'ci-lock-*'"
1796
" AND json_valid(value)"
1797
);
1798
while( db_step(&q)==SQLITE_ROW ){
1799
int x = db_column_int(&q,3);
1800
const char *zName = db_column_text(&q,4);
1801
if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
1802
/* check-in locks expire after maxAge seconds, or when the
1803
** check-in is no longer a leaf */
1804
db_unprotect(PROTECT_CONFIG);
1805
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1806
db_protect_pop();
1807
continue;
1808
}
1809
if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
1810
const char *zClientId = db_column_text(&q, 2);
1811
const char *zLogin = db_column_text(&q,0);
1812
sqlite3_int64 mtime = db_column_int64(&q, 1);
1813
if( fossil_strcmp(zClientId, blob_str(&xfer.aToken[3]))!=0 ){
1814
@ pragma ci-lock-fail %F(zLogin) %lld(mtime)
1815
}
1816
seenFault = 1;
1817
}
1818
}
1819
db_finalize(&q);
1820
if( !seenFault ){
1821
db_unprotect(PROTECT_CONFIG);
1822
db_multi_exec(
1823
"REPLACE INTO config(name,value,mtime)"
1824
"VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
1825
blob_str(&xfer.aToken[2]), g.zLogin,
1826
blob_str(&xfer.aToken[3])
1827
);
1828
db_protect_pop();
1829
}
1830
if( db_get_boolean("forbid-delta-manifests",0) ){
1831
@ pragma avoid-delta-manifests
1832
}
1833
}else
1834
1835
/* pragma ci-unlock CLIENT-ID
1836
**
1837
** Remove any locks previously held by CLIENT-ID. Clients send this
1838
** pragma with their own ID whenever they know that they no longer
1839
** have any commits pending.
1840
*/
1841
if( blob_eq(&xfer.aToken[1], "ci-unlock")
1842
&& xfer.nToken==3
1843
&& blob_is_hname(&xfer.aToken[2])
1844
){
1845
db_unprotect(PROTECT_CONFIG);
1846
db_multi_exec(
1847
"DELETE FROM config"
1848
" WHERE name GLOB 'ci-lock-*'"
1849
" AND (NOT json_valid(value) OR value->>'clientid'==%Q)",
1850
blob_str(&xfer.aToken[2])
1851
);
1852
db_protect_pop();
1853
}else
1854
1855
/* pragma client-url URL
1856
**
1857
** This pragma is an informational notification to the server that
1858
** their relationship could, in theory, be inverted by having the
1859
** server call the client at URL.
1860
*/
1861
if( blob_eq(&xfer.aToken[1], "client-url")
1862
&& xfer.nToken==3
1863
&& g.perm.Write
1864
){
1865
xfer_syncwith(blob_str(&xfer.aToken[2]), 1);
1866
}else
1867
1868
/* pragma req-links
1869
**
1870
** The client sends this message to the server to ask the server
1871
** to tell it about alternative repositories in the reply.
1872
*/
1873
if( blob_eq(&xfer.aToken[1], "req-links") ){
1874
bSendLinks = 1;
1875
}else
1876
1877
/* pragma req-clusters
1878
**
1879
** This pragma requests that the server send igot cards for every
1880
** cluster artifact that it knows about.
1881
*/
1882
if( blob_eq(&xfer.aToken[1], "req-clusters") ){
1883
send_all_clusters(&xfer);
1884
}
1885
1886
}else
1887
1888
/* Unknown message
1889
*/
1890
{
1891
cgi_reset_content();
1892
@ error bad\scommand:\s%F(blob_str(&xfer.line))
1893
}
1894
blobarray_reset(xfer.aToken, xfer.nToken);
1895
blob_reset(&xfer.line);
1896
}
1897
if( isPush ){
1898
if( rc==TH_OK ){
1899
rc = xfer_run_script(zScript, zUuidList, 1);
1900
if( rc==TH_ERROR ){
1901
cgi_reset_content();
1902
@ error push\sscript\sfailed:\s%F(g.zErrMsg)
1903
nErr++;
1904
}
1905
}
1906
request_phantoms(&xfer, 500);
1907
}
1908
if( zUuidList ){
1909
Th_Free(g.interp, zUuidList);
1910
}
1911
if( isClone && nGimme==0 ){
1912
/* The initial "clone" message from client to server contains no
1913
** "gimme" cards. On that initial message, send the client an "igot"
1914
** card for every artifact currently in the repository. This will
1915
** cause the client to create phantoms for all artifacts, which will
1916
** in turn make sure that the entire repository is sent efficiently
1917
** and expeditiously.
1918
*/
1919
send_all(&xfer);
1920
if( xfer.syncPrivate ) send_private(&xfer);
1921
}else if( isPull ){
1922
create_cluster();
1923
send_unclustered(&xfer);
1924
if( xfer.syncPrivate ) send_private(&xfer);
1925
}
1926
hook_expecting_more_artifacts(xfer.nGimmeSent?60:0);
1927
db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
1928
manifest_crosslink_end(MC_PERMIT_HOOKS);
1929
1930
/* Send URLs for alternative repositories for the same project,
1931
** if requested by the client. */
1932
if( bSendLinks && g.zBaseURL ){
1933
Stmt q;
1934
db_prepare(&q,
1935
"WITH remote(mtime, url, arg) AS (\n"
1936
" SELECT mtime, substr(name,10), '{}' FROM config\n"
1937
" WHERE name GLOB 'syncwith:http*'\n"
1938
" UNION ALL\n"
1939
" SELECT mtime, substr(name,10), '{}' FROM config\n"
1940
" WHERE name GLOB 'syncfrom:http*'\n"
1941
" UNION ALL\n"
1942
" SELECT mtime, substr(name,9), '{\"type\":\"git\"}' FROM config\n"
1943
" WHERE name GLOB 'gitpush:*'\n"
1944
")\n"
1945
"SELECT url, json_insert(arg,'$.src',%Q), max(mtime)\n"
1946
" FROM remote WHERE mtime>unixepoch('now','-1 month')\n"
1947
" GROUP BY url;",
1948
g.zBaseURL
1949
);
1950
while( db_step(&q)==SQLITE_ROW ){
1951
UrlData x;
1952
const char *zUrl = db_column_text(&q, 0);
1953
const char *zArg = db_column_text(&q, 1);
1954
i64 iMtime = db_column_int64(&q, 2);
1955
memset(&x, 0, sizeof(x));
1956
url_parse_local(zUrl, URL_OMIT_USER, &x);
1957
if( x.name!=0 && sqlite3_strlike("%localhost%", x.name, 0)!=0 ){
1958
@ pragma link %F(x.canonical) %F(zArg) %lld(iMtime)
1959
}
1960
url_unparse(&x);
1961
}
1962
db_finalize(&q);
1963
}
1964
1965
/* Send the server timestamp last, in case prior processing happened
1966
** to use up a significant fraction of our time window.
1967
*/
1968
zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
1969
@ # timestamp %s(zNow) errors %d(nErr)
1970
fossil_free(zNow);
1971
1972
db_commit_transaction();
1973
configure_rebuild();
1974
}
1975
1976
/*
1977
** COMMAND: test-xfer
1978
**
1979
** Usage: %fossil test-xfer ?OPTIONS? XFERFILE
1980
**
1981
** Pass the sync-protocol input file XFERFILE into the server-side sync
1982
** protocol handler. Generate a reply on standard output.
1983
**
1984
** This command was original created to help debug the server side of
1985
** sync messages. The XFERFILE is the uncompressed content of an
1986
** "xfer" HTTP request from client to server. This command interprets
1987
** that message and generates the content of an HTTP reply (without any
1988
** encoding and without the HTTP reply headers) and writes that reply
1989
** on standard output.
1990
**
1991
** One possible usages scenario is to capture some XFERFILE examples
1992
** using a command like:
1993
**
1994
** fossil push http://bogus/ --httptrace
1995
**
1996
** The complete HTTP requests are stored in files named "http-request-N.txt".
1997
** Find one of those requests, remove the HTTP header, and make other edits
1998
** as necessary to generate an appropriate XFERFILE test case. Then run:
1999
**
2000
** fossil test-xfer xferfile.txt
2001
**
2002
** Options:
2003
** --host HOSTNAME Supply a server hostname used to populate
2004
** g.zBaseURL and similar.
2005
*/
2006
void cmd_test_xfer(void){
2007
const char *zHost;
2008
db_find_and_open_repository(0,0);
2009
zHost = find_option("host",0,1);
2010
verify_all_options();
2011
if( g.argc!=2 && g.argc!=3 ){
2012
usage("?MESSAGEFILE?");
2013
}
2014
if( zHost==0 ) zHost = "localhost:8080";
2015
g.zBaseURL = mprintf("http://%s", zHost);
2016
g.zHttpsURL = mprintf("https://%s", zHost);
2017
g.zTop = mprintf("");
2018
blob_zero(&g.cgiIn);
2019
blob_read_from_file(&g.cgiIn, g.argc==2 ? "-" : g.argv[2], ExtFILE);
2020
disableLogin = 1;
2021
page_xfer();
2022
fossil_print("%s", cgi_extract_content());
2023
}
2024
2025
/*
2026
** Format strings for progress reporting.
2027
*/
2028
static const char zLabelFormat[] = "%-10s %10s %10s %10s %10s\n";
2029
static const char zValueFormat[] = "\r%-10s %10d %10d %10d %10d\n";
2030
static const char zBriefFormat[] =
2031
"Round-trips: %d Artifacts sent: %d received: %d\r";
2032
2033
#if INTERFACE
2034
/*
2035
** Flag options for controlling client_sync()
2036
*/
2037
#define SYNC_PUSH 0x00001 /* push content client to server */
2038
#define SYNC_PULL 0x00002 /* pull content server to client */
2039
#define SYNC_CLONE 0x00004 /* clone the repository */
2040
#define SYNC_PRIVATE 0x00008 /* Also transfer private content */
2041
#define SYNC_VERBOSE 0x00010 /* Extra diagnostics */
2042
#define SYNC_RESYNC 0x00020 /* --verily */
2043
#define SYNC_FROMPARENT 0x00040 /* Pull from the parent project */
2044
#define SYNC_UNVERSIONED 0x00100 /* Sync unversioned content */
2045
#define SYNC_UV_REVERT 0x00200 /* Copy server unversioned to client */
2046
#define SYNC_UV_TRACE 0x00400 /* Describe UV activities */
2047
#define SYNC_UV_DRYRUN 0x00800 /* Do not actually exchange files */
2048
#define SYNC_IFABLE 0x01000 /* Inability to sync is not fatal */
2049
#define SYNC_CKIN_LOCK 0x02000 /* Lock the current check-in */
2050
#define SYNC_NOHTTPCOMPRESS 0x04000 /* Do not compression HTTP messages */
2051
#define SYNC_ALLURL 0x08000 /* The --all flag - sync to all URLs */
2052
#define SYNC_SHARE_LINKS 0x10000 /* Request alternate repo links */
2053
#define SYNC_XVERBOSE 0x20000 /* Extra verbose. Network traffic */
2054
#define SYNC_PING 0x40000 /* Verify server is alive */
2055
#define SYNC_QUIET 0x80000 /* No output */
2056
#endif
2057
2058
/*
2059
** Floating-point absolute value
2060
*/
2061
static double fossil_fabs(double x){
2062
return x>0.0 ? x : -x;
2063
}
2064
2065
/*
2066
** Sync to the host identified in g.url.name and g.url.path. This
2067
** routine is called by the client.
2068
**
2069
** Records are pushed to the server if pushFlag is true. Records
2070
** are pulled if pullFlag is true. A full sync occurs if both are
2071
** true.
2072
*/
2073
int client_sync(
2074
unsigned syncFlags, /* Mask of SYNC_* flags */
2075
unsigned configRcvMask, /* Receive these configuration items */
2076
unsigned configSendMask, /* Send these configuration items */
2077
const char *zAltPCode, /* Alternative project code (usually NULL) */
2078
int *pnRcvd /* Set to # received artifacts, if not NULL */
2079
){
2080
int go = 1; /* Loop until zero */
2081
int nCardSent = 0; /* Number of cards sent */
2082
int nCardRcvd = 0; /* Number of cards received */
2083
int nCycle = 0; /* Number of round trips to the server */
2084
int size; /* Size of a config value or uvfile */
2085
int origConfigRcvMask; /* Original value of configRcvMask */
2086
int nFileRecv = 0; /* Number of files received */
2087
int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
2088
const char *zCookie; /* Server cookie */
2089
i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
2090
i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
2091
int cloneSeqno = 1; /* Sequence number for clones */
2092
Blob send; /* Text we are sending to the server */
2093
Blob recv; /* Reply we got back from the server */
2094
Xfer xfer; /* Transfer data */
2095
int pctDone; /* Percentage done with a message */
2096
int lastPctDone = -1; /* Last displayed pctDone */
2097
double rArrivalTime; /* Time at which a message arrived */
2098
const char *zSCode = db_get("server-code", "x");
2099
const char *zPCode = db_get("project-code", 0);
2100
int nErr = 0; /* Number of errors */
2101
int nRoundtrip= 0; /* Number of HTTP requests */
2102
int nArtifactSent = 0; /* Total artifacts sent */
2103
int nArtifactRcvd = 0; /* Total artifacts received */
2104
int nPriorArtifact = 0; /* Artifacts received on prior round-trips */
2105
const char *zOpType = 0;/* Push, Pull, Sync, Clone */
2106
double rSkew = 0.0; /* Maximum time skew */
2107
int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
2108
int uvDoPush = 0; /* Generate uvfile messages to send to server */
2109
int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
2110
int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */
2111
int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */
2112
int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */
2113
sqlite3_int64 mtime; /* Modification time on a UV file */
2114
int autopushFailed = 0; /* Autopush following commit failed if true */
2115
const char *zCkinLock; /* Name of check-in to lock. NULL for none */
2116
const char *zClientId; /* A unique identifier for this check-out */
2117
unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
2118
const int bOutIsTty = fossil_isatty(fossil_fileno(stdout));
2119
2120
if( pnRcvd ) *pnRcvd = 0;
2121
if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
2122
if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED|SYNC_PING))
2123
==0
2124
&& configRcvMask==0
2125
&& configSendMask==0
2126
){
2127
return 0; /* Nothing to do */
2128
}
2129
2130
/* Compute an appropriate project code. zPCode is the project code
2131
** for the local repository. zAltPCode will usually be NULL, but might
2132
** also be an alternative project code to expect on the server. When
2133
** zAltPCode is not NULL, that means we are doing a cross-project import -
2134
** in other words, reading content from one project into a different
2135
** project.
2136
*/
2137
if( syncFlags & SYNC_FROMPARENT ){
2138
const char *zPX;
2139
configRcvMask = 0;
2140
configSendMask = 0;
2141
syncFlags &= ~(SYNC_PUSH);
2142
zPX = db_get("parent-project-code", 0);
2143
if( zPX==0 || db_get("parent-project-name",0)==0 ){
2144
fossil_fatal("there is no parent project: set the 'parent-project-code'"
2145
" and 'parent-project-name' config parameters in order"
2146
" to pull from a parent project");
2147
}
2148
if( zPX ){
2149
zAltPCode = zPX;
2150
}
2151
}
2152
if( zAltPCode!=0 && zPCode!=0 && sqlite3_stricmp(zPCode, zAltPCode)==0 ){
2153
zAltPCode = 0;
2154
}
2155
2156
transport_stats(0, 0, 1);
2157
socket_global_init();
2158
memset(&xfer, 0, sizeof(xfer));
2159
xfer.pIn = &recv;
2160
xfer.pOut = &send;
2161
xfer.mxSend = db_get_int("max-upload", 250000);
2162
xfer.maxTime = -1;
2163
xfer.remoteVersion = RELEASE_VERSION_NUMBER;
2164
if( syncFlags & SYNC_PRIVATE ){
2165
g.perm.Private = 1;
2166
xfer.syncPrivate = 1;
2167
}
2168
2169
blobarray_zero(xfer.aToken, count(xfer.aToken));
2170
blob_zero(&send);
2171
blob_zero(&recv);
2172
blob_zero(&xfer.err);
2173
blob_zero(&xfer.line);
2174
origConfigRcvMask = 0;
2175
nUncSent = nUncRcvd = 0;
2176
2177
/* Send the send-private pragma if we are trying to sync private data */
2178
if( syncFlags & SYNC_PRIVATE ){
2179
blob_append(&send, "pragma send-private\n", -1);
2180
}
2181
2182
/* Figure out which check-in to lock */
2183
if( syncFlags & SYNC_CKIN_LOCK ){
2184
int vid = db_lget_int("checkout",0);
2185
zCkinLock = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
2186
}else{
2187
zCkinLock = 0;
2188
}
2189
zClientId = g.localOpen ? db_lget("client-id", 0) : 0;
2190
2191
/* When syncing unversioned files, create a TEMP table in which to store
2192
** the names of files that need to be sent from client to server.
2193
**
2194
** The initial assumption is that all unversioned files need to be sent
2195
** to the other side. But "uvigot" cards received back from the remote
2196
** side will normally cause many of these entries to be removed since they
2197
** do not really need to be sent.
2198
*/
2199
if( (syncFlags & (SYNC_UNVERSIONED|SYNC_CLONE))!=0 ){
2200
unversioned_schema();
2201
db_multi_exec(
2202
"CREATE TEMP TABLE IF NOT EXISTS uv_tosend("
2203
" name TEXT PRIMARY KEY," /* Name of file to send client->server */
2204
" mtimeOnly BOOLEAN" /* True to only send mtime, not content */
2205
") WITHOUT ROWID;"
2206
"REPLACE INTO uv_tosend(name,mtimeOnly)"
2207
" SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;"
2208
);
2209
}
2210
2211
/*
2212
** The request from the client always begin with a clone, pull,
2213
** or push message.
2214
*/
2215
blob_appendf(&send, "pragma client-version %d %d %d\n",
2216
RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
2217
MANIFEST_NUMERIC_TIME);
2218
if( syncFlags & SYNC_CLONE ){
2219
blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
2220
syncFlags &= ~(SYNC_PUSH|SYNC_PULL);
2221
nCardSent++;
2222
/* TBD: Request all transferable configuration values */
2223
content_enable_dephantomize(0);
2224
zOpType = "Clone";
2225
}else if( syncFlags & SYNC_PULL ){
2226
blob_appendf(&send, "pull %s %s\n", zSCode,
2227
zAltPCode ? zAltPCode : zPCode);
2228
nCardSent++;
2229
zOpType = (syncFlags & SYNC_PUSH)?"Sync":"Pull";
2230
if( (syncFlags & SYNC_RESYNC)!=0 && nCycle<2 ){
2231
blob_appendf(&send, "pragma send-catalog\n");
2232
nCardSent++;
2233
}
2234
}
2235
if( syncFlags & SYNC_PUSH ){
2236
blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
2237
nCardSent++;
2238
if( (syncFlags & SYNC_PULL)==0 ) zOpType = "Push";
2239
if( (syncFlags & SYNC_RESYNC)!=0 ) xfer.resync = 0x7fffffff;
2240
}
2241
if( syncFlags & SYNC_VERBOSE ){
2242
fossil_print(zLabelFormat /*works-like:"%s%s%s%s%d"*/,
2243
"", "Bytes", "Cards", "Artifacts", "Deltas");
2244
}
2245
2246
/* Send the client-url pragma on the first cycle if the client has
2247
** a known public url.
2248
*/
2249
if( zAltPCode==0 ){
2250
const char *zSelfUrl = public_url();
2251
if( zSelfUrl ){
2252
blob_appendf(&send, "pragma client-url %s\n", zSelfUrl);
2253
}
2254
}
2255
2256
/* Request URLs of alternative repositories
2257
*/
2258
if( zAltPCode==0 && (syncFlags & SYNC_SHARE_LINKS)!=0 ){
2259
blob_appendf(&send, "pragma req-links\n");
2260
}
2261
2262
while( go ){
2263
int newPhantom = 0;
2264
char *zRandomness;
2265
db_begin_transaction();
2266
db_record_repository_filename(0);
2267
db_multi_exec(
2268
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
2269
"CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;"
2270
);
2271
manifest_crosslink_begin();
2272
2273
2274
/* Client sends the most recently received cookie back to the server.
2275
** Let the server figure out if this is a cookie that it cares about.
2276
*/
2277
zCookie = db_get("cookie", 0);
2278
if( zCookie ){
2279
blob_appendf(&send, "cookie %s\n", zCookie);
2280
}
2281
2282
/* Client sends gimme cards for phantoms
2283
*/
2284
if( (syncFlags & SYNC_PULL)!=0
2285
|| ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
2286
){
2287
request_phantoms(&xfer, mxPhantomReq);
2288
if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){
2289
blob_appendf(&send, "pragma req-clusters\n");
2290
}
2291
}
2292
if( syncFlags & SYNC_PUSH ){
2293
send_unsent(&xfer);
2294
nCardSent += send_unclustered(&xfer);
2295
if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
2296
if( nGimmeRcvd>0 && nCycle==2 ){
2297
send_all_clusters(&xfer);
2298
}
2299
}
2300
2301
/* Client sends configuration parameter requests. On a clone, delay sending
2302
** this until the second cycle since the login card might fail on
2303
** the first cycle.
2304
*/
2305
if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){
2306
const char *zName;
2307
if( zOpType==0 ) zOpType = "Pull";
2308
zName = configure_first_name(configRcvMask);
2309
while( zName ){
2310
blob_appendf(&send, "reqconfig %s\n", zName);
2311
zName = configure_next_name(configRcvMask);
2312
nCardSent++;
2313
}
2314
origConfigRcvMask = configRcvMask;
2315
configRcvMask = 0;
2316
}
2317
2318
/* Client sends a request to sync unversioned files.
2319
** On a clone, delay sending this until the second cycle since
2320
** the login card might fail on the first cycle.
2321
*/
2322
if( (syncFlags & SYNC_UNVERSIONED)!=0
2323
&& ((syncFlags & SYNC_CLONE)==0 || nCycle>0)
2324
&& !uvHashSent
2325
){
2326
blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0));
2327
nCardSent++;
2328
uvHashSent = 1;
2329
}
2330
2331
/* On a "fossil config push", the client send configuration parameters
2332
** being pushed up to the server */
2333
if( configSendMask ){
2334
if( zOpType==0 ) zOpType = "Push";
2335
nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
2336
configSendMask = 0;
2337
}
2338
2339
/* Send unversioned files present here on the client but missing or
2340
** obsolete on the server.
2341
**
2342
** Or, if the SYNC_UV_REVERT flag is set, delete the local unversioned
2343
** files that do not exist on the server.
2344
**
2345
** This happens on the second exchange, since we do not know what files
2346
** need to be sent until after the uvigot cards from the first exchange
2347
** have been processed.
2348
*/
2349
if( uvDoPush ){
2350
assert( (syncFlags & SYNC_UNVERSIONED)!=0 );
2351
if( syncFlags & SYNC_UV_DRYRUN ){
2352
uvDoPush = 0;
2353
}else if( syncFlags & SYNC_UV_REVERT ){
2354
db_multi_exec(
2355
"DELETE FROM unversioned"
2356
" WHERE name IN (SELECT name FROM uv_tosend);"
2357
"DELETE FROM uv_tosend;"
2358
);
2359
uvDoPush = 0;
2360
}else{
2361
Stmt uvq;
2362
int rc = SQLITE_OK;
2363
db_prepare(&uvq, "SELECT name, mtimeOnly FROM uv_tosend");
2364
while( (rc = db_step(&uvq))==SQLITE_ROW ){
2365
const char *zName = db_column_text(&uvq, 0);
2366
send_unversioned_file(&xfer, zName, db_column_int(&uvq,1));
2367
nCardSent++;
2368
nArtifactSent++;
2369
db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName);
2370
if( syncFlags & SYNC_VERBOSE ){
2371
fossil_print("\rUnversioned-file sent: %s\n", zName);
2372
}
2373
if( (int)blob_size(xfer.pOut)>xfer.mxSend ) break;
2374
}
2375
db_finalize(&uvq);
2376
if( rc==SQLITE_DONE ) uvDoPush = 0;
2377
}
2378
}
2379
2380
/* Lock the current check-out */
2381
if( zCkinLock ){
2382
if( zClientId==0 ){
2383
zClientId = db_text(0, "SELECT lower(hex(randomblob(20)))");
2384
db_lset("client-id", zClientId);
2385
}
2386
blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
2387
zCkinLock = 0;
2388
}else if( zClientId ){
2389
blob_appendf(&send, "pragma ci-unlock %s\n", zClientId);
2390
}
2391
/* Append randomness to the end of the uplink message. This makes all
2392
** messages unique so that the login-card nonce will always
2393
** be unique.
2394
*/
2395
zRandomness = db_text(0, "SELECT hex(randomblob(20))");
2396
blob_appendf(&send, "# %s\n", zRandomness);
2397
fossil_free(zRandomness);
2398
2399
if( (syncFlags & SYNC_VERBOSE)!=0
2400
&& (syncFlags & SYNC_XVERBOSE)==0
2401
){
2402
fossil_print("waiting for server...");
2403
}
2404
fflush(stdout);
2405
/* Exchange messages with the server */
2406
if( (syncFlags & SYNC_CLONE)!=0 && nCycle==0 ){
2407
/* Do not send a login card on the first round-trip of a clone */
2408
mHttpFlags = 0;
2409
}else{
2410
mHttpFlags = HTTP_USE_LOGIN;
2411
}
2412
if( syncFlags & SYNC_NOHTTPCOMPRESS ){
2413
mHttpFlags |= HTTP_NOCOMPRESS;
2414
}
2415
if( syncFlags & SYNC_XVERBOSE ){
2416
mHttpFlags |= HTTP_VERBOSE;
2417
}
2418
if( syncFlags & SYNC_QUIET ){
2419
mHttpFlags |= HTTP_QUIET;
2420
}
2421
2422
/* Do the round-trip to the server */
2423
if( http_exchange(&send, &recv, mHttpFlags, MAX_REDIRECTS, 0) ){
2424
nErr++;
2425
go = 2;
2426
break;
2427
}
2428
2429
/* Remember the URL of the sync target in the config file on the
2430
** first successful round-trip */
2431
if( nCycle==0 && db_is_writeable("repository") ){
2432
xfer_syncwith(g.url.canonical, 0);
2433
}
2434
2435
/* Output current stats */
2436
nRoundtrip++;
2437
nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
2438
if( syncFlags & SYNC_VERBOSE ){
2439
fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Sent:",
2440
blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
2441
xfer.nFileSent, xfer.nDeltaSent);
2442
}else if( syncFlags & SYNC_QUIET ){
2443
/* No-op */
2444
}else{
2445
if( bOutIsTty!=0 ){
2446
fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
2447
nRoundtrip, nArtifactSent, nArtifactRcvd);
2448
}
2449
}
2450
nCardSent = 0;
2451
nCardRcvd = 0;
2452
xfer.nFileSent = 0;
2453
xfer.nDeltaSent = 0;
2454
xfer.nGimmeSent = 0;
2455
xfer.nIGotSent = 0;
2456
xfer.nPrivIGot = 0;
2457
2458
lastPctDone = -1;
2459
nUncSent += blob_size(&send);
2460
blob_reset(&send);
2461
blob_appendf(&send, "pragma client-version %d %d %d\n",
2462
RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
2463
MANIFEST_NUMERIC_TIME);
2464
rArrivalTime = db_double(0.0, "SELECT julianday('now')");
2465
2466
/* Send the send-private pragma if we are trying to sync private data */
2467
if( syncFlags & SYNC_PRIVATE ){
2468
blob_append(&send, "pragma send-private\n", -1);
2469
}
2470
2471
/* Begin constructing the next message (which might never be
2472
** sent) by beginning with the pull or push cards
2473
*/
2474
if( syncFlags & SYNC_PULL ){
2475
blob_appendf(&send, "pull %s %s\n", zSCode,
2476
zAltPCode ? zAltPCode : zPCode);
2477
nCardSent++;
2478
}
2479
if( syncFlags & SYNC_PUSH ){
2480
blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
2481
nCardSent++;
2482
}
2483
go = 0;
2484
nUvGimmeSent = 0;
2485
nUvFileRcvd = 0;
2486
nGimmeRcvd = 0;
2487
nPriorArtifact = nArtifactRcvd;
2488
2489
/* Process the reply that came back from the server */
2490
while( blob_line(&recv, &xfer.line) ){
2491
if( blob_buffer(&xfer.line)[0]=='#' ){
2492
const char *zLine = blob_buffer(&xfer.line);
2493
if( memcmp(zLine, "# timestamp ", 12)==0 ){
2494
char zTime[20];
2495
double rDiff;
2496
sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]);
2497
rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g",
2498
zTime, rArrivalTime);
2499
if( rDiff>9e98 || rDiff<-9e98 ) rDiff = 0.0;
2500
if( rDiff*24.0*3600.0 >= -(blob_size(&recv)/5000.0 + 20) ){
2501
rDiff = 0.0;
2502
}
2503
if( fossil_fabs(rDiff)>fossil_fabs(rSkew) ) rSkew = rDiff;
2504
}
2505
nCardRcvd++;
2506
continue;
2507
}
2508
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
2509
nCardRcvd++;
2510
if( (syncFlags & SYNC_VERBOSE)!=0 && recv.nUsed>0 ){
2511
pctDone = (recv.iCursor*100)/recv.nUsed;
2512
if( pctDone!=lastPctDone ){
2513
fossil_print("\rprocessed: %d%% ", pctDone);
2514
lastPctDone = pctDone;
2515
fflush(stdout);
2516
}
2517
}
2518
2519
/* file HASH SIZE \n CONTENT
2520
** file HASH DELTASRC SIZE \n CONTENT
2521
**
2522
** Client receives a file transmitted from the server.
2523
*/
2524
if( blob_eq(&xfer.aToken[0],"file") ){
2525
xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0, 0, 0);
2526
nArtifactRcvd++;
2527
}else
2528
2529
/* cfile HASH USIZE CSIZE \n CONTENT
2530
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
2531
**
2532
** Client receives a compressed file transmitted from the server.
2533
*/
2534
if( blob_eq(&xfer.aToken[0],"cfile") ){
2535
xfer_accept_compressed_file(&xfer, 0, 0);
2536
nArtifactRcvd++;
2537
}else
2538
2539
/* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
2540
**
2541
** Client accepts an unversioned file from the server.
2542
*/
2543
if( blob_eq(&xfer.aToken[0], "uvfile") ){
2544
xfer_accept_unversioned_file(&xfer, 1);
2545
nArtifactRcvd++;
2546
nUvFileRcvd++;
2547
if( syncFlags & SYNC_VERBOSE ){
2548
fossil_print("\rUnversioned-file received: %s\n",
2549
blob_str(&xfer.aToken[1]));
2550
}
2551
}else
2552
2553
/* gimme HASH
2554
**
2555
** Client receives an artifact request from the server.
2556
** If the file is a manifest, assume that the server will also want
2557
** to know all of the content artifacts associated with the manifest
2558
** and send those too.
2559
*/
2560
if( blob_eq(&xfer.aToken[0], "gimme")
2561
&& xfer.nToken==2
2562
&& blob_is_hname(&xfer.aToken[1])
2563
){
2564
remote_unk(&xfer.aToken[1]);
2565
if( syncFlags & SYNC_PUSH ){
2566
int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
2567
if( rid ){
2568
send_file(&xfer, rid, &xfer.aToken[1], 0);
2569
nGimmeRcvd++;
2570
}
2571
}
2572
}else
2573
2574
/* igot HASH ?PRIVATEFLAG?
2575
**
2576
** Server announces that it has a particular file. If this is
2577
** not a file that we have and we are pulling, then create a
2578
** phantom to cause this file to be requested on the next cycle.
2579
** Always remember that the server has this file so that we do
2580
** not transmit it by accident.
2581
**
2582
** If the PRIVATE argument exists and is 1, then the file is
2583
** private. Pretend it does not exists if we are not pulling
2584
** private files.
2585
*/
2586
if( xfer.nToken>=2
2587
&& blob_eq(&xfer.aToken[0], "igot")
2588
&& blob_is_hname(&xfer.aToken[1])
2589
){
2590
int rid;
2591
int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
2592
rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
2593
if( rid>0 ){
2594
if( isPriv ){
2595
content_make_private(rid);
2596
}else{
2597
content_make_public(rid);
2598
}
2599
}else if( isPriv && !g.perm.Private ){
2600
/* ignore private files */
2601
}else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
2602
rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
2603
if( rid ) newPhantom = 1;
2604
}
2605
remote_has(rid);
2606
}else
2607
2608
/* uvigot NAME MTIME HASH SIZE
2609
**
2610
** Server announces that it has a particular unversioned file. The
2611
** server will only send this card if the client had previously sent
2612
** a "pragma uv-hash" card with a hash that does not match.
2613
**
2614
** If the identified file needs to be transferred, then setup for the
2615
** transfer. Generate a "uvgimme" card in the reply if the server
2616
** version is newer than the client. Generate a "uvfile" card if
2617
** the client version is newer than the server. If HASH is "-"
2618
** (indicating that the file has been deleted) and MTIME is newer,
2619
** then do the deletion.
2620
*/
2621
if( xfer.nToken==5
2622
&& blob_eq(&xfer.aToken[0], "uvigot")
2623
&& blob_is_filename(&xfer.aToken[1])
2624
&& blob_is_int64(&xfer.aToken[2], &mtime)
2625
&& blob_is_int(&xfer.aToken[4], &size)
2626
&& (blob_eq(&xfer.aToken[3],"-") || blob_is_hname(&xfer.aToken[3]))
2627
){
2628
const char *zName = blob_str(&xfer.aToken[1]);
2629
const char *zHash = blob_str(&xfer.aToken[3]);
2630
int iStatus;
2631
if( mtime<0 || size<0 ){
2632
xfer_fatal_error("invalid uvigot");
2633
return ++nErr;
2634
}
2635
iStatus = unversioned_status(zName, mtime, zHash);
2636
if( (syncFlags & SYNC_UV_REVERT)!=0 ){
2637
if( iStatus==4 ) iStatus = 2;
2638
if( iStatus==5 ) iStatus = 1;
2639
}
2640
if( syncFlags & (SYNC_UV_TRACE|SYNC_UV_DRYRUN) ){
2641
const char *zMsg = 0;
2642
switch( iStatus ){
2643
case 0:
2644
case 1: zMsg = "UV-PULL"; break;
2645
case 2: zMsg = "UV-PULL-MTIME-ONLY"; break;
2646
case 4: zMsg = "UV-PUSH-MTIME-ONLY"; break;
2647
case 5: zMsg = "UV-PUSH"; break;
2648
}
2649
if( zMsg ) fossil_print("\r%s: %s\n", zMsg, zName);
2650
if( syncFlags & SYNC_UV_DRYRUN ){
2651
iStatus = 99; /* Prevent any changes or reply messages */
2652
}
2653
}
2654
if( iStatus<=1 ){
2655
if( zHash[0]!='-' ){
2656
blob_appendf(xfer.pOut, "uvgimme %s\n", zName);
2657
nCardSent++;
2658
nUvGimmeSent++;
2659
db_multi_exec("DELETE FROM unversioned WHERE name=%Q", zName);
2660
}else if( iStatus==1 ){
2661
db_multi_exec(
2662
"UPDATE unversioned"
2663
" SET mtime=%lld, hash=NULL, sz=0, encoding=0, content=NULL"
2664
" WHERE name=%Q", mtime, zName
2665
);
2666
db_unset("uv-hash", 0);
2667
}
2668
}else if( iStatus==2 ){
2669
db_multi_exec(
2670
"UPDATE unversioned SET mtime=%lld WHERE name=%Q", mtime, zName
2671
);
2672
db_unset("uv-hash", 0);
2673
}
2674
if( iStatus>=4 && uvPullOnly==1 ){
2675
fossil_warning(
2676
"Warning: uv-pull-only \n"
2677
" Unable to push unversioned content because you lack\n"
2678
" sufficient permission on the server\n"
2679
);
2680
uvPullOnly = 2;
2681
}
2682
if( iStatus<=3 || uvPullOnly ){
2683
db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName);
2684
}else if( iStatus==4 ){
2685
db_multi_exec("UPDATE uv_tosend SET mtimeOnly=1 WHERE name=%Q",zName);
2686
}else if( iStatus==5 ){
2687
db_multi_exec("REPLACE INTO uv_tosend(name,mtimeOnly) VALUES(%Q,0)",
2688
zName);
2689
}
2690
}else
2691
2692
/* push SERVERCODE PRODUCTCODE
2693
**
2694
** Should only happen in response to a clone. This message tells
2695
** the client what product code to use for the new database.
2696
*/
2697
if( blob_eq(&xfer.aToken[0],"push")
2698
&& xfer.nToken==3
2699
&& (syncFlags & SYNC_CLONE)!=0
2700
&& blob_is_hname(&xfer.aToken[2])
2701
){
2702
if( zPCode==0 ){
2703
zPCode = mprintf("%b", &xfer.aToken[2]);
2704
db_set("project-code", zPCode, 0);
2705
}
2706
if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
2707
nCardSent++;
2708
}else
2709
2710
/* config NAME SIZE \n CONTENT
2711
**
2712
** Client receive a configuration value from the server.
2713
**
2714
** The received configuration setting is silently ignored if it was
2715
** not requested by a prior "reqconfig" sent from client to server.
2716
*/
2717
if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
2718
&& blob_is_int(&xfer.aToken[2], &size) ){
2719
const char *zName = blob_str(&xfer.aToken[1]);
2720
Blob content;
2721
if( size<0 ){
2722
xfer_fatal_error("invalid config record");
2723
return ++nErr;
2724
}
2725
blob_zero(&content);
2726
blob_extract(xfer.pIn, size, &content);
2727
g.perm.Admin = g.perm.RdAddr = 1;
2728
configure_receive(zName, &content, origConfigRcvMask);
2729
nCardRcvd++;
2730
nArtifactRcvd++;
2731
blob_reset(&content);
2732
blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
2733
}else
2734
2735
2736
/* cookie TEXT
2737
**
2738
** The client reserves a cookie from the server. The client
2739
** should remember this cookie and send it back to the server
2740
** in its next query.
2741
**
2742
** Each cookie received overwrites the prior cookie from the
2743
** same server.
2744
*/
2745
if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
2746
db_set("cookie", blob_str(&xfer.aToken[1]), 0);
2747
}else
2748
2749
2750
/* private
2751
**
2752
** The server tells the client that the next "file" or "cfile" will
2753
** contain private content.
2754
*/
2755
if( blob_eq(&xfer.aToken[0], "private") ){
2756
xfer.nextIsPrivate = 1;
2757
}else
2758
2759
2760
/* clone_seqno N
2761
**
2762
** When doing a clone, the server tries to send all of its artifacts
2763
** in sequence. This card indicates the sequence number of the next
2764
** blob that needs to be sent. If N<=0 that indicates that all blobs
2765
** have been sent.
2766
*/
2767
if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){
2768
blob_is_int(&xfer.aToken[1], &cloneSeqno);
2769
if( cloneSeqno<0 ){
2770
xfer_fatal_error("invalid clone_seqno");
2771
return ++nErr;
2772
}
2773
}else
2774
2775
/* message MESSAGE
2776
**
2777
** A message is received from the server. Print it.
2778
** Similar to "error" but does not stop processing.
2779
*/
2780
if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
2781
char *zMsg = blob_terminate(&xfer.aToken[1]);
2782
defossilize(zMsg);
2783
if( (syncFlags & SYNC_PUSH) && zMsg
2784
&& sqlite3_strglob("pull only *", zMsg)==0 ){
2785
syncFlags &= ~SYNC_PUSH;
2786
zMsg = 0;
2787
}
2788
if( zMsg && zMsg[0] ){
2789
fossil_force_newline();
2790
fossil_print("Server says: %s\n", zMsg);
2791
}
2792
}else
2793
2794
/* pragma NAME VALUE...
2795
**
2796
** The server can send pragmas to try to convey meta-information to
2797
** the client. These are informational only. Unknown pragmas are
2798
** silently ignored.
2799
*/
2800
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
2801
/* pragma server-version VERSION ?DATE? ?TIME?
2802
**
2803
** The server announces to the server what version of Fossil it
2804
** is running. The DATE and TIME are a pure numeric ISO8601 time
2805
** for the specific check-in of the client.
2806
*/
2807
if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){
2808
xfer.remoteVersion = g.syncInfo.remoteVersion =
2809
atoi(blob_str(&xfer.aToken[2]));
2810
if( xfer.nToken>=5 ){
2811
xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
2812
xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
2813
}
2814
xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
2815
xfer.remoteTime, 0x08 );
2816
}
2817
2818
/* pragma uv-pull-only
2819
** pragma uv-push-ok
2820
**
2821
** If the server is unwilling to accept new unversioned content (because
2822
** this client lacks the necessary permissions) then it sends a
2823
** "uv-pull-only" pragma so that the client will know not to waste
2824
** bandwidth trying to upload unversioned content. If the server
2825
** does accept new unversioned content, it sends "uv-push-ok".
2826
*/
2827
else if( syncFlags & SYNC_UNVERSIONED ){
2828
if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
2829
uvPullOnly = 1;
2830
if( syncFlags & SYNC_UV_REVERT ) uvDoPush = 1;
2831
}else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
2832
uvDoPush = 1;
2833
}
2834
}
2835
2836
/* pragma ci-lock-fail USER-HOLDING-LOCK LOCK-TIME
2837
**
2838
** The server generates this message when a "pragma ci-lock"
2839
** is attempted on a check-in for which there is an existing
2840
** lock. USER-HOLDING-LOCK is the name of the user who originated
2841
** the lock, and LOCK-TIME is the timestamp (seconds since 1970)
2842
** when the lock was taken.
2843
*/
2844
else if( blob_eq(&xfer.aToken[1], "ci-lock-fail") && xfer.nToken==4 ){
2845
char *zUser = blob_terminate(&xfer.aToken[2]);
2846
sqlite3_int64 mtime, iNow;
2847
defossilize(zUser);
2848
iNow = time(NULL);
2849
if( blob_is_int64(&xfer.aToken[3], &mtime) && iNow>mtime ){
2850
if( mtime<0 ){
2851
xfer_fatal_error("invalid ci-lock-fail time");
2852
return ++nErr;
2853
}
2854
iNow = time(NULL);
2855
fossil_print("\nParent check-in locked by %s %s ago\n",
2856
zUser, human_readable_age((iNow+1-mtime)/86400.0));
2857
}else{
2858
fossil_print("\nParent check-in locked by %s\n", zUser);
2859
}
2860
g.ckinLockFail = fossil_strdup(zUser);
2861
}
2862
2863
/* pragma avoid-delta-manifests
2864
**
2865
** Discourage the use of delta manifests. The remote side sends
2866
** this pragma when its forbid-delta-manifests setting is true.
2867
*/
2868
else if( blob_eq(&xfer.aToken[1], "avoid-delta-manifests") ){
2869
g.bAvoidDeltaManifests = 1;
2870
}
2871
2872
/* pragma link URL ARG MTIME
2873
**
2874
** The server has sent the URL for a link to another repository.
2875
** Record this as a link:URL entry in the config table.
2876
*/
2877
else if( blob_eq(&xfer.aToken[1], "link")
2878
&& xfer.nToken==5
2879
&& (syncFlags & SYNC_SHARE_LINKS)!=0
2880
){
2881
UrlData x;
2882
char *zUrl = blob_str(&xfer.aToken[2]);
2883
char *zArg = blob_str(&xfer.aToken[3]);
2884
i64 iTime = strtoll(blob_str(&xfer.aToken[4]),0,0);
2885
memset(&x, 0, sizeof(x));
2886
defossilize(zUrl);
2887
defossilize(zArg);
2888
url_parse_local(zUrl, URL_OMIT_USER, &x);
2889
if( x.protocol
2890
&& strncmp(x.protocol,"http",4)==0
2891
&& iTime>0
2892
){
2893
db_unprotect(PROTECT_CONFIG);
2894
db_multi_exec(
2895
"INSERT INTO config(name,value,mtime)\n"
2896
" VALUES('link:%q',%Q,%lld)\n"
2897
" ON CONFLICT DO UPDATE\n"
2898
" SET value=excluded.value, mtime=excluded.mtime\n"
2899
" WHERE mtime<excluded.mtime;",
2900
zUrl, zArg, iTime
2901
);
2902
db_protect_pop();
2903
}
2904
url_unparse(&x);
2905
}
2906
2907
}else
2908
2909
/* error MESSAGE
2910
**
2911
** The server is reporting an error. The client will abandon
2912
** the sync session.
2913
**
2914
** Except, when cloning we will sometimes get an error on the
2915
** first message exchange because the project-code is unknown
2916
** and so the login card on the request was invalid. The project-code
2917
** is returned in the reply before the error card, so second and
2918
** subsequent messages should be OK. Nevertheless, we need to ignore
2919
** the error card on the first message of a clone.
2920
**
2921
** Also ignore "not authorized to write" errors if this is an
2922
** autopush following a commit.
2923
*/
2924
if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
2925
char *zMsg = blob_terminate(&xfer.aToken[1]);
2926
defossilize(zMsg);
2927
if( (syncFlags & SYNC_IFABLE)!=0
2928
&& sqlite3_strlike("%not authorized to write%",zMsg,0)==0 ){
2929
autopushFailed = 1;
2930
nErr++;
2931
}else if( (syncFlags & SYNC_CLONE)==0 || nCycle>0 ){
2932
fossil_force_newline();
2933
fossil_print("Error: %s\n", zMsg);
2934
blob_appendf(&xfer.err, "server says: %s\n", zMsg);
2935
nErr++;
2936
break;
2937
}
2938
}else
2939
2940
/* Unknown message */
2941
if( xfer.nToken>0 ){
2942
if( blob_str(&xfer.aToken[0])[0]=='<' ){
2943
fossil_warning(
2944
"server replies with HTML instead of fossil sync protocol:\n%b",
2945
&recv
2946
);
2947
nErr++;
2948
break;
2949
}
2950
blob_appendf(&xfer.err, "unknown command: [%b]\n", &xfer.line);
2951
}
2952
2953
if( blob_size(&xfer.err) ){
2954
fossil_force_newline();
2955
fossil_warning("%b", &xfer.err);
2956
nErr++;
2957
break;
2958
}
2959
blobarray_reset(xfer.aToken, xfer.nToken);
2960
blob_reset(&xfer.line);
2961
}
2962
origConfigRcvMask = 0;
2963
if( nCardRcvd>0 && (syncFlags & SYNC_VERBOSE) ){
2964
fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Received:",
2965
blob_size(&recv), nCardRcvd,
2966
xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
2967
}else if( syncFlags & SYNC_QUIET ){
2968
/* No-op */
2969
}else{
2970
if( bOutIsTty!=0 ){
2971
fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
2972
nRoundtrip, nArtifactSent, nArtifactRcvd);
2973
}
2974
}
2975
nUncRcvd += blob_size(&recv);
2976
blob_reset(&recv);
2977
nCycle++;
2978
2979
/* Set go to 1 if we need to continue the sync/push/pull/clone for
2980
** another round. Set go to 0 if it is time to quit. */
2981
nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
2982
if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
2983
go = 1;
2984
mxPhantomReq = nFileRecv*2;
2985
if( mxPhantomReq<200 ) mxPhantomReq = 200;
2986
}else if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2987
/* Go another round if files are queued to send */
2988
go = 1;
2989
}else if( xfer.nPrivIGot>0 && nCycle==1 ){
2990
go = 1;
2991
}else if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ){
2992
/* Continue looping as long as new uvfile cards are being received
2993
** and uvgimme cards are being sent. */
2994
go = 1;
2995
}else if( (syncFlags & SYNC_CLONE)!=0 ){
2996
if( nCycle==1 ){
2997
go = 1; /* go at least two rounds on a clone */
2998
}else if( nFileRecv>0 ){
2999
go = 1;
3000
}else if( cloneSeqno>0 && nArtifactRcvd>nPriorArtifact ){
3001
/* Continue the clone until we see the clone_seqno 0" card or
3002
** until we stop receiving artifacts */
3003
go = 1;
3004
}
3005
}
3006
3007
nCardRcvd = 0;
3008
xfer.nFileRcvd = 0;
3009
xfer.nDeltaRcvd = 0;
3010
xfer.nDanglingFile = 0;
3011
db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
3012
if( go ){
3013
manifest_crosslink_end(MC_PERMIT_HOOKS);
3014
}else{
3015
manifest_crosslink_end(MC_PERMIT_HOOKS);
3016
content_enable_dephantomize(1);
3017
}
3018
db_end_transaction(0);
3019
}; /* while(go) */
3020
transport_stats(&nSent, &nRcvd, 1);
3021
if( pnRcvd ) *pnRcvd = nArtifactRcvd;
3022
if( (rSkew*24.0*3600.0) > 10.0 ){
3023
fossil_warning("*** time skew *** server is fast by %s",
3024
db_timespan_name(rSkew));
3025
g.clockSkewSeen = 1;
3026
}else if( rSkew*24.0*3600.0 < -10.0 ){
3027
fossil_warning("*** time skew *** server is slow by %s",
3028
db_timespan_name(-rSkew));
3029
g.clockSkewSeen = 1;
3030
}
3031
if( bOutIsTty==0 && (syncFlags & SYNC_QUIET)==0 ){
3032
fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
3033
nRoundtrip, nArtifactSent, nArtifactRcvd);
3034
fossil_force_newline();
3035
}
3036
fossil_force_newline();
3037
if( g.zHttpCmd==0 ){
3038
if( syncFlags & SYNC_QUIET ){
3039
/* no-op */
3040
}else if( syncFlags & SYNC_VERBOSE ){
3041
fossil_print(
3042
"%s done, wire bytes sent: %lld received: %lld remote: %s%s\n",
3043
zOpType, nSent, nRcvd,
3044
(g.url.name && g.url.name[0]!='\0') ? g.url.name : "",
3045
(g.zIpAddr && g.zIpAddr[0]!='\0'
3046
&& fossil_strcmp(g.zIpAddr, g.url.name))
3047
? mprintf(" (%s)", g.zIpAddr) : "");
3048
}else{
3049
fossil_print(
3050
"%s done, wire bytes sent: %lld received: %lld remote: %s\n",
3051
zOpType, nSent, nRcvd, g.zIpAddr);
3052
}
3053
}
3054
if( syncFlags & SYNC_VERBOSE ){
3055
fossil_print(
3056
"Uncompressed payload sent: %lld received: %lld\n", nUncSent, nUncRcvd);
3057
}
3058
blob_reset(&send);
3059
blob_reset(&recv);
3060
transport_close(&g.url);
3061
transport_global_shutdown(&g.url);
3062
if( nErr && go==2 ){
3063
db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
3064
manifest_crosslink_end(MC_PERMIT_HOOKS);
3065
content_enable_dephantomize(1);
3066
db_end_transaction(0);
3067
}
3068
if( nErr && autopushFailed ){
3069
fossil_warning(
3070
"Warning: The check-in was successful and is saved locally but you\n"
3071
" are not authorized to push the changes back to the server\n"
3072
" at %s",
3073
g.url.canonical
3074
);
3075
nErr--;
3076
}
3077
if( (syncFlags & SYNC_CLONE)==0 && g.rcvid && fossil_any_has_fork(g.rcvid) ){
3078
fossil_warning("***** WARNING: a fork has occurred *****\n"
3079
"use \"fossil leaves -multiple\" for more details.");
3080
}
3081
return nErr;
3082
}
3083

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button